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.