moleculer-web 
The moleculer-web is the official API gateway service for Moleculer framework. Use it to publish your services as RESTful APIs.
Features
- support HTTP & HTTPS
- serve static files
- multiple routes
- support Connect-like middlewares in global-level, route-level and alias-level.
- alias names (with named parameters & REST routes)
- whitelist
- multiple body parsers (json, urlencoded)
- CORS headers
- Rate limiter
- before & after call hooks
- Buffer & Stream handling
- middleware mode (use as a middleware with Express)
Try it in your browser!
Install
npm i moleculer-web |
Usage
Run with default settings
This example uses API Gateway service with default settings.
You can access all services (including internal $node.) via http://localhost:3000/
const { ServiceBroker } = require("moleculer"); |
Example URLs:
- Call
test.helloaction:http://localhost:3000/test/hello Call
math.addaction with params:http://localhost:3000/math/add?a=25&b=13Get health info of node:
http://localhost:3000/~node/health- List all actions:
http://localhost:3000/~node/actions
Whitelist
If you don’t want to publish all actions, you can filter them with whitelist option.
Use match strings or regexp in list. To enable all actions, use "**" item.
broker.createService({ |
Aliases
You can use alias names instead of action names. You can also specify the method. Otherwise it will handle every method types.
Using named parameters in aliases is possible. Named parameters are defined by prefixing a colon to the parameter name (:name).
broker.createService({ |
The named parameter is handled with path-to-regexp module. Therefore you can use optional and repeated parameters, as well.
You can also create RESTful APIs.broker.createService({
mixins: [ApiService],
settings: {
routes: [{
aliases: {
"GET users": "users.list",
"GET users/:id": "users.get",
"POST users": "users.create",
"PUT users/:id": "users.update",
"DELETE users/:id": "users.remove"
}
}]
}
});
For REST routes you can also use this simple shorthand alias:broker.createService({
mixins: [ApiService],
settings: {
routes: [{
aliases: {
"REST users": "users"
}
}]
}
});
To use this shorthand alias, create a service which has
list,get,create,updateandremoveactions.
You can make use of custom functions within the declaration of aliases. In this case, the handler’s signature is function (req, res) {...}.broker.createService({
mixins: [ApiService],
settings: {
routes: [{
aliases: {
"POST upload"(req, res) {
this.parseUploadedFile(req, res);
},
"GET custom"(req, res) {
res.end('hello from custom handler')
}
}
}]
}
});
There are some internal pointer in
req&resobjects:
req.$ctxare pointed to request context.req.$service&res.$serviceare pointed to this service instance.req.$route&res.$routeare pointed to the resolved route definition.req.$paramsis pointed to the resolved parameters (from query string & post body)req.$aliasis pointed to the resolved alias definition.req.$actionis pointed to the resolved action.req.$endpointis pointed to the resolved action endpoint.req.$nextis pointed to thenext()handler if the request comes from ExpressJS.E.g.: To access the broker, use
req.$service.broker.
Mapping policy
The route has a mappingPolicy property to handle routes without aliases.
Available options:
all- enable to request all routes with or without aliases (default)restrict- enable to request only the routes with aliases.
broker.createService({ |
You can’t request the /math.add or /math/add URLs, only POST /add.
Parameters
API gateway collects parameters from URL querystring, request params & request body and merges them. The results is placed to the req.$params.
Disable merging
To disable parameter merging set mergeParams: false in route settings. In this case the parameters is separated.
Examplebroker.createService({
mixins: [ApiService],
settings: {
routes: [{
path: "/",
mergeParams: false
}]
}
});
Un-merged req.$params:{
// Querystring params
query: {
category: "general",
}
// Request body content
body: {
title: "Hello",
content: "...",
createdAt: 1530796920203
},
// Request params
params: {
id: 5
}
}
Query string parameters
More information: https://github.com/ljharb/qs
Array parameters
URL: GET /api/opt-test?a=1&a=2a: ["1", "2"]
Nested objects & arrays
URL: GET /api/opt-test?foo[bar]=a&foo[bar]=b&foo[baz]=cfoo: {
bar: ["a", "b"],
baz: "c"
}
Middlewares
It supports Connect-like middlewares in global-level, route-level & alias-level. Signature: function(req, res, next) {...}.
Examplebroker.createService({
mixins: [ApiService],
settings: {
// Global middlewares. Applied to all routes.
use: [
cookieParser(),
helmet()
],
routes: [
{
path: "/",
// Route-level middlewares.
use: [
compression(),
passport.initialize(),
passport.session(),
serveStatic(path.join(__dirname, "public"))
],
aliases: {
"GET /secret": [
// Alias-level middlewares.
auth.isAuthenticated(),
auth.hasRole("admin"),
"top.secret" // Call the `top.secret` action
]
}
}
]
}
});
Error-handler middleware
There is support to use error-handler middlewares in the API Gateway. So if you pass an Error to the next(err) function, it will call error handler middlewares which have signature as (err, req, res, next).
broker.createService({ |
Serve static files
It serves assets with the serve-static module like ExpressJS.
broker.createService({ |
Calling options
The route has a callOptions property which is passed to broker.call. So you can set timeout, retries or fallbackResponse options for routes. Read more about calling options
broker.createService({ |
Multiple routes
You can create multiple routes with different prefix, whitelist, alias, calling options & authorization.
When using multiple routes you should explicitly set the body parser(s) for each route.
broker.createService({ |
Response type & status code
When the response is received from an action handler, the API gateway detects the type of response and set the Content-Type in the res headers. The status code is 200 by default. Of course you can overwrite these values, moreover, you can define custom response headers, too.
To define response headers & status code use ctx.meta fields:
Available meta fields:
ctx.meta.$statusCode- setres.statusCode.ctx.meta.$statusMessage- setres.statusMessage.ctx.meta.$responseType- setContent-Typein header.ctx.meta.$responseHeaders- set all keys in header.ctx.meta.$location- setLocationkey in header for redirects.
Examplemodule.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"`
};
return csvFileStream;
}
// Redirect the request
redirectSample(ctx) {
ctx.meta.$statusCode = 302;
ctx.meta.$location = "/login";
return;
}
}
}
Authorization
You can implement authorization. Do 2 things to enable it.
- Set
authorization: truein your routes - Define the
authorizemethod in service.
Example authorizationconst E = require("moleculer-web").Errors;
broker.createService({
mixins: [ApiService],
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")) {
let token = auth.slice(7);
// Check the token
if (token == "123456") {
// Set the authorized user entity to `ctx.meta`
ctx.meta.user = { id: 1, name: "John Doe" };
return Promise.resolve(ctx);
} else {
// Invalid token
return Promise.reject(new E.UnAuthorizedError(E.ERR_INVALID_TOKEN));
}
} else {
// No token
return Promise.reject(new E.UnAuthorizedError(E.ERR_NO_TOKEN));
}
}
}
}
You can find a more detailed role-based JWT authorization example in full example.
Authentication
To enable the support for authentication, you need to do something similar to what is describe in the Authorization paragraph. Also in this case you have to:
- Set
authentication: truein your routes - Define your custom
authenticatemethod in your service
Example authenticationbroker.createService({
mixins: ApiGatewayService,
settings: {
routes: [{
// Enable authentication
authentication: true
}]
},
methods: {
authenticate(ctx, route, req, res) {
let accessToken = req.query["access_token"];
if (accessToken) {
if (accessToken === "12345") {
// valid credentials
return Promise.resolve({ id: 1, username: "john.doe", name: "John Doe" });
} else {
// invalid credentials
return Promise.reject();
}
} else {
// anonymous user
return Promise.resolve(null);
}
}
}
});
Route hooks
The route has before & after call hooks. You can use it to set ctx.meta, access req.headers or modify the response data.
broker.createService({ |
In previous versions of Moleculer Web, you couldn’t manipulate the
datainonAfterCall. Now you can, but you must always return the new or originaldata.
Error handlers
You can add route-level & global-level custom error handlers.
In handlers, you must call the
res.end. Otherwise, the request is unhandled.
broker.createService({ |
CORS headers
You can use CORS headers in Moleculer-Web service.
Usageconst svc = broker.createService({
mixins: [ApiService],
settings: {
// Global CORS settings for all routes
cors: {
// Configures the Access-Control-Allow-Origin CORS header.
origin: "*",
// Configures the Access-Control-Allow-Methods CORS header.
methods: ["GET", "OPTIONS", "POST", "PUT", "DELETE"],
// Configures the Access-Control-Allow-Headers CORS header.
allowedHeaders: [],
// Configures the Access-Control-Expose-Headers CORS header.
exposedHeaders: [],
// Configures the Access-Control-Allow-Credentials CORS header.
credentials: false,
// Configures the Access-Control-Max-Age CORS header.
maxAge: 3600
},
routes: [{
path: "/api",
// Route CORS settings (overwrite global settings)
cors: {
origin: ["http://localhost:3000", "https://localhost:4000"],
methods: ["GET", "OPTIONS", "POST"],
credentials: true
},
}]
}
});
Rate limiter
The Moleculer-Web has a built-in rate limiter with a memory store.
Usageconst svc = broker.createService({
mixins: [ApiService],
settings: {
rateLimit: {
// How long to keep record of requests in memory (in milliseconds).
// Defaults to 60000 (1 min)
window: 60 * 1000,
// Max number of requests during window. Defaults to 30
limit: 30,
// Set rate limit headers to response. Defaults to false
headers: true,
// Function used to generate keys. Defaults to:
key: (req) => {
return req.headers["x-forwarded-for"] ||
req.connection.remoteAddress ||
req.socket.remoteAddress ||
req.connection.socket.remoteAddress;
},
//StoreFactory: CustomStore
}
}
});
Custom Store example
class CustomStore { |
ExpressJS middleware usage
You can use Moleculer-Web as a middleware in an ExpressJS application.
Usageconst svc = broker.createService({
mixins: [ApiService],
settings: {
middleware: true
}
});
// Create Express application
const app = express();
// Use ApiGateway as middleware
app.use("/api", svc.express());
// Listening
app.listen(3000);
// Start server
broker.start();
Full service settings
List of all settings of Moleculer Web service:
settings: { |
Examples
-
- simple gateway with default settings.
-
- open HTTPS server
- whitelist handling
-
- serve static files from the
assetsfolder - whitelist
- aliases
- multiple body-parsers
- serve static files from the
-
- simple authorization demo
- set the authorized user to
Context.meta
-
- simple server with RESTful aliases
- example
postsservice with CRUD actions
-
- webserver with Express
- use moleculer-web as a middleware
-
- start socket.io websocket server
- call action and send back the response via websocket
- send Moleculer events to the browser via websocket
-
- SSL
- static files
- middlewares
- multiple routes with different roles
- role-based authorization with JWT
- whitelist
- aliases with named params
- multiple body-parsers
- before & after hooks
- metrics, statistics & validation from Moleculer
- custom error handlers
-
- Webpack development environment for client-side developing
- webpack config file
- compression
- static file serving
-
- Webpack+Vue development environment for VueJS client developing
- webpack config file
- Hot-replacement
- Babel, SASS, SCSS, Vue SFC