0.10.8 (2025-03-24)
Changes
0.10.7 (2023-11-12)
Changes
- add SSE Example with simple chat #329
- fix: secure header assignment to avoid process crash. #330
- avoid stream hanging on error. #337
- fix: request url not logged correctly. #341
- refactor tests. #342
- update index.d.ts #338, #340
0.10.6 (2023-07-15)
Changes
- update qs package to mitigate CVE-2022-24999. #320
- correct types for route middleware functions. #327
- incorrect key for calloptions type. #328
- add support for qs options. #326
0.10.5 (2022-12-17)
Changes
- improving d.ts
- register on close and error event on response. #299
- resolve promise when close event is fired for res. #301
- add default reqTimeout. #312
- improve rate limiter. #317
- add 201 Created to status codes. #297
0.10.4 (2022-01-09)
What's Changed
Other changes
- update dependencies.
- change to @fastify/busboy implementation. #287
0.10.3 (2021-10-17)
Named authenticate & authorize methods #275
You can define custom authentication and authorization methods for every routes. In this case you should set the method name instead of true value.
module.exports = {
mixins: ApiGatewayService,
settings: {
routes: [
{
path: "/aaa",
authentication: "aaaAuthn",
authorization: "aaaAuthz",
aliases: {
"GET hello": "test.hello"
}
},
{
path: "/bbb",
authentication: "bbbAuthn",
authorization: "bbbAuthz",
aliases: {
"GET hello": "test.hello"
}
},
{
path: "/ccc",
authentication: true,
authorization: true,
aliases: {
"GET hello": "test.hello"
}
}
]
},
methods: {
aaaAuthn() {
},
aaaAuthz() {
},
bbbAuthn() {
},
bbbAuthz() {
},
authenticate() {
},
authorize() {
}
}
}
module.exports = {
name: 'api',
mixins: [ApiGateway],
settings: {
rateLimit: rateLimitGlobalConfig,
routes: [
{
path: '/withLocalRateLimit',
rateLimit: rateLimitConfig,
aliases: {
'GET /2': 'test.2'
}
},
{
path: '/withGlobalRateLimit',
aliases: {
'GET /1': 'test.1'
}
}
]
}
}
Other changes
- update dependencies.
- add support for custom cors origin function. #274
- typescript definition file is rewritten. #259
0.10.2 (2021-09-05)
Named routes
Many developers issued that version 0.10 doesn't support multiple routes with the same path. This version fixes it but you should give a unique name for the routes if they have same path.
Example
You can call /api/hi without auth, but /api/hello only with auth.
const ApiGateway = require("moleculer-web");
module.exports = {
mixins: [ApiGateway],
settings: {
path: "/api",
routes: [
{
name: "no-auth-route",
path: "/",
aliases: {
hi: "greeter.hello",
}
},
{
name: "with-auth-route",
path: "/",
aliases: {
"hello": "greeter.hello",
},
authorization: true
}
]
}
};
Changes
- add
removeRouteByName(name: string) method to remove a route by its name.
0.10.1 (2021-09-01)
Changes
- set the default JSON bodyparser if
bodyParser: true. #258
- add
pathToRegexpOptions to route options to make available to pass options to path-to-regexp library. #268
- add
debounceTime to route options to make available to change the debounce time at service changes. #260
- new
errorHandler method to allow developers to change the default error handling behaviour. #266
- fixes CORS preflight request handling in case of full-path aliases. #269
- update dependencies
0.10.0 (2021-06-27)
Breaking changes
Avoid array wrapping at file uploading
Many users issued that at file uploading the response always wrapped into an array even single file uploading. This issues is fixed in this version.
If you define files: 1 in busboy settings, the response won't be wrapped into array.
Example
const ApiGateway = require("moleculer-web");
module.exports = {
mixins: [ApiGateway],
settings: {
path: "/upload",
routes: [
{
path: "/upload",
busboyConfig: {
limits: {
files: 1
}
},
}
]
}
};
JSON body-parser is the new default.
In early version, if you added multiple routes, you should always set JSON body-parser. As of v0.10, it's the new default, if you don't define bodyParsers in the route options. If you don't want to use any body-parsers just set bodyParsers: false.
Example: disable body parsers
const ApiGateway = require("moleculer-web");
module.exports = {
mixins: [ApiGateway],
settings: {
routes: [
{
path: "/api",
bodyParsers: false
}
]
}
};
New features
Actions for adding and removing routes
The addRoute and removeRoute methods exposed to service actions, as well. It means, you can add/remove routes from remote nodes, as well.
Adding a new route
broker.call("api.addRoute", {
route: {
path: "/api",
aliases: {
"hi": "greeter.hello"
}
},
toBottom: true
})
Removing a route
broker.call("api.removeRoute", { path: "/api" });
New logging options
There are two new logging service settings: logRequest, logResponse. You can define the logging level for request & response log messages.
const ApiGateway = require("moleculer-web");
module.exports = {
mixins: [ApiGateway],
settings: {
logRequest: "debug",
logResponse: false,
}
};
New rootCallOptions options
There a new rootCallOptions property in the service settings. Here you can define the root Context calling options. It can be a static Object or a Function. It can be useful to take some data from the req and put them to the calling options (like tracing informations)
Example with static object
const ApiGateway = require("moleculer-web");
module.exports = {
mixins: [ApiGateway],
settings: {
routes: [],
rootCallOptions: {
timeout: 500
}
}
};
Example with Function which takes tracing information from the req and put them to the calling options
const ApiGateway = require("moleculer-web");
module.exports = {
mixins: [ApiGateway],
settings: {
routes: [],
rootCallOptions(options, req, res) {
if (req.headers["traceparent"]) {
const traceparent = req.headers["traceparent"].toLowerCase();
if (traceparent.match(/^([0-9a-f]{2})-([0-9a-f]{32})-([0-9a-f]{16})-([0-9a-f]{2})$/)) {
const [version, id, parentSpan, flags] = traceparent.split("-");
const sampled = (flags & FLAG_SAMPLED) == FLAG_SAMPLED;
options.parentSpan = {
id: parentSpan,
traceID: id,
sampled
};
}
} else {
options.parentSpan = {};
if (req.headers["x-b3-traceid"]) {
options.parentSpan.traceID = req.headers["x-b3-traceid"].toLowerCase();
options.parentSpan.sampled = true;
}
if (req.headers["x-b3-spanid"]) {
options.parentSpan.id = req.headers["x-b3-spanid"].toLowerCase();
}
}
}
}
};
Multiple route aliases
You can define multiple REST aliases in the action definitions.
module.exports = {
name: "posts",
settings: {
rest: ["/posts", "/v1/posts"]
},
actions: {
find: {
rest: ["GET /", "GET /all"]
handler(ctx) {}
}
}
};
Multipart fields & URL Params
Several issues has been fixed with multipart handling.
- URL parameters (e.g.
/api/upload/:tenant/:folder) is available via ctx.meta.$params
- Multipart fields is available via
ctx.meta.$multipart
ctx.params.$params is not available, use ctx.meta.$params
- The target action is called if no uploaded files but has multipart fields. In this case
ctx.params is {} and the fields are in ctx.meta.$multipart.
Other changes
- set response header from
ctx.meta in case of errors, as well.
- update dependencies.
- update index.d.ts.
- remove deprecated
publish: false condition. Use `visibility: "public", instead.
0.9.1 (2020-02-29)
Changes
- remove empty log lines
- add
encodeResponse(req, res, data) method. You can overwrite it in order to use other response encoding instead of JSON.
0.9.0 (2020-02-12)
Breaking changes
Drop Node 6 & 8 support
Due to Node 6 & 8 LTS end of life, the minimum Node version is 10.
Changed mappingPolicy default value
In the previous version the mappingPolicy default value was all which means, you can call
any services via API Gateway which accepted by whitelist. This setting is not too secure.
From this version, the default value is restrict if at least one alias is defined in route options.
If there are not aliases & mappingPolicy defined, the behaviour will be the old one.
Use server property instead of middleware
We have removed the middleware service setting because it was not straightforward. Therefore, we have created a new server setting.
If server: true (which is the default value), API Gateway will create a HTTP(s) server. If server: false, it won't create a HTTP server, so you can use API Gateway as an Express middleware.
Migration guide
Before
const ApiGateway = require("moleculer-web");
module.exports = {
mixins: [ApiGateway],
settings: {
middleware: true
}
}
After
const ApiGateway = require("moleculer-web");
module.exports = {
mixins: [ApiGateway],
settings: {
server: false
}
}
Other low-level breaking changes
sendResponse signature is changed to this.sendResponse(req, res, data)
New
File upload aliases
API Gateway has implemented file uploads. You can upload files as a multipart form data (thanks for busboy library) or as a raw request body. In both cases, the file is transferred to an action as a Stream. In multipart form data mode you can upload multiple files, as well.
Please note, you have to disable other body parsers in order to accept files.
Example
const ApiGateway = require("moleculer-web");
module.exports = {
mixins: [ApiGateway],
settings: {
path: "/upload",
routes: [
{
path: "",
bodyParsers: {
json: false,
urlencoded: false
},
aliases: {
"POST /": "multipart:file.save",
"PUT /": "stream:file.save",
"POST /multi": {
type: "multipart",
busboyConfig: {
limits: {
files: 3
}
},
action: "file.save"
}
},
busboyConfig: {
limits: {
files: 1
}
},
mappingPolicy: "restrict"
}
]
}
});
HTTP2 server
HTTP2 experimental server has been implemented into API Gateway. You can turn it on with http2: true service setting.
Example
const ApiGateway = require("moleculer-web");
module.exports = {
mixins: [ApiGateway],
settings: {
port: 8443,
https: {
key: fs.readFileSync("key.pem"),
cert: fs.readFileSync("cert.pem")
},
http2: true
}
});
Dynamic routing
The this.addRoute(opts, toBottom = true) new service method is added to add/replace routes. You can call it from your mixins to define new routes (e.g. swagger route, graphql route...etc).
The function detects that the route is defined early. In this case, it will replace the previous route configuration with the new one.
To remove a route, use the this.removeRoute("/admin") method. It removes the route by path.
ETag supporting
Thank to tiaod for ETag implementation. PR: #92
Example
const ApiGateway = require("moleculer-web");
module.exports = {
mixins: [ApiGateway],
settings: {
etag: false,
routes: [
{
path: "/",
etag: true
}
]
}
}
The etag option value can be false, true, weak, strong, or a custom Function.
Custom ETag generator function
module.exports = {
mixins: [ApiGateway],
settings: {
etag: (body) => generateHash(body)
}
}
Please note, it doesn't work with stream responses. In this case, you should generate the etag by yourself.
Example
module.exports = {
name: "export",
actions: {
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;
}
}
}
Auto-aliasing feature
The auto-aliasing means you don't have to add all service aliases to the routes, the Gateway can generate it from service schema. If a new service is entered or leaved, Gateway regenerate aliases.
To configure which services are used in route use the whitelist.
Example
module.exports = {
mixins: [ApiGateway],
settings: {
routes: [
{
path: "/api",
whitelist: [
"posts.*",
"test.*"
],
aliases: {
"GET /hi": "test.hello"
},
autoAliases: true
}
]
}
};
module.exports = {
name: "posts",
version: 2,
settings: {
rest: "posts/"
},
actions: {
list: {
rest: "GET /",
handler(ctx) {}
},
get: {
rest: "GET /:id",
handler(ctx) {}
},
create: {
rest: "POST /",
handler(ctx) {}
},
update: {
rest: "PUT /:id",
handler(ctx) {}
},
remove: {
rest: "DELETE /:id",
handler(ctx) {}
}
}
};
The generated aliases
GET /api/hi => test.hello
GET /api/v2/posts => v2.posts.list
GET /api/v2/posts/:id => v2.posts.get
POST /api/v2/posts => v2.posts.create
PUT /api/v2/posts/:id => v2.posts.update
DELETE /api/v2/posts/:id => v2.posts.remove
Example to define full path alias
module.exports = {
name: "posts",
version: 2,
settings: {
rest: "posts/"
},
actions: {
tags: {
rest: {
method: "GET",
fullPath: "/tags"
},
handler(ctx) {}
}
}
};
Changes
- new
optimizeOrder: true setting in order to optimize route & alias paths (deeper first). Default: true.
- new
logging route option to disable request logging. It can be useful for health check routes. Default: true.
- tilde (
~) replace issue fixed. #98
- throw
503 - ServiceUnavailableError when a service defined in aliases but not available. Ref: #27
- new
internalServiceSpecialChar service setting to override special char for internal services (~)
- new
httpServerTimeout setting to overwrite the default HTTP server timeout. #126
- add
reformatError method to change the response error object (remove or add fields, localize error message...etc).
- new
listAliases action to get all registered route aliases.
- remove bluebird dependency & using native Promise & async/await.
0.8.5 (2018-11-28)
Changes
- allow multiple whitespaces between method & path in aliases.
0.8.4 (2018-11-18)
Changes
- fix
req.url, add req.originalUrl and req.baseUrl for better middleware support (e.g. support static serving in subpath).
- update deps
0.8.3 (2018-11-11)
Changes
- use
Promise in started & stopped handlers.
- disable 4xx errors with
log4XXResponses setting.
0.8.2 (2018-10-04)
New authenticate method.
This authenticate method is similar to authorize. You have access to req, res and route objects and you can authenticate the user from the request.
The returned data is saved to the ctx.meta.user. To enable this logic set authentication: true in route options.
Example
module.exports = {
name: "api",
mixins: [ApiGatewayService],
settings: {
routes: [
{
authentication: true
}
]
},
methods: {
authenticate(ctx, route, req, res) {
let accessToken = req.query["access_token"];
if (accessToken) {
if (accessToken === "12345") {
return Promise.resolve({ id: 1, username: "john.doe", name: "John Doe" });
} else {
return Promise.reject();
}
} else {
return Promise.resolve(null);
}
}
}
});
Changes
- update dependencies.
- added
.npmignore
0.8.1 (2018-08-04)
Changes
- fix missing dependency.
- fix middleware array promise-chaining bug
- handle terminated requests in middlewares
- update webpack-vue example to be up-to-date.
0.8.0 (2018-07-08)
Breaking changes
The onAfterCall hook has changed
In previous versions of Moleculer Web, you couldn't manipulate the data in onAfterCall. Now you can, but you must always return the new or original data.
Modify only headers
broker.createService(ApiGatewayService, {
settings: {
routes: [{
onAfterCall(ctx, route, req, res, data) {
res.setHeader("X-Custom-Header", "123456");
return data;
}
}]
}
});
Modify (wrap) the original data
broker.createService(ApiGatewayService, {
settings: {
routes: [{
onAfterCall(ctx, route, req, res, data) {
return {
other: "things",
data: data
};
}
}]
}
});
Custom alias hooks
The onBeforeCall and authorize hooks are called before custom alias functions too.
And you have access to Context as req.$ctx or res.$ctx
Whitelist string matcher changed
In early versions the * match string is enabled to call all services & actions. The matcher changed, in new versions use the ** (double star) match string for the same function.
New
Since Moleculer v0.12, you can use ctx.meta to send back response headers to the Moleculer Web.
The old method is deprecated but works.
Available meta fields:
ctx.meta.$statusCode - set res.statusCode.
ctx.meta.$statusMessage - set res.statusMessage.
ctx.meta.$responseType - set Content-Type in header.
ctx.meta.$responseHeaders - set all keys in header.
ctx.meta.$location - set Location key in header for redirects.
Old method
module.exports = {
name: "export",
actions: {
downloadCSV:
responseType: "text/csv",
responseHeaders: {
"Content-Disposition": "attachment; filename=\"data.csv\"",
},
handler() {
return "...";
}
}
}
}
New method
module.exports = {
name: "export",
actions: {
downloadCSV(ctx) {
ctx.meta.$responseType = "text/csv";
ctx.meta.$responseHeaders = {
"Content-Disposition": `attachment; filename="data-${ctx.params.id}.csv"`
};
return "...";
}
redirectSample(ctx) {
ctx.meta.$statusCode = 302;
ctx.meta.$location = "/test/hello";
}
}
}
Support array & nested objects in query
Thanks for @hwuethrich, Moleculer Web supports arrays & nested objects in querystring.
GET /api/opt-test?a=1&a=2
a: ["1", "2"]
GET /api/opt-test?foo[bar]=a&foo[bar]=b&foo[baz]=c
foo: {
bar: ["a", "b"],
baz: "c"
}
Support error-handler middlewares
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({
mixins: [ApiService],
settings: {
use: [
cookieParser(),
helmet()
],
routes: [
{
path: "/",
use: [
compression(),
passport.initialize(),
passport.session(),
function(err, req, res, next) {
this.logger.error("Error is occured in middlewares!");
this.sendError(req, res, err);
}
],
Changes
preValidate has been removed.
- fix multiple CORS origin handling. Thanks for @felipegcampos
- if
X-Correlation-Id is in the request header, it is used as requestID in Context.
- types in errors have been changed (removed
ERR_ prefix)
path-to-regexp is updated to v2.x.x
0.6.4 (2018-03-04)
Changes
0.6.3 (2018-02-25)
Changes
- fix Bluebird cancellation error
UnhandledPromiseRejectionWarning: Error: cannot enable cancellation after promises are in use#202
-update dependencies
0.6.2 (2018-01-15)
Changes
0.6.1 (2018-01-07)
Changes
- fix CORS
OPTIONS handling. #30
0.6.0 (2018-01-04)
Breaking changes
Alias custom function arguments is changed
The route first argument is removed. The new signature of function is function(req, res) {}. To access to route use the req.$route property.
However you can use an array of Function for aliases. With it you can call middlewares. In this case the third argument is next. I.e.: function(req, res, next) {}.
Other changes
- better error handling. Always returns with JSON error response.
- The charset is
UTF-8 for application/json responses.
logRequestParams setting to log the request parameters. Use log level value i.e. "debug", "info" or null to disable.
logResponseData setting to log the response data. Use log level value i.e. "debug", "info" or null to disable.
req.$service & res.$service is pointed to the service instance.
req.$route & res.$route is pointed to the route definition.
req.$params is pointed to the resolved parameters (from query string & post body)
req.$alias is pointed to the alias definition.
req.$endpoint is pointed to the resolved action endpoint. It contains action and nodeID.
Middlewares
Support middlewares in global, routes & aliases.
broker.createService({
mixins: [ApiService],
settings: {
use: [
cookieParser(),
helmet()
],
routes: [
{
path: "/",
use: [
compression(),
passport.initialize(),
passport.session(),
serveStatic(path.join(__dirname, "public"))
],
aliases: {
"GET /secret": [
auth.isAuthenticated(),
auth.hasRole("admin"),
"top.secret"
]
}
}
]
}
});
It supports custom response headers to define in action definition.
module.exports = {
name: "export",
actions: {
downloadCSV:
responseHeaders: {
"Content-Disposition": "attachment; filename=\"data.csv\"",
"Content-Type": "text/csv"
},
handler() {
return "...";
}
}
}
}
Error handlers
You can add route & global custom error handlers.
broker.createService({
mixins: [ApiService],
settings: {
routes: [{
path: "/api",
onError(req, res, err) {
res.setHeader("Content-Type", "application/json; charset=utf-8");
res.writeHead(500);
res.end(JSON.stringify(err));
}
}],
onError(req, res, err) {
res.setHeader("Content-Type", "text/plain");
res.writeHead(501);
res.end("Global error: " + err.message);
}
}
}
New examples to serve client-side developing with Webpack
0.5.2 (2017-10-24)
New
- add
mappingPolicy route option
0.5.1 (2017-10-07)
New
- add CORS headers
- add Rate limiter
0.5.0 (2017-09-12)
Breaking changes
- compatibility with Moleculer >= v0.11.x
0.4.4 (2017-08-20)
Changes
- update Moleculer to v0.10
0.4.1 (2017-07-24)
New
Prohibited action with publish: false action properties
module.exports = {
name: "test",
actions: {
dangerZone: {
publish: false,
handler(ctx) {
return "You cannot call this action via API Gateway!";
}
}
}
};
Calling options in routes
The route has a callOptions property which is passed to broker.call. So you can set timeout, retryCount or fallbackResponse options for routes.
broker.createService(ApiGatewayService, {
settings: {
routes: [{
callOptions: {
timeout: 1000,
retryCount: 0,
}
}]
}
});
0.4.0 (2017-07-07)
Breaking changes
- in the REST shorthand, the
GET / calls the list action instead of find. The reason is list action in moleculer-db is support pagination
Changes
- changed order of param handling
ctx.params = Object.assign({}, body, query, params).
- moved
onBeforeCall before authorize in request flow. So you can also reach unauthorized requests in onBeforeCall handler.
- the
sendResponse method has new arguments: sendResponse(ctx, route, req, res, data, responseType)
0.3.3 (2017-06-07)
New
Functions in aliases
There is available to use custom function in aliases. In this case you got req & res and you should return with the response. Use it for example file uploads. You can find example in the full example.
Usage
...
aliases: {
"add/:a/:b": "math.add",
"GET sub": "math.sub",
"POST upload"(route, req, res) {
}
}
...
New camelCaseNames route setting
There is a new camelCaseNames option in route setting. If it is true, the service will convert the received action name to camelCase name.
Usage
broker.createService(ApiGatewayService, {
settings: {
routes: [{
camelCaseNames: true
}]
}
});
broker.createService({
name: "test",
actions: {
sayHi(ctx) {
return "Hi!"
}
}
});
broker.start();
In the above example the sayHi action can be called with http://localhost:3000/test/say-hi as well.
0.3.2 (2017-06-02)
New
Exposed error classes
Available errors:
| Class |
Params |
Description |
UnAuthorizedError |
type, data |
Unauthorized HTTP error (401) |
ForbiddenError |
type, data |
Forbidden HTTP error (403) |
BadRequestError |
type, data |
Bad Request HTTP error (400) |
Type contants:
ERR_NO_TOKEN
ERR_INVALID_TOKEN
ERR_UNABLE_DECODE_PARAM
Usage
const { UnAuthorizedError, ERR_NO_TOKEN } = require("moleculer-web").Errors;
...
actions: {
update(ctx) {
if(!ctx.meta.user)
return Promise.reject(new UnAuthorizedError(ERR_NO_TOKEN));
}
}
...
0.3.1 (2017-06-02)
New
RESTful routes
It is possible to use RESTful aliases which routed to CRUD service actions.
Usage
broker.createService(ApiGatewayService, {
settings: {
routes: [{
aliases: {
"REST posts": "posts"
}
}]
}
});
broker.start();
The "REST posts": "posts" will be extracted to these aliases:
"GET posts": "posts.find",
"GET posts/:id": "posts.get",
"POST posts": "posts.create",
"PUT posts/:id": "posts.update",
"DELETE posts/:id": "posts.remove"
Example: examples/rest
0.3.0 (2017-06-01)
New
Named parameters in aliases
It is possible to use named parameters in aliases. Named paramters are defined by prefixing a colon to the parameter name (:name)
Usage
broker.createService(ApiGatewayService, {
settings: {
routes: [{
path: "/api",
aliases: {
"GET greeter/:name": "test.greeter",
"optinal-param/:name?": "test.echo",
"repeat-param/:args*": "test.echo",
"GET /": "test.hello"
}
}]
}
});
broker.start();
Example: examples/full
0.2.2 (2017-06-01)
New
Before & after call hooks
The route of service has onBeforeCall and onAfterCall hooks. It can be asynchronous if return with Promise. In methods the this is pointed to Service instance. So you can access the service methods & broker.
Usage
broker.createService(ApiGatewayService, {
settings: {
routes: [{
onBeforeCall(ctx, route, req, res) {
ctx.meta.userAgent = req.headers["user-agent"];
},
onAfterCall(ctx, route, req, res, data) {
res.setHeader("X-Custom-Header", "123456");
}
}]
}
});
broker.start();
Example: examples/full
0.2.1 (2017-05-23)
New
ExpressJS middleware usage
You can use Moleculer-Web as a middleware for ExpressJS.
Usage
const svc = broker.createService(ApiGatewayService, {
settings: {
middleware: true
}
});
const app = express();
app.use("/api", svc.express());
app.listen(3000);
broker.start();
Example: examples/express
0.2.0 (2017-05-09)
New
Support custom authorization
For more information check the full or authorization examples or readme
0.1.0 (2017-05-08)
First release.