From 26e52584b351b0bd309c21c34fbbcc61099f7800 Mon Sep 17 00:00:00 2001 From: Pedro Pereira Date: Wed, 6 Jun 2018 14:05:06 +0100 Subject: [PATCH 1/4] release: v1.1.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 0eb77d4..a8107cf 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. + +# [1.1.0](https://github.com/ezypeeze/nuxt-neo/compare/v1.0.2...v1.1.0) (2018-06-06) + + +### Features + +* **responseMiddleware:** add responseMiddleware (both client and server side) to perform actions to payload, uniformly ([b5083b5](https://github.com/ezypeeze/nuxt-neo/commit/b5083b5)) + + + ## [1.0.2](https://github.com/ezypeeze/nuxt-neo/compare/v1.0.1...v1.0.2) (2018-06-04) ### Bug Fixes diff --git a/package-lock.json b/package-lock.json index feea467..ccf8814 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "nuxt-neo", - "version": "1.0.2", + "version": "1.1.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index a215fa0..1aaf7f8 100755 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "nuxt-neo", - "version": "1.0.2", + "version": "1.1.0", "description": "A nuxt.js module that implements a universal api layer, same-way compatible between server and client side.", "keywords": [ "nuxt", From ea84fd7d51ded6b4825b0b7583baad4596f26a8f Mon Sep 17 00:00:00 2001 From: Pedro Pereira Date: Wed, 6 Jun 2018 14:19:09 +0100 Subject: [PATCH 2/4] docs: more docs for responseMiddleware feature. --- docs/middleware.md | 80 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 80 insertions(+) diff --git a/docs/middleware.md b/docs/middleware.md index e718c99..5644293 100644 --- a/docs/middleware.md +++ b/docs/middleware.md @@ -70,4 +70,84 @@ TodoController.MIDDLEWARE = [ } ] ] +``` + +## 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). + +To do this, define the response middleware file into module config: +```js +// nuxt.config.js +{ + modules: [ + ['nuxt-neo', { + // ... + responseMiddleware: '~/response_middleware' + // ... + } + ] +} +``` + +Then create that file: +```js +// ~/response_middleware.js + +class Collection() { + + constructor(payload, meta) { + this.payload = payload; + this.meta = meta; + } + + getTotal() { + return this.meta.total; + } + + getPayload() { + return this.payload; + } +} + +// The result is the response payload, that you define on successHandler option. +// Imagine you have this response json structure: + +/** +* { +* meta: { total: 100, offset: 0, limit: 25 }, +* payload: [ {...}, {...}, {...} } +* } +*/ + +export default function(result) { + return new Collection(result.payload, result.meta); +} +``` + +Then on your Vue Page: +```vue + + + ``` \ No newline at end of file From e9cb27372982a76e401db8161a50aa8fa0587d6b Mon Sep 17 00:00:00 2001 From: Pedro Pereira Date: Tue, 12 Jun 2018 13:09:59 +0100 Subject: [PATCH 3/4] feat(bodyParsers): add body parsers middleware option (using express/body-parsers lib or custom handler). #1 --- lib/module.js | 1 + lib/server_middleware/api.js | 35 +++++++++++++++++- lib/utility/body_parser.js | 9 +++++ package-lock.json | 69 +++++++++++++++++++---------------- package.json | 2 + tests/api.body.parser.test.js | 44 ++++++++++++++++++++++ yarn.lock | 30 +++++++++++++-- 7 files changed, 154 insertions(+), 36 deletions(-) create mode 100644 lib/utility/body_parser.js create mode 100644 tests/api.body.parser.test.js diff --git a/lib/module.js b/lib/module.js index 06c244b..b258fb3 100755 --- a/lib/module.js +++ b/lib/module.js @@ -4,6 +4,7 @@ const _ = require('lodash/fp/object'); const DEFAULT_MODULE_OPTIONS = { directory: path.resolve(__dirname, '/api'), prefix: '/api', + bodyParsers: 'json', httpErrors: true, debug: process.env.NODE_ENV !== 'production', noContentStatusOnEmpty: true, diff --git a/lib/server_middleware/api.js b/lib/server_middleware/api.js index 062cc4b..b63d075 100755 --- a/lib/server_middleware/api.js +++ b/lib/server_middleware/api.js @@ -1,5 +1,7 @@ -const express = require('express'); -const url = require('url'); +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'); /** @@ -52,6 +54,32 @@ function createControllerRouter(controllerFile, options) { return router; } +/** + * Injects body parsers to the API. + * + * @param options + */ +function injectBodyParsers(options) { + const parsers = []; + if (options.bodyParsers) { + (Array.isArray(options.bodyParsers) ? options.bodyParsers : [options.bodyParsers]).forEach(function (parser) { + if (typeof parser === 'string' && expressBodyParser.hasOwnProperty(parser)) { + parsers.push(expressBodyParser[parser](DEFAULT_PARSER_OPTIONS[parser] || {})); + } else if (typeof parser === 'object' && parser.adapter && expressBodyParser.hasOwnProperty(parser.adapter)) { + parsers.push(expressBodyParser[parser.adapter]( + parser.options && Object.assign({}, DEFAULT_PARSER_OPTIONS[parser.adapter] || {}, parser.options) || + DEFAULT_PARSER_OPTIONS[parser.adapter] || + {} + )); + } else if (typeof parser === 'function') { + parsers.push(parser(options)); + } + }); + } + + return parsers; +} + /** * Inject controllers and api routes. * @@ -89,6 +117,9 @@ function injectAPI(options) { next(); }); + // Request body parsers + router.use(...(injectBodyParsers(options))); + // Middleware for all API routes if (options.middleware) { router.use(...(Array.isArray(options.middleware) ? options.middleware : [options.middleware])); diff --git a/lib/utility/body_parser.js b/lib/utility/body_parser.js new file mode 100644 index 0000000..815bbea --- /dev/null +++ b/lib/utility/body_parser.js @@ -0,0 +1,9 @@ +const DEFAULT_PARSER_OPTIONS = { + urlencoded: { + extended: true // URL-encoded using qs library, which allows rich objects and arrays. (required option) + } +}; + +module.exports = { + DEFAULT_PARSER_OPTIONS +}; \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index ccf8814..626c25d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2090,20 +2090,35 @@ "dev": true }, "body-parser": { - "version": "1.18.2", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.18.2.tgz", - "integrity": "sha1-h2eKGdhLR9hZuDGZvVm84iKxBFQ=", + "version": "1.18.3", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.18.3.tgz", + "integrity": "sha1-WykhmP/dVTs6DyDe0FkrlWlVyLQ=", "requires": { "bytes": "3.0.0", "content-type": "1.0.4", "debug": "2.6.9", "depd": "1.1.2", "http-errors": "1.6.3", - "iconv-lite": "0.4.19", + "iconv-lite": "0.4.23", "on-finished": "2.3.0", - "qs": "6.5.1", - "raw-body": "2.3.2", + "qs": "6.5.2", + "raw-body": "2.3.3", "type-is": "1.6.16" + }, + "dependencies": { + "iconv-lite": { + "version": "0.4.23", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.23.tgz", + "integrity": "sha512-neyTUVFtahjf0mB3dZT77u+8O0QB89jFdnBkd5P1JgYPbPaia3gXXOVL2fq8VyU2gMMD7SaN7QukTB/pmXYvDA==", + "requires": { + "safer-buffer": "2.1.2" + } + }, + "qs": { + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", + "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==" + } } }, "boolbase": { @@ -5543,7 +5558,6 @@ "requires": { "accepts": "1.3.5", "array-flatten": "1.1.1", - "body-parser": "1.18.2", "content-disposition": "0.5.2", "content-type": "1.0.4", "cookie": "0.3.1", @@ -7901,7 +7915,8 @@ "iconv-lite": { "version": "0.4.19", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.19.tgz", - "integrity": "sha512-oTZqweIP51xaGPI4uPa56/Pri/480R+mo7SeU+YETByQNhDG55ycFyNLIgta9vXhILrxXDmF7ZGhqZIcuN0gJQ==" + "integrity": "sha512-oTZqweIP51xaGPI4uPa56/Pri/480R+mo7SeU+YETByQNhDG55ycFyNLIgta9vXhILrxXDmF7ZGhqZIcuN0gJQ==", + "dev": true }, "icss-replace-symbols": { "version": "1.1.0", @@ -14237,36 +14252,23 @@ "integrity": "sha1-9JvmtIeJTdxA3MlKMi9hEJLgDV4=" }, "raw-body": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.3.2.tgz", - "integrity": "sha1-vNYMd9Prk83gBQKVw/N5OJvIj4k=", + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.3.3.tgz", + "integrity": "sha512-9esiElv1BrZoI3rCDuOuKCBRbuApGGaDPQfjSflGxdy4oyzqghxu6klEkkVIvBje+FF0BX9coEv8KqW6X/7njw==", "requires": { "bytes": "3.0.0", - "http-errors": "1.6.2", - "iconv-lite": "0.4.19", + "http-errors": "1.6.3", + "iconv-lite": "0.4.23", "unpipe": "1.0.0" }, "dependencies": { - "depd": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.1.tgz", - "integrity": "sha1-V4O04cRZ8G+lyif5kfPQbnoxA1k=" - }, - "http-errors": { - "version": "1.6.2", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.2.tgz", - "integrity": "sha1-CgAsyFcHGSp+eUbO7cERVfYOxzY=", + "iconv-lite": { + "version": "0.4.23", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.23.tgz", + "integrity": "sha512-neyTUVFtahjf0mB3dZT77u+8O0QB89jFdnBkd5P1JgYPbPaia3gXXOVL2fq8VyU2gMMD7SaN7QukTB/pmXYvDA==", "requires": { - "depd": "1.1.1", - "inherits": "2.0.3", - "setprototypeof": "1.0.3", - "statuses": "1.4.0" + "safer-buffer": "2.1.2" } - }, - "setprototypeof": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.0.3.tgz", - "integrity": "sha1-ZlZ+NwQ+608E2RvWWMDL77VbjgQ=" } } }, @@ -14880,6 +14882,11 @@ "ret": "0.1.15" } }, + "safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, "sax": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", diff --git a/package.json b/package.json index 1aaf7f8..f80b577 100755 --- a/package.json +++ b/package.json @@ -46,6 +46,7 @@ ], "license": "MIT", "dependencies": { + "body-parser": "^1.18.3", "config": "^1.30.0", "express": "^4.16.3", "lodash": "^4.17.10" @@ -72,6 +73,7 @@ "nuxt": "^1.4.0", "pre-commit": "^1.2.2", "prettier": "^1.12.1", + "querystring": "^0.2.0", "require-extension-hooks": "^0.3.2", "require-extension-hooks-babel": "^0.1.1", "require-extension-hooks-vue": "^1.0.0", diff --git a/tests/api.body.parser.test.js b/tests/api.body.parser.test.js new file mode 100644 index 0000000..cde3ab8 --- /dev/null +++ b/tests/api.body.parser.test.js @@ -0,0 +1,44 @@ +import test from 'ava'; +import qs from 'querystring'; + +test.before(globalBeforeAll({ + moduleOptions: { + bodyParsers: [ + 'json', + { + adapter: 'urlencoded' + } + ] + } +})); +test.after(globalAfterAll()); + +test('Test json body parser', async (t) => { + const {data} = await api.post('/users', { + first_name: 'nuxt', + last_name: 'neo' + }); + + t.true(data.ok); + t.is(data.body.first_name, 'nuxt'); + t.is(data.body.last_name, 'neo'); +}); + +test('Test uri encoded body parser', async (t) => { + const {data} = await api.post( + '/users', + qs.stringify({ + first_name: 'nuxt', + last_name: 'neo' + }), + { + headers: { + 'Content-Type': 'application/x-www-form-urlencoded' + } + } + ); + + t.true(data.ok); + t.is(data.body.first_name, 'nuxt'); + t.is(data.body.last_name, 'neo'); +}); \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index f02bf3f..8e89cff 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1437,6 +1437,21 @@ body-parser@1.18.2: raw-body "2.3.2" type-is "~1.6.15" +body-parser@^1.18.3: + version "1.18.3" + resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.18.3.tgz#5b292198ffdd553b3a0f20ded0592b956955c8b4" + dependencies: + bytes "3.0.0" + content-type "~1.0.4" + debug "2.6.9" + depd "~1.1.2" + http-errors "~1.6.3" + iconv-lite "0.4.23" + on-finished "~2.3.0" + qs "6.5.2" + raw-body "2.3.3" + type-is "~1.6.16" + boolbase@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/boolbase/-/boolbase-1.0.0.tgz#68dff5fbe60c51eb37725ea9e3ed310dcc1e776e" @@ -4297,7 +4312,7 @@ http-errors@1.6.2: setprototypeof "1.0.3" statuses ">= 1.3.1 < 2" -http-errors@^1.2.8, http-errors@^1.6.1, http-errors@~1.6.1, http-errors@~1.6.2: +http-errors@1.6.3, http-errors@^1.2.8, http-errors@^1.6.1, http-errors@~1.6.1, http-errors@~1.6.2, http-errors@~1.6.3: version "1.6.3" resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.6.3.tgz#8b55680bb4be283a0b5bf4ea2e38580be1d9320d" dependencies: @@ -4341,7 +4356,7 @@ iconv-lite@0.4.19: version "0.4.19" resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.19.tgz#f7468f60135f5e5dad3399c0a81be9a1603a082b" -iconv-lite@^0.4.17, iconv-lite@^0.4.4, iconv-lite@~0.4.13: +iconv-lite@0.4.23, iconv-lite@^0.4.17, iconv-lite@^0.4.4, iconv-lite@~0.4.13: version "0.4.23" resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.23.tgz#297871f63be507adcfbfca715d0cd0eed84e9a63" dependencies: @@ -7258,7 +7273,7 @@ qs@6.5.1: version "6.5.1" resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.1.tgz#349cdf6eef89ec45c12d7d5eb3fc0c870343a6d8" -qs@~6.5.1: +qs@6.5.2, qs@~6.5.1: version "6.5.2" resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.2.tgz#cb3ae806e8740444584ef154ce8ee98d403f3e36" @@ -7315,6 +7330,15 @@ raw-body@2.3.2: iconv-lite "0.4.19" unpipe "1.0.0" +raw-body@2.3.3: + version "2.3.3" + resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.3.3.tgz#1b324ece6b5706e153855bc1148c65bb7f6ea0c3" + dependencies: + bytes "3.0.0" + http-errors "1.6.3" + iconv-lite "0.4.23" + unpipe "1.0.0" + rc@^1.0.1, rc@^1.1.6, rc@^1.1.7: version "1.2.7" resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.7.tgz#8a10ca30d588d00464360372b890d06dacd02297" From f8d57393e8719cfb34abb36b6836d86d8f3b4dde Mon Sep 17 00:00:00 2001 From: Pedro Pereira Date: Tue, 12 Jun 2018 13:10:29 +0100 Subject: [PATCH 4/4] docs(bodyParser): add documentation for new module option: bodyParsers. --- docs/configuration.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/docs/configuration.md b/docs/configuration.md index 606ba7d..dd3e236 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -9,6 +9,11 @@ The default options are: // api prefix path prefix: '/api', + // Request body parser (using expressjs/body-parser: https://github.com/expressjs/body-parser). + // String | Array | Object<{adapter: String, options: Object}> | + // Array> + bodyParsers: 'json', + // globalize http error classes (BadRequestError, NotFoundError, ...) httpErrors: true,