Userid ctx return posts register new gauge metric this type gauge
Create a ServiceBroker
Quick tip: You don’t need to create manually ServiceBroker in your project. Use the Moleculer Runner to create and
execute a broker and load services. Read more about Moleculer Runner.
const { ServiceBroker } = require("moleculer");
const broker = new ServiceBroker({
Metadata option
Use metadata property to store custom values. It can be useful for a custom or
Ping
To ping remote nodes, use broker.ping method. You can ping a node, or all available nodes. It returns
{ } |
---|
Output
Output
{ |
---|
Properties of ServiceBroker
|
||
---|---|---|
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
Boolean Checks if broker is listening to an event.
Array<Object> Returns all registered event listeners for an
|
||
---|---|---|
|
The global error handler is generic way to handle exceptions. It catches the unhandled errors of action & event handlers.
Catch, handle & log the error
event definition.
Configuration
|
---|
{ |
---|
requestTimeout: 5000,
retryPolicy: {
enabled: true,
retries: 5,
delay: 100,
maxDelay: 1000,
factor: 2,
check: err => err && !!err.retryable},
registry: {
strategy: "RoundRobin",
preferLocal: true},
transit: {
maxQueueSize: 50 * 1000,
disableReconnect: false,
disableVersionCheck: false,
packetLogFilter: ["HEARTBEAT"]},
metrics: {
enabled: true,
reporter: [
"Console"
]},
}, |
---|
middlewares: ["MyMiddleware"],
replDelimiter: "mol $",
replCommands: [],started(broker) {},
stopped(broker) {}
The schema has some main parts: name, version, settings, actions, methods, events.
Simple service schema to define two
The Service has some base properties in the schema.
// posts.v1.service.js
module.exports = {
name: "posts",
version: 1
name. It can be a Number or a String.
// posts.v2.service.js
module.exports = {
name: "posts",
version: 2,
actions: {
find() {...}
}Via API Gateway, make a request to GET /v2/posts/find.
To disable version prefixing set $noVersionPrefix: true in Service settings.
},
|
|||
---|---|---|---|
} | |||
The |
Internal
There are some internal settings which are used by core modules. These setting names start with $ (dollar sign).
// mail.service.js
module.exports = {
name: "mailer",
settings: {$secu.user", "transport.auth.pass"],
}; | ||
---|---|---|
these mixins with the current schema. When a service uses mixins, all properties present in the mixin will be “mixed”
into the current service.
The above example creates an api service which inherits all properties from ApiGwService but overwrite the port
setting and extend it with a new myAction action.
hooks Deep extend with
methods Merge & overwrite.
any custom Merge & overwrite.
Merge algorithm examples
// math.service.js
module.exports = {
name: "math",
actions: {
// Shorthand definition, only a handler function
add(ctx) {
return Number(ctx.params.a) + Number(ctx.params.b); },
}; |
|
---|
Inside actions, you can call other nested actions in other services with ctx.call method. It is an alias to broker.call, but it sets itself as parent context (due to correct tracing chains).
// posts.service.js
module.exports = {
name: "posts",
actions: {
async get(ctx) {
// Find a post by ID
let post = posts[ctx.params.id];
In action handlers the this is always pointed to the Service instance.
You can subscribe to events under the events key.
// Subscribe to all "user.*" events
"user.*"(ctx) {
console.log("Payload:", ctx.params);
console.log("Sender:", ctx.nodeID);
console.log("Metadata:", ctx.meta);
console.log("The called event name:", ctx.eventName); }
}; |
|
---|
// payment.service.js
module.exports = {
name: "payment",
events: {
"order.created": {
// Register handler to the "other" group instead of "payment" group.group: "other",
handler(payload) {
// ...
// mailer.service.js
module.exports = {
name: "mailer",
actions: {
send(ctx) {
// Call the `sendMail` method
return this.sendMail(ctx.params.recipients, ctx.params.subject, ctx.params.body);
}
},methods: {
// Send an email to recipients
sendMail(recipients, subject, body) {
return new Promise((resolve, reject) => { ...
// posts.service.js
module.exports = {name: "posts",
}; | |||
---|---|---|---|
} |
because these words are reserved in the schema.
In methods the this is always pointed to the Service instance.
merged() {
// Fired after the service schemas merged and before the service instance created},
If your service depends on other services, use the dependencies property in the schema. The service waits for
dependent services before calls the started lifecycle event handler.
} |
---|
Wait for services via ServiceBroker
To wait for services, you can also use the waitForServices method of ServiceBroker. It returns a Promise which will
Set timeout & interval
broker.waitForServices("accounts", 10 * 1000, 500).then(() => { // Called if `accounts` service becomes available in 10 seconds }).catch(err => {
// Called if service is not available in 10 seconds
});
module.exports = {
name: "posts",
},
}; |
|
---|
Name Type Description
this.name String Name of service (from schema)
this.schema Object Schema definition of service
this.broker ServiceBroker Instance of broker
this.currentContext Context Get or set the current Context object.
Service
name: "math",
actions: {
});
Load service from
Load it with broker:
// Create broker
instance of Service.
const { Service } = require("moleculer");
// Export a function, the `loadService` will call with the ServiceBroker instance. module.exports = function() {
let users = [. ... ];
If you have many services (and you will have) we suggest to put them to a services folder and load all of them with
the broker.loadServices method.
Local
If you would like to use local properties/variables in your service, declare them in the created event handler.
started() {
// Listening...this.server.listen(this.settings.port);
} |
---|
Naming restriction
It is important to be aware that you can’t use variable name which is reserved for service or coincides with
Native ES6 classes with schema
Define actions and events handlers as class methods and call the parseServiceSchema method in constructor with
this.parseServiceSchema({
name: "greeter",
version: "v2",
meta: {
scalable: true
},
dependencies: [
"auth",
"users"],
Need a compiler
Please note, you must use Typescript or Babel to compile decorators.
}
// With options
@Action({
cache: false,
params: {
a: "number",
b: "number"
}
})
Login2(ctx) {
//...}
hello() { // Private
}
} |
|
---|
disable them by setting internalServices: false in broker options.
List of
withServices Boolean false List with services.
onlyAvailable Boolean false List only available nodes.
Name Type Default Description
onlyLocal Boolean false List only local services.
It lists all registered actions (local & remote).
broker.call("$node.actions").then(res => console.log(res));
skipInternal Boolean false Skip the internal actions ($node).
withEndpoints Boolean false List with endpoints (nodes).
It has some options which you can declare within params.
Options
onlyAvailable Boolean false List only available subscriptions.
List of metrics
|
Type | Default | Description |
---|---|---|---|
|
String or Array null |
broker.call("$node.health").then(res => console.log(res)); Example health info:
} |
---|
´s internalServices option.
moleculer.config.js
ule.exports = {
nodeID: "node-1",
logger: true,
internalServices: {
It has request parameters & returns response, like a HTTP request.
If you have multiple instances of services, the broker will load balance the request among instances.
The actionName is a dot-separated string. The first part of it is the service name, while the second part of it represents the action name. So if you have a posts service with a create action, you can call it as posts.create.
The params is an object which is passed to the action as a part of the Context. The service can access it via ctx.params. It is optional. If you don’t define, it will be {}.
Name | Description |
---|
null |
|
||
---|---|---|---|
Object {} | |||
|
|||
|
const res = await broker.call("user.get", { id: 3 });
Call with calling options
const res = await broker.call("$node.health", null, { nodeID: "node-21" })
Metadata
The meta is sent back to the caller service. Use it to send extra meta information back to the caller. E.g.: send response
headers back to API gateway or set resolved logged in user to metadata.
console.log(ctx.meta);
// Prints: { a: "John", b: 5 }
broker.createService({
name: "mod",
actions: {
hello(ctx) {
console.log(ctx.meta);
// Prints: { user: 'John' }
ctx.meta.age = 123
return this.actions.subHello(ctx.params, { parentCtx: ctx }); },subHello(ctx) {
console.log("meta from subHello:", ctx.meta); // Prints: { user: 'John', age: 123 }
return "hi!";
}
}
Example
// moleculer.config.js
module.exports = {
nodeID: "node-1",
requestTimeout: 3000Calling examples
// It uses the global 3000 timeout
await broker.call("greeter.normal");
// It uses the 5000 timeout from action definition
await broker.call("greeter.slow");
// It uses 1000 timeout from calling option
await broker.call("greeter.slow", null, { timeout: 1000 });meta: { token: '63f20c2d-8902-4d86-ad87-b58c9e2333c2' } }
);mcall with Object and options.meta
resolved Promise in any case and the response contains the statuses and responses of all calls. Note that, without this
option you won’t know how many (and which) calls were rejected.
] |
|
---|
const stream = fs.createReadStream(fileName);
broker.call("storage.save", stream, { meta: { filename: "avatar-123.jpg" }});
Receiving a stream in a service
module.exports = {
name: "storage",
actions: {
save(ctx) {
// Save the received stream to a file
const s = fs.createWriteStream(`/tmp/${ctx.meta.filename}`); ctx.params.pipe(s);
}
}Process received stream on the caller side
const filename = "avatar-123.jpg";
broker.call("storage.get", { filename })
.then(stream => {
const s = fs.createWriteStream(`./${filename}`);
},
}; |
|
---|
The default values is null (means published) due to backward compatibility.
Action hooks
In before hooks, it receives the ctx, it can manipulate the ctx.params, ctx.meta, or add custom variables into ctx.locals what you can use in the action handlers.
If there are any problem, it can throw an Error. Please note, you can’t break/skip the further executions of hooks or action handler.
Main usages:
property populating
remove sensitive data.
Main usages:
error handling
wrap the error into another one
fallback response
Please notice that hook registration order matter as it defines sequence by which hooks are executed. |
---|
module.exports = {
name: "posts",
mixins: [DbService]
hooks: {
before: {
// Define a global hook for all actions
// The hook will call the `resolveLoggedUser` method."*": "resolveLoggedUser",
const DbService = require("moleculer-db");
module.exports = {
name: "users",
mixins: [DbService]
hooks: {
after: {
// Define a global hook for all actions to remove sensitive data "*": function(ctx, res) {
// Remove password
delete res.password;
return res;
}
],
// Applies to all actions that start with "create-"
"create-*": [
async function (ctx, res){}
],
// Applies to all actions that end with "-user"
"*-user": [
async function (ctx, res){}
],
},
error: {
// Global error handler
"*": function(ctx, err) {
this.logger.error(`Error occurred when '${ctx.action.name}' action was called`, err);
}; | } |
---|
Before & After hooks
broker.createService({
name: "greeter",
actions: {
hello: {
hooks: {
before(ctx) {
broker.logger.info("Before action hook"); },
after(ctx, res) {
broker.logger.info("After action hook")); return res;
}
}); | } | ||
---|---|---|---|
Execution
It is important to keep in mind that hooks have a specific execution order. This is especially important to remember
When using several hooks it might be difficult visualize their execution order. However, you can set
the logLeve o quickly check the execution order of global and service level hooks.
actions: {
hello: {
hooks: {
before(ctx) {
broker.logger.info(chalk.yellow.bold(" },
after(ctx, res) {
broker.logger.info(chalk.yellow.bold(" return res;
}
},Before action hook"));
}); | } | |||
---|---|---|---|---|
|
Output produced by global, service & action level hooks
// authorize.mixin.js
module.exports = {
methods: {
checkIsAuthenticated(ctx) {
if (!ctx.meta.user)
throw new Error("Unauthenticated");
},
checkUserRole(ctx) {
if (ctx.action.role && ctx.meta.user.role != ctx.action.role) throw new Error("Forbidden");
},
checkOwner(ctx) {
// Check the owner of entity
}
}
}// posts.service.js
}; |
|
|
---|---|---|
Setting ctx.locals in before hook
module.exports = {
name: "user",
Events
Broker has a built-in event bus to support Event-driven architecture and to send events to local and remote services.
the user.created event, only one users and one payments service instance will receive the event.
Emit balanced events
Send balanced events with broker.emit function. The first parameter is the name of the event, the second parameter is
// Only the `mail` & `payments` services receives it broker.emit("user.created", user, ["mail", "payments"]);
Broadcast
broker.broadcast("config.changed", config); Specify which groups/services shall receive the event:
// Send to all "mail" service instances
broker.broadcast("user.created", { user }, "mail"); // Send to all "user" & "purchase" service instances.
Subscribe to
The v0.14 version supports Context-based event handlers. Event context is useful if you are using event-driven
signature "user.created"(payload) { ... }. It is capable to detect different signatures of event
handlers:
You can also force the usage of the new signature by setting context: true in the event declaration
Context-based event handler & emit a nested event
Subscribe to events in ‘events’ property of services. Use of wildcards (?, *, **) is available in event names.
module.exports = {
events: {
// Subscribe to `user.created` event
"user.created"(ctx) {
console.log("User created:", ctx.params);
} | } |
---|
Like in action definition, you should define params in even definition and the built-in Validator validates the
parameters in events.
The broker broadcasts some internal events. These events always starts with $ prefix.
The broker sends this event if the local node or a remote node loads or destroys services.
The broker sends this event when the circuit breaker module change its state to open.
Payload
The broker sends this event when the circuit breaker module change its state to half-open.
Payload
The broker sends this event when a node connected or reconnected.
Payload
Name Type Description
node Node Node info object
The broker sends this event when a node disconnected (gracefully or unexpectedly).
Name | Type | ||
---|---|---|---|
Node |
unexpected Boolean true - Not received heartbeat, false - Received DISCONNECT message from node.
The broker sends this event once broker.start() is called and all local services are started.
Event payload
{
"error": "<the error object with all properties>"
"module": "broker" // Name of the module where the error happened "type": "error-type" // Type of error. Full of error types: https://github.com/moleculerjs/moleculer/blob/master/src/constants.js }Event payload
{
"error": "<the error object with all properties>"
"module": "transit" // Name of the module where the error happened "type": "error-type" // Type of error. Full of error types: https://github.com/moleculerjs/moleculer/blob/master/src/constants.js }
Event payload
{
}
Context
ctx.id String Context ID
ctx.broker ServiceBroker Instance of the broker.
ctx.eventType String Type of event (“emit” or “broadcast”).
ctx.eventGroups Array<String> Groups of event.
Methods of Context
ctx.startSpan(name, opts) Span Creates a new child span.
ctx.finishSpan(span) void Finishes a span.
Enable context tracking & change the timeout value
const broker = new ServiceBroker({
nodeID: "node-1",
tracking: {
enabled: true,
shutdownTimeout: 10 * 1000
}
});Broker
This section describes what happens when the broker is starting & stopping.
Stopping
When you call broker.stop or stop the process, at first broker publishes an empty service list to remote nodes, so they will route the requests to other instances instead of services that are stopping. Next, the broker starts all local services. After that, the transporter disconnects and process exits.
at broker.createService or broker.loadService).
You can use it to create other module instances (e.g. http server, database modules) and store them in this.
started event
This handler is triggered when the broker.start is called and the broker starts all local services. Use it to connect to
This handler is triggered when the broker.stop is called and the broker starts stopping all local services. Use it to close
database connections, close sockets…etc.
This handler is called after the service schemas (including has been merged but before service is registered. It
means you can manipulate the merged service schema before it’s processed.
}; |
|
|
---|---|---|
} |
|
|
wrapper functions. It allows to wrap action handlers, event handlers, broker methods and hook lifecycle events.
Example
Some hooks are wrappers. It means that you can wrap the original handler and return a new Function.
Wrap hooks are which the first parameter is next.
const MyValidator = {
localAction(next, action) {
// Wrap with a param validator if `action.params` is defined if (_.isObject(action.params)) {
return ctx => {
this.validate(action.params, ctx.params);
return next(ctx);
};
}
}; |
|
---|
original handler. If the params property is not defined, it simply returns the original handler (skip wrapping).
If you don’t call the original next in the middleware it will break the request. It can be used in cachers. For
// Call the next
const result = await next(ctx);
Decorate core modules (extend
Middleware functions can be used to add new features to ServiceBroker or Service classes.
const res = await broker.allCall("$node.health");
localAction(next, action)
} |
---|
remoteAction(next, action)
This hook wraps the remote action handlers.
This hook wraps the local event handlers.
// my.middleware.js
module.export = {
} |
---|
// my.middleware.js
module.exports = {name: "MyMiddleware",
This hook wraps the broker.destroyService method
} |
---|
// my.middleware.js
module.export = {
name: "MyMiddleware",
} |
---|
This hook wraps the broker.emit method.
// my.middleware.js
module.export = {
name: "MyMiddleware",
} |
|
|
---|---|---|
} |
|
This hook is called after local service creating.
// my.middleware.js
module.export = {
name: "MyMiddleware",
} |
---|
// my.middleware.js
module.export = {
name: "MyMiddleware",
} |
|
---|
} |
|
---|
serviceCreating(service, schema)
} |
|
---|
This hook is called before sending a communication packet.
// my.middleware.js
module.export = {name: "MyMiddleware",
} |
---|
name: "MyMiddleware",
} |
---|
newLogEntry(type, args, bindings) (sync)
This hook is called when a new log messages iscreated.
created(broker)
This hook is called when broker created.
} |
---|
This hook is called before broker starting.
// my.middleware.js
module.export = {
name: "MyMiddleware",
// my.middleware.js
module.export = {
name: "MyMiddleware",
} |
|
---|
stopped(broker)
} |
---|
Many integrated features have been exposed as internal middlewares. These middlewares are loaded by default when broker is created. However, they can be turned off by setting the internalMiddlewares: false in broker option. In this case you must explicitly specify the required middlewares in the middlewares: [] broker option.
Internal middlewares
Cacher Optional Cacher middleware.
ContextTracker Optional Context tracker feature.
ErrorHandler AlwaysError handling.
Metrics Optional Metrics feature.
Debugging.TransitLogger Optional Transit Logger.
Debugging.ActionLogger Optional Action logger.
This middleware uses built-in Node crypto lib.
// moleculer.config.js
middlewares: [
Middlewares.Transmit.Encryption("secret-password", "aes-256-cbc", initVector) //
This middleware uses built-in Node zlib lib.
// moleculer.config.js
Middlewares.Transmit.Compression("deflate") // or "deflateRaw" or "gzip"
]
// moleculer.config.js
const { Middlewares } = require("moleculer");
logPacketData: false,
folder: null,
packetFilter: ["HEARTBEAT"]
})
logger Object or Function null Logger class.
logLevel String info Log level for built-in console logger.
color.send String grey Supports all
packetFilter Array<String> HEARTBEAT Type of o skip
// Create broker
module.exports = {
folder: null,
colors: {
})
]
logLevel String info Log level for built-in console logger.
logParams Boolean falseLogs request parameters
color.response String cyan Supports all
Event Execution
handler(ctx) { /* ... */}
}
}
};
Unlike throttling, debouncing is a technique of keeping th 0 until a period of calm, and then
Loading &
If you want to use the built-in middlewares use their names in middlewares[] broker option. Also,
// Extend with custom middleware
Middlewares.MyCustom = {
created(broker) {
broker.logger.info("My custom middleware is created!"); }};
nodes. These message brokers mainly support publish/subscribe messaging pattern.
Transporter is an important module if you are running services on multiple nodes. Transporter communicates with other
There are several built-in transporters in Moleculer framework.
TCP
be a static list in your configuration or a file path which contains the list.
Use TCP transporter with default options
// UDP port
udpPort: 4445,
// UDP bind address (if null, bind on all interfaces) udpBindAddress: null,
// UDP sending period (seconds)
udpPeriod: 30,// Multicast address.
}; | } |
|
---|
TCP transporter with static endpoint list
It needs to start with tcp://.
// moleculer.config.js
module.exports = {
nodeID: "node-1",
transporter: "tcp://172.17.0.1:6000/node-1,172.17.0.2:6000/node- 2,172.17.0.3:6000/node-3"
]
Serviceless node
NATS
Built-in transporter for
To use this transporter install the nats module with npm install nats --save command.
Connect to ‘nats://localhost:4222’
Connect to a remote NATS server with auth
// moleculer.config.js
module.exports = {
transporter: "nats://user:pass@nats-server:4222" };
// moleculer.config.js
module.exports = {transporter: {
// moleculer.config.js
module.exports = {
nodeID: "server-1",
transporter: "redis://redis.server:6379"
};Dependencies
Connect with connection string
// moleculer.config.js
module.exports = {
transporter: "redis://localhost:6379"
};
};
Connect to Redis cluster
Dependencies
To use this transporter install the mqtt module with npm install mqtt --save command.
// moleculer.config.js
module.exports = {
transporter: "mqtt://mqtt-server:1883"
};
Connect to secure MQTT server
AMQP (0.9)
Built-in transporter for 0.9 protocol (e.g.: .
Options can be passed to amqp.connect() method.
Connect to ‘amqp://guest:guest@localhost:5672’
// moleculer.config.js
module.exports = {
transporter: "amqps://rabbitmq-server:5672" });
Connect to a remote server with options & credentials
// moleculer.config.js
module.exports = {
transporter: "amqp10://activemq-server:5672" };Dependencies
Connect to ‘amqp10://guest:guest@localhost:5672’
// moleculer.config.js
module.exports = {
transporter: "AMQP10"
};
};
Kafka
To use this transporter install the kafka-node module with npm install kafka-node --save command.
Connect to Zookeeper
// KafkaClient options. More info: https://github.com/SOHU-Co/kafka- node#clientconnectionstring-clientid-zkoptions-noackbatchoptions-ssloptions client: {
zkOptions: undefined,
noAckBatchOptions: undefined,
sslOptions: undefined},
Built-in transporter for
It is a simple implementation. It transfers Moleculer packets to consumers via pub/sub. There are not
--save command.
Connect with default settings
// moleculer.config.js
module.exports = {
transporter: {
type: "STAN",
options: {
url: "stan://127.0.0.1:4222",
clusterID: "my-cluster"
}
}
};
Custom transporter
class MyTransporter extends BaseTransporter {
connect() { /*...*/ }
disconnect() { /*...*/ }
subscribe() { /*...*/ }
send() { /*...*/ }
}
Use custom
Some transporter servers have built-in balancer solution. E.g.: RabbitMQ, NATS, NATS-Streaming. If you want to use
the transporter balancer instead of Moleculer balancer, set the disableBalancer broker option to true.
If you disable the built-in Moleculer balancer, all requests & events will be transferred via transporter
(including local requests). E.g. you have a local math service and you call math.add locally, the request will
Note that certain data types (e.g., Date, Map, BigInt) cd with native JSON serializer. If
you are working with this kind of data consider using or any other binary serializer.
This is the default serializer. It serializes the packets to JSON string and deserializes the received data to packet.
// moleculer.config.js
module.exports = {
serializer: "JSON" // not necessary to set, because it is the default };
To use this serializer install the avsc module with npm install avsc --save command.
MsgPack serializer
}; |
---|
Notepack
Built-in erializer.
// moleculer.config.js
module.exports = {
serializer: "Notepack"
};
Dependencies
To use this serializer install the notepack module with npm install notepack.io --save command.
CBOR serializer
CBOR ) is the han any other serializers.
Example
// moleculer.config.js
module.exports = {
logger: true,
serializer: "CBOR"
};
Custom
Use custom
// moleculer.config.js
const MySerializer = require("./my-serializer");
Local discovery (default option) uses the module to exchange node info and heartbeat packets (for more info about packet structure check . It’s the simplest and the fastest among the available discovery mechanisms as it doesn’t require any external solutions. However, this discovery method also has some drawbacks, especially for large scale deployments with >100 nodes. The heartbeat packets can generate large amount traffic that can saturate the communication bus and, therefore, deteriorate the performance of actions and events, i.e., slow down the delivery of request/response and event packets.
Please note the TCP transporter uses Gossip protocol & UDP packets for discovery & heartbeats, it means it can work only with local discovery mechanism.
registry: {
discoverer: {
type: "Local",
options: {
// Send heartbeat in every 10 seconds
heartbeatInterval: 10,// Heartbeat timeout in seconds
heartbeatTimeout: 30,
This approach reduces the load over the transporter module, it’s used exclusively for the exchange of the request,
response, event packets.
periodicity depends on the heartbeatInterval broker option.
To use Redis discovery install the ioredis module with the npm install ioredis --save command.
// moleculer.config.js
module.exports = {
registry: {
discoverer: "redis://redis-server:6379"
}}
} |
---|
// Serializer
serializer: "JSON",// Full heartbeat checks. It generates more network traffic // 10 means every 10 cycle.
// Send heartbeat in every 10 seconds
heartbeatInterval: 10,// Heartbeat timeout in seconds
heartbeatTimeout: 30,
Etcovery d is very similar to t stores heartbeat and discovery packets
at etcd3’s option will remove heartbeat info of nodes that have crashed or disconnected from the
Example to connect local etcd3 server
// moleculer.config.js
module.exports = {
registry: {
discoverer: "Etcd3"
}
Example with options
// moleculer.config.js
module.exports = {
registry: {
discoverer: {
type: "Etcd3",
options: {
etcd: {
// etcd3 connection options.fullCheck: 10,
// --- COMMON DISCOVERER OPTIONS ---
} | } | } | } |
|
---|
Tip: To further reduce network traffic use nstead of JSON.
Moleculer has a built-in service registry module. It stores all information about services, actions, event listeners and
nodes. When you call a service or emit an event, broker asks the registry to look up a node which is able to execute the
Built-in
To configure strategy, set strategy broker options under registry property. It can be either a name (in case of built-in
// moleculer.config.js
module.exports = {
registry: {
strategy: "RoundRobin"
}};
};
CPU usage-based
}
};
only ping one node / host. Since the node list can be very long, it gets samples and selects the host with the lowest
latency only from a sample instead of the whole node list.
Name |
|
Description |
---|---|---|
|
||
lowLatency |
|
|
Sharding strategy
Shard invocation strategy is based on consistent-hashing algorithm. It uses a key value from context params or meta to
Example of a shard key user.id in context meta
// moleculer.config.js
module.exports = {
registry: {
strategy: "Shard",
strategyOptions: {
shardKey: "#user.id"
}
}
};
vnodes Number 10 Number of virtual nodes
ringSize Number 2^32 Size of the ring
You can overwrite globally defined load balancing strategy in action/event definitions.
Using ‘Shard’ strategy for ‘hello’ action instead of global ‘RoundRobin’
Custom strategy can be created. We recommend to copy the source of RandomStrategy and implement
the select method.
module.exports = MyStrategy;
Use custom strategy
The ServiceBroker first tries to call the local instances of service (if exists) to reduce network latencies. It means, if the
given service is available on the local broker, the configured strategy will be skipped and the broker will call the local
Fault tolerance
Moleculer has several built-in fault-tolerance features. They can be enabled or disabled in broker options.
The Circuit Breaker can prevent an application from repeatedly trying to execute an operation that’s likely
to fail. Allowing it to continue without waiting for the fault to be fixed or wasting CPU cycles while it
Enable it in the broker options
const broker = new ServiceBroker({
circuitBreaker: {
enabled: true,
threshold: 0.5,
minRequestCount: 20,
Name | Type | Default |
|
|
---|---|---|---|---|
|
|
|||
|
||||
|
||||
If the circuit-breaker state is changed, ServiceBroker will send internal events.
These global options can be overridden in action definition, as well.
Retry
Name Type Default Description
enabled Boolean false Enable feature.
check Function err && !!err.retryable A function to check failed requests.
Overwrite the retries value in calling option
}; |
|
---|
Timeout
});
Overwrite the timeout value in calling option
call has already been rejected with a RequestTimeoutError error.
Bulkhead feature is implemented in Moleculer framework to control the concurrent request handling of actions.
Name Type Default Description
enabled Boolean falseEnable feature.
throw QueueIsFull exception for every addition requests.
Action
Events
Event handlers also support eature.
Fallback feature is useful, when you don’t want to give back errors to the users. Instead, call an other action or return
some common content. Fallback response can be set in calling options or in action definition. It should be
});
Fallback in action
Fallback as a function
module.exports = {
name: "recommends",
actions: {
add: {
fallback: (ctx, err) => "Some cached result", handler(ctx) {
// Do something
}
}
}
};
}; |
---|
Caching
Moleculer has a built-in caching solution to cache responses of service actions. To enable it, set a cacher type in
});
// Create a service
broker.createService({
name: "users",
actions: {
list: {
// Enable caching to this action
cache: true,
handler(ctx) {
this.logger.info("Handler called!"); return [
{ id: 1, name: "John" },
{ id: 2, name: "Jane" }
]
}
}
}
Console messages:
[2017-08-18T13:04:33.845Z] INFO dev-pc/BROKER: Broker started. [2017-08-18T13:04:33.848Z] INFO dev-pc/USERS: Handler called! [2017-08-18T13:04:33.849Z] INFO dev-pc/BROKER: Users count: 2
The cacher generates key from service name, action name and the params of context.
The syntax of key is:
posts.find:limit|5|offset|20
The params object can contain properties that are not relevant for the cache key. Also, it can cause performance issues if
{
name: "posts",
actions: {
list: {
cache: {
// generate cache key from "limit", "offset" params and "user.id" meta keys: ["limit", "offset","#user.id"]
},
handler(ctx) {
return this.getList(ctx.params.limit, ctx.params.offset);
}
}
}
}// If params is { limit: 10, offset: 30 } and meta is { user: { id: 123 } }, // the cache key will be:
// posts.list:10|30|123
concatenated params in the key with maxParamsLength cacher option. When the key is longer than the configured limit
value, the cacher calculates a hash (SHA256) from the full key and adds it to the end of the key.
Generate a limited-length key
const broker = new ServiceBroker({
Conditional caching allows to bypass the cached response and execute an action in order to obtain “fresh” data.
To bypass the cache set ctx.meta.$cache to false before calling an action.
flag within the request.
Example of a custom conditional caching function
} | ||
---|---|---|
}; |
// Use custom `enabled` function to turn off caching for this request broker.call("greeter.hello", { name: "Moleculer", noCache: true }))
Default TTL setting can be overriden in action definition.
}
}
}
});
Custom
// Save to cache
broker.cacher.set("mykey.a", { a: 5 });// Get from cache (async)
await broker.cacher.clean("mykey.**");
// Clean all entries
Clear
When you create a new model in your service, you have to clear the old cached model entries.
this.broker.cacher.clean("users.**");
// Clear multiple cache entries
this.broker.cacher.clean([ "users.**", "posts.**" ]);
} | } | } |
---|
only required for non-centralized cachers like Memory or MemoryLRU.
Example
methods: {
cleanCache() {
// Broadcast the event, so all service instances receive it (including this
} |
---|
Clear cache among different services
Service dependency is a common situation. E.g. posts service stores information from users service in cached entries (in case of populating).
The author field is received from users service. So if the users service clears cache entries, the posts service has to clear own cache entries, as well. Therefore you should also subscribe to the cache.clear.users event in posts service. To make it easier, create a CacheCleaner mixin and define in the dependent services schema.
cache.cleaner.mixin.js
const CacheCleaner = require("./cache.cleaner.mixin"); module.exports = {
name: "posts",
mixins: [CacheCleaner([
"users",
"posts"
])],
actions: {
//...}
Moleculer also supports cache locking feature. For detailed info
Enable Lock
const broker = new ServiceBroker({
cacher: {
ttl: 60,
lock: {
ttl: 15, // The maximum amount of time you want the resource locked in seconds
staleTime: 10, // If the TTL is less than this number, means that theresources are staled
}
}
seconds
staleTime: 10, // If the TTL is less than this number, means that the resources are staled
}
}});
are staled
},
// Redlock settings
redlock: {
// Redis clients. Support node-redis or ioredis. By default will use the local client.clients: [client1, client2, client3],
// thedetails
// see
driftF
} | } | } | |
---|---|---|---|
}); |
Memory
MemoryCacher is a built-in memory cache module. It stores entries in the heap memory.
const broker = new ServiceBroker({
cacher: true});
ttl Number null Time-to-live in seconds.
clone Boolean or Function falseClone the cached data when return it.
The cacher uses the lodash _.cloneDeep method for cloning. To change it, set a Function to the clone option instead
of a Boolean.
options: {
clone: data => JSON.parse(JSON.stringify(data))
LRU memory cacher is a built-in module. It deletes the least-recently-used items.
Enable LRU cacher
let broker = new ServiceBroker({
logLevel: "debug",
max: 100,
// Time-to-Live
Options
Name Type Default Description
maxParamsLength Number null Maximum length of params in generated keys.
lock Boolean or Object null Enable lock feature.
RedisCacher is a built-in based distributed cache module. It uses ibrary.
Use it, if you have multiple instances of services because if one instance stores some data in the cache, other instances
With connection string
const broker = new ServiceBroker({
cacher: "redis://redis-server:6379"
monitor: false
// Redis settings
redis: {host: "redis-server",
const broker = new ServiceBroker({
nodeID: "node-123",
// Using MessagePack serializer to store data.
serializer: "MsgPack",
}); | } | ||
---|---|---|---|
const broker = new ServiceBroker({
cacher: {
ttl Number null Time-to-live in seconds. Disabled: 0 or null
monitor Boolean falseEnable Redis client f enabled, every client
maxParamsLength Number null Maximum length of params in generated keys.
serializer String "JSON" Name of a built-in serializer.
Dependencies
To be able to use this cacher, install the ioredis module with the npm install ioredis --
Create custom
const BaseCacher = require("moleculer").Cachers.Base;
const broker = new ServiceBroker({
cacher: new MyCacher()});
Default usage
//moleculer.config.js
module.exports = {
nodeID: "node-100",
validator: true // Using the default Fastest Validator
//moleculer.config.js
module.exports = {
nodeID: "node-100",
validator: {
type: "Fastest",
} | } |
|
---|
const broker = new ServiceBroker({
validator: true // Default is true
});broker.createService({
name: "say",
actions: {
hello: {
// Validator schema for params
params: {
name: { type: "string", min: 2 } },
handler(ctx) {
return "Hello " + ctx.params.name;
broker.call("say.hello", { name: "Walter" }).then(console.log) .catch(err => console.error(err.message));
// -> "Hello Walter"
Example validation schema
Find more information about validation schema in the
Async custom
Using custom async validation
// posts.service.js
module.exports = {
name: "posts",
actions: {
params: {
$$async: true,
owner: { type: "string", custom: async (value, errors, schema, name, parent, context) => {
} | } |
|
---|
Events Validation
definitions
params: {
from: "string|optional",
to: "email",
subject: "string"
},
handler(ctx) {
this.logger.info("Event received, parameters OK!", ctx.params); }
}
}
};
Custom
class MyValidator extends BaseValidator {}
module.exports = {
nodeID: "node-100",
validator: new MyValidator()
}
compile(schema) {
return (params) => this.validate(params, schema);}
// --- TEST BROKER ---
broker.createService({
name: "greeter",
actions: {
} | |||
---|---|---|---|
} |
.catch(err => broker.logger.error(err.message, err.data));
Metrics
Enable metrics & define console reporter
// moleculer.config.js
"Console"
]
reporter Object or Array<Object> null Metric reporter configuration.
collectProcessMetrics Boolean
Default bucket values for histograms. Default: [0.005,
60 | 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1, 2.5, 5, | ||
---|---|---|---|
|
|||
Default quantiles for histograms. Default: [0.5, 0.9, | |||
|
|||
defaultMaxAgeSeconds | |||
10 |
|
||
|
sum |
Name Type Default Description
includes String or Array<String> null List of metrics to be exported.
// moleculer.config.js
module.exports = {
metrics: {
enabled: true,
reporter: [
{
type: "Console",
options: {
includes: ["moleculer.**.total"],
excludes: ["moleculer.broker.**","moleculer.request.**"],metricNamePrefix: "mol:", // Original "moleculer.node.type". With prefix: "mol:moleculer.node.type"
metricNameSuffix: ".value", // Original "moleculer.node.type". With prefix: "moleculer.node.type.value"
logger: null,
// Using colors
colors: true,
// Prints only changed metrics, not the full list.onlyChanges: true
}
}
]
}
};
- "metric" - save metrics to individual files //
// - "label" - save metrics by labels to individual files mode: "metric",
}; |
|
] | } | } |
---|
// moleculer.config.js
module.exports = {
metrics: {
enabled: true,
reporter: [
{
type: "Event",
options: {
// Event name
eventName: "$metrics.snapshot", // Broadcast or emit
broadcast: false,
// Event groups
groups: null,
// Send only changed metrics onlyChanges: false,
// Sending interval in seconds interval: 5,
}
}
]
}};
Prometheus
Prometheus reporter publishes metrics in Prometheus format. The server can collect them. Default port
// moleculer.config.js
module.exports = {
metrics: {
enabled: true,
reporter: [
{
type: "StatsD",
options: {
// Server host
host: "localhost",
// Server port
port: 8125,
// Maximum payload size.maxPayloadSize: 1300
}
}
]
}
Create custom metrics
const BaseReporter = require("moleculer").MetricReporters.Base;
Gauge provides the following methods:
increment(labels?: GenericObject, value?: number, timestamp?:
number)
decrement(labels?: GenericObject, value?: number, timestamp?:
number)
set(value: number, labels?: GenericObject, timestamp?: number)
Histogram
A histogram samples observations (usually things like request durations
or response sizes) and counts them in configurable buckets. It also
provides a sum of all observed values and calculates configurable
quantiles over a sliding time window. It can also provide 1-minute
rate.
Histogram provides the following methods:
observe(value: number, labels?: GenericObject, timestamp?: number)
An info is a single string or number value like process arguments,
hostname or version numbers.
|
---|
OS metrics
New metric registration
You can easily create custom metrics.
get(ctx) {
// Increment metric
this.broker.metrics.increment("posts.get.total", 1);
}; |
---|
name: "posts",
},
}; |
---|
actions: {
// Create a new post
create(ctx) {
// Measure the post creation time
const timeEnd = this.broker.metrics.timer("posts.creation.time"); const post = await this.adapter.create(ctx.params);const duration = timeEnd();
}; | |||
---|---|---|---|
}); |
|
Errors
Parameters
Name Type Default Description
Example
const { MoleculerError } = require("moleculer").Errors;
Parameters
Name Type Default Description
Example
const { MoleculerRetryableError } = require("moleculer").Errors;
Error for client error which is not retryable. Parameters are same as .
sses
Throw it if your request is timed out.
Error code: 504
Retryable: true
Type: REQUEST_TIMEOUT
Throw it if there are too many active requests.
Error code: 429
Retryable: true
Type: QUEUE_FULL
Error code: 500
Retryable: false
Type: MAX_CALL_LEVELServiceSchemaError
Error code: 500
Retryable: false
Type: BROKER_OPTIONS_ERRORThrow it if shutdown is timed out.
Throw it if transporter receives unknown data.
Error code: 500
Retryable: false
Type: INVALID_PACKET_DATA
Preserve custom error classes while transferring between re
For this purpose provide your own Regenerator. We recommend looking at the source code of and
Create custom
const { Regenerator, MoleculerError } = require("moleculer").Errors; const { ServiceBroker } = require("moleculer");
} |
---|
module.exports = CustomRegenerator;
Use custom regenerator
Moleculer Runner is a helper script that helps you run Moleculer projects. With it, you don’t need to create a ServiceBroker instance with options. Instead, you can create a moleculer.config.js file in the root of repo with broker options. Then simply call the moleculer-runner in NPM script, and it will automatically load the configuration file, create the broker and load the services. Alternatively, you can declare your configuration as environment variables.
Production-ready
Option Type Default Description
-r, --repl Boolean falseIf true, it switches to mode after broker started.
-E, --envfile String null Load environment variables from the specified file. <file>
-i, --instances Number null Launch [number] node instances or max for all cpu cores
The start script is to load the default moleculer.config.js file if it exists, otherwise only loads options from environment variables. Starts 4 instances of broker, then they start all services from the services folder. Run it with npm start command.
Configuration loading
3.If not defined, it loads the moleculer.config.js file from the current directory. If it does not exist, it loads
the moleculer.config.json file.
To overwrite broker’s deeply nested default options, which are not present in moleculer.config.js, via
environment variables, use the L_ prefix and double underscore for nested properties in .env file. For
// moleculer.config.js
module.exports = {
nodeID: "node-test",
logger: true,
logLevel: "debug",transporter: "nats://localhost:4222",
requestTimeout: 5 * 1000,
Moleculer Runner also supports asynchronous configuration files. In this case moleculer.config.js must export
a Function that returns a Promise (or you can use async/await).
This function runs with the MoleculerRunner instance as the this context. Useful if you need to access the
flags passed to the runner. Check the ource more details.
# Shorthand transporter
TRANSPORTER=nats://localhost:4222
REQUESTTIMEOUT=5000# Nested property
CIRCUITBREAKER_ENABLED=true
with SERVICES and SERVICEDIR environment variables.
Loading steps:
it applies and load the found files.
Please note: shorthand names can also be used in SERVICES env var.
It loads all *.service.js files from the my-services folder (including sub-folders too).
Glob
!services/others/**/*.service.js - skip all services in the services/others folder and sub-folders.
services/others/mandatory/main.service.js - load the exact service.
.env files
Moleculer runner can load .env file at starting. There are two new cli options to load env file:
moleculer-web
The moleculer-web is the official API gateway service for Moleculer framework. Use it to publish your services as RESTful APIs.
Install
npm i moleculer-web
const { ServiceBroker } = require("moleculer");
const ApiService = require("moleculer-web");const broker = new ServiceBroker();
Call math.add action with params: http://localhost:3000/math/add?a=25&b=13
Get health info of node: http://localhost:3000/~node/health
settings: {
routes: [{path: "/api",
}); | }] |
---|
method types.
Using named parameters in aliases is possible. Named parameters are defined by prefixing a colon to the parameter
Aliases Action
The API gateway implements listAliases that lists the HTTP endpoints to actions mappings.
}); |
---|
broker.createService({
mixins: [ApiService],
broker.createService({
mixins: [ApiService],
}); |
---|
E.g.: To access the broker, use req.$service.broker.
restrict - enable to request only the routes with aliases.
broker.createService({
mixins: [ApiService],
}); |
---|
Example
const ApiGateway = require("moleculer-web");
module.exports = {
mixins: [ApiGateway],
aliases: {
// File upload from HTML multipart form "POST /": "multipart:file.save",// File upload from AJAX or cURL
"PUT /:id": "stream:file.save",
}); |
|
] | } |
|
---|
ctx.meta.$multipart contains the additional text form-data fields must be sent before other files fields.
The auto-alias feature allows you to declare your route alias directly in your services. The gateway will dynamically
// api.service.js
module.exports = {mixins: [ApiGateway],
aliases: {
"GET /hi": "test.hello"
},
actions: {
list: {
// Expose as "/api/v2/posts/"
rest: "GET /",
handler(ctx) {}
},get: {
}, |
---|
update: {
rest: "PUT /:id",
handler(ctx) {}},
|
|
|
---|---|---|
settings: {
// Base path
rest: "posts/"},
Disable
To disable parameter merging set mergeParams: false in route settings. In this case the parameters is separated.
}); | }] |
---|
Un-merged req.$params:
} |
---|
More information: https://github.com/ljharb/qs
Array parameters
foo: {
bar: ["a", "b"],
baz: "c"}
use: [
cookieParser(),
helmet()],
import { Service, ServiceSchema } from "moleculer"; import ApiGatewayService from "moleculer-web"; const swStats = require("swagger-stats");
const swMiddleware = swStats.getMiddleware();
}, |
|
---|
async started(this: Service): Promise<void> {
this.addRoute({
path: "/",
use: [swMiddleware],
});
},
broker.createService({
mixins: [ApiService],
settings: {
// Global middlewares. Applied to all routes.use: [
cookieParser(),
helmet()
passport.initialize(),
passport.session(),
broker.createService({
mixins: [ApiService],settings: {
assets: {
// Root folder of assets
folder: "./assets",
}); | } |
---|
set timeout, retries or fallbackResponse options for routes. Read more about calling options
Please note that you can also set the timeout for an action directly in its definition
}
broker.createService({
mixins: [ApiService],settings: {
routes: [
{
path: "/admin",
}); | ] | } |
|
---|
Response type & status code
Available meta fields:
};
} |
|
---|
1.Set authorization: true in your routes
2.Define the authorize method in service.
settings: {
routes: [{
// First thing
authorization: true
}]
},methods: {
// Second thing
authorize(ctx, route, req, res) {
// Read the token from header
let auth = req.headers["authorization"];
if (auth && auth.startsWith("Bearer")) {
} | } | } |
|
---|---|---|---|
|
1.Set authentication: true in your routes
2.Define your custom authenticate method in your service
mixins: ApiGatewayService,
settings: {
routes: [{
// Enable authentication
authentication: true
response data.
broker.createService({
mixins: [ApiService],
}); | ] | } |
|
---|
In previous versions of Moleculer Web, you couldn’t manipulate the data in onAfterCall. Now you can,
broker.createService({
mixins: [ApiService],
settings: {routes: [{
Error
Usage
const svc = broker.createService({
mixins: [ApiService],allowedHeaders: [],
// Configures the Access-Control-Expose-Headers CORS header.exposedHeaders: [],
// Configures the Access-Control-Allow-Credentials CORS header.
}); |
|
}] |
|
---|
settings: {
rateLimit: {
// How long to keep record of requests in memory (in milliseconds).// Defaults to 60000 (1 min)
} |
---|
}
} |
|
---|
Custom etag Function
module.exports = {
name: "export",
actions: {
// Download response as a file in the browser downloadCSV(ctx) {
ctx.meta.$responseType = "text/csv"; ctx.meta.$responseHeaders = {
"Content-Disposition": `attachment; filename="data-${ctx.params.id}.csv"`,
"ETag": '<your etag here>'
};
return csvFileStream;
}
}
}
HTTP2
port: 8443,
// HTTPS server with certificate
https: {
key: fs.readFileSync("key.pem"),
cert: fs.readFileSync("cert.pem")
You can use Moleculer-Web as a middleware in an application.
Usage
}); |
---|
// Use ApiGateway as middleware
app.use("/api", svc.express());// Listening
app.listen(3000);
// Exposed port
port: 3000,// Exposed IP
ip: "0.0.0.0",// Global-level middlewares
use: [
compression(),
cookieParser()],
// Optimize route & alias paths (deeper first). optimizeOrder: true,
// Routes
routes: [
{
// Path prefix to this route (full path: /api/admin ) path: "/admin",],
// Action aliases
aliases: {
"POST users": "users.create",
"health": "$node.health"
// Whitelist of actions (array of string mask or regex) whitelist: [
"posts.*",
"file.*",
/^math\.\w+$/// Use bodyparser module
bodyParsers: {
json: false,
urlencoded: { extended: true }},
], |
|
|
---|---|---|
|
||
Service
This service method (this.addRoute(opts, toBottom = true)) add/replace a route. For example, you can call it from your mixins to define new routes (e.g. swagger route, graphql route, etc.).
open HTTPS server
aliases
multiple body-parsers
simple server with RESTful aliases
start socket.io websocket server
static files
middlewares
multiple body-parsers
before & after hooks
webpack config file
compression
Hot-replacement
Babel, SASS, SCSS, Vue SFC
Usage
Switch broker to REPL mode
});
REPL Commands
broadcastLocal <eventName> Broadcast an event locally
cache Manage cache
destroy <serviceName> Destroy a local service
emit <eventName> Emit an event
load <servicePath> Load a service from file
loadFolder <serviceFolder> [fileMask] Load all services from folder
help [command] display help for command
List
-f, --filter <match>filter nodes (e.g.: 'node-*')
--raw print service registry to JSON
List
-f, --filter <match>filter services (e.g.: 'user*')
-i, --skipinternal skip internal services
List
-f, --filter <match>filter actions (e.g.: 'users.*')
-i, --skipinternal skip internal actions
List
-f, --filter <match>filter event listeners (e.g.: 'user.*')
-i, --skipinternal skip internal event listeners
Show common
mol $ info
Output
Call an action with params, meta & options
mol $ call "math.add" --a 5 --#b Bob --$timeout 1
Params will be { a: 5 }, meta will be { b: 'Bob' } and options will be { timeout: 1 }.mol $ call "math.add" --load my-params.json
It tries to load the my-params.jon file to params.Call with file stream
Direct
Get health info from node-12 node
mol $ emit "user.created" --a 5 --b Bob --c --no-d --e.f "hello" Params will be { a: 5, b: 'Bob', c: true, d: false, e: { f: 'hello' } }
Emit an event with params &
Parameters
Please note, parameters can be passed only as JSON string.
mol $ bench math.add '{ "a": 50, "b": 32 }'
Load a service from file
Cache
You can list keys of cache entries with
mol $ cache keys
Options
-f, --filter <match>filter keys
Cache
You clear the cache with:
mol $ cache clear
that by default removes all the entries. If you want to remove a subset
of entries, you must add a pattern: Clear with
pattern
mol $ cache clear greeter.*
Event listener
REPL can subscribe and listen to events. To subscribe use:
mol $ listener add user.created
Subscribe with group option
mol $ listener add user.created --group abcd
To unsubscribe use:
mol $ listener remove user.created
// moleculer.config.js
module.exports = {
replCommands: [
{
command: "hello <name>",
description: "Call the greeter.hello service with name",
alias: "hi",
options: [
{ option: "-u, --uppercase", description: "Uppercase the name" } ],
types: {
string: ["name"],
boolean: ["u", "uppercase"]
},
//parse(command, args) {},
//validate(args) {},
//help(args) {},
allowUnknownOptions: true,
action(broker, args/*, helpers*/) {
const name = args.options.uppercase ? args.name.toUpperCase() : args.name; return broker.call("greeter.hello", { name }).then(console.log); }
}
]
};mol $ hello -u John
Hello JOHN$ npm i -g moleculer-cli
Commands
and generates a new module to the ./my-project folder.
Answers from
You can disable the automatic NPM dependency installation with --no-install argument. It can be useful to generate project programmatically.
$ moleculer init project my-project --answers ./answers.json --no-install
Docker & Docker Coe files
tests & c with
tests & c with
lint with
readme skeleton
tests & coverage with
The shorthand repo notation is passed to o it can be bitbucket:username/repo for a Bitbucket repo and username/repo#branch for tags or branches.
Local Templates
$ moleculer alias-template myAlias somegithubuser/reponame
$ moleculer alias-template otherAlias ./path/to/some-local/custom/template$ moleculer init myAlias my-project
The meta.js file exports a function that returns an object defining the Moleculer CLI init interface. The function takes a
parameter values that gives access to external values passed in from the CLI. The object has several keys which are
The before function executes before the transformation is run, the after function executes after the transformation is
run, and the complete function executes after the transformation is run and the files are copied to the destination
The filters object takes a set of keys matching a path and a value matching the name of a question variable. If the
question variable’s value is false, the specified path will be ignored during the transformation and those files will not
Handlebars can also transform file names.
This command starts a new ServiceBroker locally and switches to REPL mode.
--config, -cLoad configuration from a file [string] [default: ""]
--ns Namespace [string] [default: ""]
[string] [default: null]
Connect
$ moleculer connect nats://localhost:4222
# Connect to Redis
$ moleculer connect amqp://localhost:5672
# Load all options from config file
--config, -cLoad configuration from a file [string] [default: ""]
--ns Namespace [string] [default: ""]
--commands Custom REPL command file mask (e.g.: ./commands/*.js)
[string] [default: null]
Options
--version Show version number [boolean]
--ns Namespace [string] [default: ""]
--level Logging level [string] [default: "silent"]
Example with params & meta
moleculer call math.add --transporter NATS --@a 5 --@b 3 --#meta-key MyMetaValue
TRANSPORTER=nats://localhost:42222 moleculer call math.add --@a 5 --@b 3
Emit
--help Show help [boolean]
--config, -c Load configuration from a file [string] [default: ""]
--id NodeID [string] [default: null]
--serializer Serializer [string] [default: null]
Example with params & meta
moleculer emit math.add --transporter NATS --@id 3 --@name John --#meta-key MyMetaValue
Features
Try it in your browser!
Base Adapter
Moleculer’s default adapter is based on Use it to quickly set up and
test you prototype.
const broker = new ServiceBroker();
// Create a DB service for `user` entities
broker.createService({
name: "users",
// Create a new user
.then(() => broker.call("users.create", {
username: "john",
name: "John Doe",
status: 1
}))
// Get all users
.then(() => broker.call("users.find").then(console.log));
Settings
All DB adapters share a common set of settings:
DB adapters also implement CRUD operations. These actions are published methods and can be called by other
services.
fields Array.<String>- Fieldsfilter.
limit Number required Max count of rows.
query Object required Query object. Passes to adapter.
Type: Array.<Object> - List of found entities.
searchFields String required Fields list for searching.
query Object required Query object. Passes to adapter.
populate Array.<String>- Populated fields.
fields Array.<String> - Fields filter.
searchFields String required Fields for searching.
query Object required Query object. Passes to adapter.
Property |
|
|||
---|---|---|---|---|
- | - |
|
Property Type Default Description
entity Object - Entity to save.
Property Type Default Description
id any, Array.<any> required ID(s) of entity.
update
Update an entity by ID.
No input parameters.
Type: Object - Updated entity.
DB adapters also has a set of helper
Get entity(ies) by ID(s).
Property |
|
|||
---|---|---|---|---|
- | - |
|
Type: any
Decode ID of entity.
Find entities by query.
Property Type Default Description
sort String required Sorted fields.
search String required Search text.
_count
Get count of entities by query.
Type: Number
Count of found entities.
fields Array.<String>- Fieldsfilter.
page Number required Page number.
query Object required Query object. Passes to adapter.
Type: Object
Type: Object
entities Array.<Object> - Entities to save.
Type: Object, Array.<Object>
id any, Array.<any> required ID(s) of entity.
populate Array.<String> - Field list for populate.
Update an entity by ID.
After update, clear the cache & call lifecycle events.
Remove an entity by ID.
Property Type Default Description
You can easily use o modify (e.g. add timestamps, hash user’s passwords or remove sensitive info) before or after saving the data in DB.
Example of hooks adding a timestamp and removing sensitive data
// Load DB actions
mixins: [DbService],// Add Hooks to DB actions
hooks: {
before: {
create: [
function addTimestamp(ctx) {
// Add timestamp
ctx.params.createdAt = new Date();
return ctx;
}
]
},
after: {
get: [
// Arrow function as a Hook
(ctx, res) => {
// Remove sensitive data
delete res.mail;
delete res.phoneNumber;Example of populate schema
broker.createService({
name: "posts",
mixins: [DbService],
settings: {
populates: {
// Shorthand populate rule. Resolve the `voters` values with `users.get` action.
"reviewer": {
field: "reviewerId",
action: "users.get",
params: {
fields: "username fullName"
}
},
// Custom populator handler function
"rate"(ids, items, rule, ctx) {
// items argument is a mutable array containing response items
// the resolved value from promise do not matter - items array will
always be passed as populated response
// so you should du custom populate by mutating items, if confused check
implementaion:
//
https://github.com/moleculerjs/moleculer-db/blob/master/packages/moleculer-db/src/index.js#L636
} | } | |
---|---|---|
}); |
Lifecycle entity events
There are 3 lifecycle entity events which are called when entities are manipulated.
entityUpdated(json, ctx) {
// You can also access to Context
this.logger.info(`Entity updated by '${ctx.meta.user.name}' user!`);
},entityRemoved(json, ctx) {
this.logger.info("Entity removed", json);
},
});module.exports = {
name: "posts",
mixins: [DbService],settings: {
fields: ["_id", "title", "content", "votes"]
},
$ npm install moleculer-db moleculer-db-adapter-mongo --save
Dependencies
// Create a Mongoose service for `post` entities
broker.createService({
name: "posts",
mixins: [DbService],
adapter: new MongoDBAdapter("mongodb://localhost/moleculer-demo"), collection: "posts"
});broker.start()
// Create a new post
.then(() => broker.call("posts.create", {
title: "My first post",
content: "Lorem ipsum...",
votes: 0
}))
Example with connection URI & options
|
||
---|---|---|
Mongoose Adapter
This adapter is based on
const { ServiceBroker } = require("moleculer");
const DbService = require("moleculer-db");
const MongooseAdapter = require("moleculer-db-adapter-mongoose"); const mongoose = require("mongoose");const broker = new ServiceBroker();
pass: process.env.MONGO_PASSWORD
keepAlive: true
SQL adapter (Postgres, MySQL, SQLite & MSSQL) for Moleculer DB service with Sequelize.
$ npm install moleculer-db-adapter-sequelize --save
# For MSSQL
$ npm install tedious --save"use strict";
broker.start()
// Create a new post
.then(() => broker.call("posts.create", {
title: "My first post",
content: "Lorem ipsum...",
votes: 0
}))// Get all posts
.then(() => broker.call("posts.find").then(console.log));
); |
---|
Example with connection options
|
---|
|
---|