Skip to content

Releases: moleculerjs/moleculer-web

v0.9.1

29 Feb 09:34
Compare
Choose a tag to compare

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.

v0.9.0

12 Feb 09:08
Compare
Choose a tag to compare

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: "",

                // You should disable body parsers
                bodyParsers: {
                    json: false,
                    urlencoded: false
                },

                aliases: {
                    // File upload from HTML multipart form
                    "POST /": "multipart:file.save",
                    
                    // File upload from AJAX or cURL
                    "PUT /": "stream:file.save",

                    // File upload from HTML form and overwrite busboy config
                    "POST /multi": {
                        type: "multipart",
                        // Action level busboy config
                        busboyConfig: {
                            limits: {
                                files: 3
                            }
                        },
                        action: "file.save"
                    }
                },

                // Route level busboy config.
                // More info: https://github.com/mscdex/busboy#busboy-methods
                busboyConfig: {
                    limits: {
                        files: 1
                    }
                    // Can be defined limit event handlers
                    // `onPartsLimit`, `onFilesLimit` or `onFieldsLimit`
                },

                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 server with certificate
        https: {
            key: fs.readFileSync("key.pem"),
            cert: fs.readFileSync("cert.pem")
        },

        // Use HTTP2 server
        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: {
        // Service-level option
        etag: false,

        routes: [
            {
                path: "/",

                // Route-level option.
                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: {
        // Service-level option
        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: {
        // 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;
        }
    }
}

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

// api.service.js
module.exports = {
    mixins: [ApiGateway],

    settings: {
        routes: [
            {
                path: "/api",

                whitelist: [
                    "posts.*",
                    "test.*"
                ],

                aliases: {
                    "GET /hi": "test.hello"
                },

                autoAliases: true
            }
        ]
    }
};
// posts.service.js
module.exports = {
    name: "posts",
    version: 2,

    settings: {
        // Base path
        rest: "posts/"
    },

    actions: {
        list: {
            // Expose as "/api/v2/posts/"
            rest: "GET /",
            handler(ctx) {}
        },

        get: {
            // Expose as "/api/v2/posts/:id"
            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

// posts.service.js
module.exports = {
    name: "posts",
    version: 2,

    settings: {
        // Base path
        rest: "posts/"
    },

    actions: {
        tags: {
            // Expose as "/tags/" instead of "/api/v2/posts/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.

v0.8.5

28 Nov 19:18
Compare
Choose a tag to compare

Changes

  • allow multiple whitespaces between method & path in aliases.

v0.8.4

18 Nov 21:33
Compare
Choose a tag to compare

Changes

  • fix req.url, add req.originalUrl and req.baseUrl for better middleware support (e.g. support static serving in subpath).

v0.8.3

11 Nov 14:08
Compare
Choose a tag to compare

Changes

  • use Promise in started & stopped handlers.
  • disable 4xx errors with log4XXResponses setting.

0.8.2

04 Oct 19:50
Compare
Choose a tag to compare

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: [
            {
                // Enable authentication
                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

v0.8.1

04 Aug 10:48
Compare
Choose a tag to compare

Changes

  • fix missing dependency.
  • fix middleware array promise-chaining bug
  • handle terminated requests in middlewares
  • update webpack-vue example to be up-to-date.

v0.8.0

08 Jul 13:25
Compare
Choose a tag to compare

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");

                // Must return the original `data`
                return data;
            }
        }]
    }
});

Modify (wrap) the original data

broker.createService(ApiGatewayService, {
    settings: {
        routes: [{
            onAfterCall(ctx, route, req, res, data) {
                // Wrap the original data to a new object
                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

New

Response header data from ctx.meta

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: {
        // Download 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 "...";
        }

        // Redirect the request
        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: {
        // Global middlewares. Applied to all routes.
        use: [
            cookieParser(),
            helmet()
        ],

        routes: [
            {
                path: "/",

                // Route-level middlewares.
                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

v0.6.2

15 Jan 13:06
Compare
Choose a tag to compare

Changes

  • turnable pre-validation with preValidate setting. Default is true to backward compatibility.
     broker.createService({
     	mixins: [ApiService],
     	settings: {
     		// Disable pre-validation at action calls
     		preValidate: false
     	}
     })

v0.6.1

15 Jan 13:05
Compare
Choose a tag to compare

Changes

  • fix CORS OPTIONS handling. #30