From db639dd12738e90a39596cdc6989babb3952cf55 Mon Sep 17 00:00:00 2001 From: Pedro Pereira Date: Mon, 16 Jul 2018 15:58:30 +0100 Subject: [PATCH 1/4] feat(middleware): change middleware flow to make it more enrich/error based --- lib/server_middleware/api.js | 32 +++++++++++++++---------- lib/utility/controllers.js | 39 +++++++++++++++++++++++++++---- lib/utility/http_errors.js | 7 ++++++ tests/api.middleware.test.js | 16 +------------ tests/fixtures/test_controller.js | 6 ++--- 5 files changed, 64 insertions(+), 36 deletions(-) diff --git a/lib/server_middleware/api.js b/lib/server_middleware/api.js index b63d075..b813cca 100755 --- a/lib/server_middleware/api.js +++ b/lib/server_middleware/api.js @@ -1,22 +1,22 @@ const express = require('express'); const url = require('url'); const expressBodyParser = require('body-parser'); -const { DEFAULT_PARSER_OPTIONS } = require('../utility/body_parser'); -const { getControllerFiles, getControllerMiddleware, controllerMappingServerSide } = require('../utility/controllers'); +const { DEFAULT_PARSER_OPTIONS } = require('../utility/body_parser'); +const { getControllerFiles, getControllerMiddleware, middlewareHandler, controllerMappingServerSide } = require('../utility/controllers'); /** * Action handler middleware. * * @param ControllerClass - * @param methodName + * @param actionName * @param options * @returns {Function} */ -function actionHandler(ControllerClass, methodName, options) { +function actionHandler(ControllerClass, actionName, options) { return function (req, res, next) { const controller = new ControllerClass(req); - return Promise.resolve(controller[methodName]({params: req.params, body: req.body, query: req.query})) + return Promise.resolve(controller[actionName]({params: req.params, body: req.body, query: req.query})) .then(function (result) { res.result = options.successHandler(result, req); next(); @@ -37,16 +37,20 @@ function createControllerRouter(controllerFile, options) { const ControllerClass = require(controllerFile); const routes = ControllerClass.ROUTES || {}; - Object.keys(routes).forEach(function (methodName) { - if (ControllerClass.prototype[methodName]) { - const {path, verb} = routes[methodName]; + Object.keys(routes).forEach(function (actionName) { + if (ControllerClass.prototype[actionName]) { + const {path, verb} = routes[actionName]; const controllerMiddleware = getControllerMiddleware(ControllerClass); router[verb.toLowerCase()]( path, - ...controllerMiddleware.all, // Controller middleware for all actions - ...(controllerMiddleware.actions && controllerMiddleware.actions[methodName] || []), // Action only middleware - actionHandler(ControllerClass, methodName, options) // Handle controller action method. + function (req, res, next) { + return middlewareHandler(controllerMiddleware.all, req) + .then(() => middlewareHandler(controllerMiddleware.actions && controllerMiddleware.actions[actionName] || [], req)) + .then(() => next()) + .catch(next); + }, + actionHandler(ControllerClass, actionName, options) // Handle controller action method. ); } }); @@ -122,7 +126,11 @@ function injectAPI(options) { // Middleware for all API routes if (options.middleware) { - router.use(...(Array.isArray(options.middleware) ? options.middleware : [options.middleware])); + router.use(function (req, res, next) { + return middlewareHandler(options.middleware, req) + .then(() => next()) + .catch(next); + }); } // Inject Controller routes diff --git a/lib/utility/controllers.js b/lib/utility/controllers.js index 0428d01..5434288 100755 --- a/lib/utility/controllers.js +++ b/lib/utility/controllers.js @@ -94,6 +94,19 @@ function controllerMapping(directory, controllerValue) { return mapping; } +/** + * Middleware handler for express router. + * + * @param middleware + * @returns {Object} + */ +function middlewareHandler(middleware, req) { + middleware = Array.isArray(middleware) ? middleware : middleware && [middleware] || []; + return Promise.all(middleware.map(function (fn) { + return Promise.resolve(fn(req)); + })); +} + /** * Route Controller mapping for client side api plugin. * @@ -118,6 +131,7 @@ function controllerMappingClientSide(directory) { * @returns {Object} */ function controllerMappingServerSide(directory, req, options) { + const apiMiddleware = options.middleware; let ResponseMiddleware = options.responseMiddleware ? require(options.responseMiddleware.replace(options.aliasKey, options.srcDir)) : null; @@ -127,13 +141,27 @@ function controllerMappingServerSide(directory, req, options) { : ResponseMiddleware; return controllerMapping(options.directory, function (ControllerClass, actionName) { + const controllerMiddleware = getControllerMiddleware(ControllerClass); + return function ({params, body, query} = {}) { try { - return Promise.resolve(new ControllerClass(req)[actionName]({ - params: params || {}, - body: body || {}, - query: query || {} - })) + return middlewareHandler(apiMiddleware, req) + .then(function () { + return middlewareHandler(controllerMiddleware.all, req); + }) + .then(function () { + return middlewareHandler( + controllerMiddleware.actions && controllerMiddleware.actions[actionName] || [], + req + ); + }) + .then(function () { + return Promise.resolve(new ControllerClass(req)[actionName]({ + params: params || {}, + body: body || {}, + query: query || {} + })) + }) .then(function (result) { return options.successHandler(result); }) @@ -153,6 +181,7 @@ function controllerMappingServerSide(directory, req, options) { module.exports = { getControllerFiles, getControllerMiddleware, + middlewareHandler, controllerMappingClientSide, controllerMappingServerSide }; diff --git a/lib/utility/http_errors.js b/lib/utility/http_errors.js index 9a4c35d..00ca02c 100755 --- a/lib/utility/http_errors.js +++ b/lib/utility/http_errors.js @@ -26,3 +26,10 @@ global.NotFoundError = class extends Error { this.statusCode = 404; } }; + +global.InternalServerError = class extends Error { + constructor(message = 'Internal Server Error') { + super(message); + this.statusCode = 500; + } +}; diff --git a/tests/api.middleware.test.js b/tests/api.middleware.test.js index 07305ff..e546289 100644 --- a/tests/api.middleware.test.js +++ b/tests/api.middleware.test.js @@ -4,30 +4,16 @@ test.before(globalBeforeAll({ moduleOptions: { prefix: '/api/v2', middleware: [ - function (req, res, next) { + function (req) { req.locals = { test: true }; - - next(); - }, - function (req, res, next) { - if (req.query.middleware_response) { - return res.status(200).json(req.locals); - } - - next(); } ] } })); test.after(globalAfterAll()); -test('Test middleware for all api', async (t) => { - const {data} = await api.get('/users?middleware_response=true'); - t.true(data.test); -}); - test('Test middleware for a controller', async (t) => { const getProducts = await api.get('/products'); const getProduct = await api.get('/products/123123'); diff --git a/tests/fixtures/test_controller.js b/tests/fixtures/test_controller.js index 7595885..1f87a0f 100755 --- a/tests/fixtures/test_controller.js +++ b/tests/fixtures/test_controller.js @@ -93,17 +93,15 @@ TestController.ROUTES = { }; TestController.MIDDLEWARE = [ - function (req, res, next) { + function (req) { if (!req.locals) req.locals = {}; req.locals.controller_middleware = true; - next(); }, - ['allAction', function (req, res, next) { + ['allAction', function (req) { if (!req.locals) req.locals = {}; req.locals.action_middleware = true; - next(); }] ]; From dbb5fc389d474c11fd238c653aeabb7ff2999c11 Mon Sep 17 00:00:00 2001 From: Pedro Pereira Date: Mon, 16 Jul 2018 15:59:01 +0100 Subject: [PATCH 2/4] docs(middleware): change docs to support new middleware flow --- docs/basic-usage.md | 4 +--- docs/error-handling.md | 4 ++++ docs/middleware.md | 27 +++++++++++++-------------- 3 files changed, 18 insertions(+), 17 deletions(-) diff --git a/docs/basic-usage.md b/docs/basic-usage.md index 84d38c5..3af71fd 100644 --- a/docs/basic-usage.md +++ b/docs/basic-usage.md @@ -147,9 +147,7 @@ properties for server-side fetching on vue.js pages, we can simply do this: - ```$api``` is injected into all Vue instances (including root), since ```asyncData``` doesn't have ```this``` property. - If ```asyncData``` is called on server-side, it will go directly to your controller action code. If its called on -client-side it uses your ```clientSideApiHandler``` to handle the api request. However, if ```asyncData``` is called on server-side, -api, controller and action middleware will not be triggered, because server-side ```$api``` calls are private and programmatically, -so your code knows what it's doing (or not :D). +client-side it uses your ```clientSideApiHandler``` to handle the api request. - When calling an ```action``` (e.g: ```todos.allActions```), it accepts an object as param: ```json { diff --git a/docs/error-handling.md b/docs/error-handling.md index ff94645..24e7403 100644 --- a/docs/error-handling.md +++ b/docs/error-handling.md @@ -64,3 +64,7 @@ new ForbiddenError(message = 'No access to this area') ```js new NotFoundError(message = 'The resource was not found') ``` +- InternalServerError (status = 500) +```js +new InternalServerError(message = 'An internal error has occurred.') +``` diff --git a/docs/middleware.md b/docs/middleware.md index 5644293..d7237b8 100644 --- a/docs/middleware.md +++ b/docs/middleware.md @@ -8,16 +8,13 @@ It's possible to add middleware into 3 parts of your API: ['nuxt-neo', { // ... middleware: [ - function (req, res, next) { + function (req) { console.log('first'); - next() }, - function (req, res, next) { + function (req) { if (req.query.fail === 'true') { - return res.status(500).send(); + throw new Error("force fail"); } - - next(); } ] // ... @@ -40,9 +37,8 @@ TodoController.ROUTES = { }; TodoController.MIDDLEWARE = [ - function (req, res, next) { - console.log('second'); - next(); + function (req) { + console.log('first'); } ] ``` @@ -60,18 +56,21 @@ TodoController.ROUTES = { TodoController.MIDDLEWARE = [ ['allAction', - function (req, res, next) { + function (req) { console.log('second'); - next(); }, - function (req, res, next) { - console.log('third'); - next(); + function (req) { + // supports promises + return Promise.resolve().then(() => console.log('third')); } ] ] ``` +*NOTE:* The middleware is mostly used to block access and enrich request object with data. + You only have access to ```request``` object and can only break the continuation to the controller action + throwing errors (e.g : ```throw new InternalServer("I dont want to continue")```); + ## Response Middleware ## You will mostly get to the point to make your api payload data more structured and abstracted. (for example: inject payload data into somekind of collection). From c5ca0e4a96b6c23ad85edcd3182c8184d086bb7d Mon Sep 17 00:00:00 2001 From: Pedro Pereira Date: Mon, 16 Jul 2018 15:59:27 +0100 Subject: [PATCH 3/4] chore: add new script for major releases --- package.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index c84b75a..d324c46 100755 --- a/package.json +++ b/package.json @@ -33,7 +33,8 @@ "test": "ava", "lint": "eslint --ext .js lib", "release:patch": "sure && standard-version --no-verify --release-as patch && git push --tags", - "release:minor": "sure && standard-version --no-verify --release-as minor && git push --tags" + "release:minor": "sure && standard-version --no-verify --release-as minor && git push --tags", + "release:major": "sure && standard-version --no-verify --release-as major && git push --tags" }, "pre-commit": [ "lint" From aae0f784439fa5997fe4d608a5f6c4bd61114c00 Mon Sep 17 00:00:00 2001 From: Pedro Pereira Date: Mon, 16 Jul 2018 15:59:39 +0100 Subject: [PATCH 4/4] chore(release): 2.0.0 --- CHANGELOG.md | 10 ++++++++++ package-lock.json | 2 +- package.json | 2 +- 3 files changed, 12 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ed78601..648b3e3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,16 @@ All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. + +# [2.0.0](https://github.com/ezypeeze/nuxt-neo/compare/v1.3.0...v2.0.0) (2018-07-16) + + +### Features + +* **middleware:** change middleware flow to make it more enrich/error based ([db639dd](https://github.com/ezypeeze/nuxt-neo/commit/db639dd)) + + + # [1.3.0](https://github.com/ezypeeze/nuxt-neo/compare/v1.2.1...v1.3.0) (2018-07-03) diff --git a/package-lock.json b/package-lock.json index ab22b2b..1fbb15d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "nuxt-neo", - "version": "1.3.0", + "version": "2.0.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index d324c46..a68def5 100755 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "nuxt-neo", - "version": "1.3.0", + "version": "2.0.0", "description": "A nuxt.js module that implements a universal api layer, same-way compatible between server and client side.", "keywords": [ "nuxt",