diff --git a/.gitignore b/.gitignore index 38c350585..986b5dd13 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,11 @@ **/*.log +__tests__/**/db-*.json node_modules tmp lib .DS_Store .idea db.json + +.history +*.tgz \ No newline at end of file diff --git a/README.md b/README.md index 54e763839..8fd358e9a 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,59 @@ +This code is modified based on [typicode/json-server](https://github.com/typicode/json-server). Used to change some features, if these features are liked, they will apply to typicode for a merge. + +### Install + +``` sh +npm i @wll8/json-server +# or npm i -g @wll8/json-server +``` + +### jsonServer.defaults +Now you can customize the size of the request body + +``` js +jsonServer.defaults({ + bodyParser: [ + bodyParser.json({ + limit: `100mb`, + extended: false, + }), + bodyParser.urlencoded({ + extended: false, + }), + ] +}) +``` + +- issues: [#38](https://github.com/typicode/json-server/pull/38), [#37](https://github.com/typicode/json-server/pull/37) + +### express +At present, it seems that json-server relies heavily on express and is inconvenient to upgrade, so using its dependencies directly will make me install one less package + +``` js +const {lib: { express }} = jsonServer +``` + + +### options._noRemoveDependents +After deleting data, do not clean up data that are not related to each other + +- type: boolean +- Defaults: false +- issues: [#885](https://github.com/typicode/json-server/issues/885) + +### options._noDataNext +Allows entry to the next route when there is no data, which makes it work seamlessly with other programs + +- type: boolean +- Defaults: false +- issues: [#1330](https://github.com/typicode/json-server/issues/1330) + +### options._noDbRoute +Assuming a db.json data breach poses a risk, it can be turned off with this option + +- type: boolean +- Defaults: false + # JSON Server [![Node.js CI](https://github.com/typicode/json-server/actions/workflows/node.js.yml/badge.svg?branch=master)](https://github.com/typicode/json-server/actions/workflows/node.js.yml) Get a full fake REST API with __zero coding__ in __less than 30 seconds__ (seriously) diff --git a/__tests__/arg.js.run b/__tests__/arg.js.run new file mode 100644 index 000000000..df7660260 --- /dev/null +++ b/__tests__/arg.js.run @@ -0,0 +1,16 @@ +const cp = require('child_process') +test({ _noDataNext: true }) +test({ _noDbRoute: true }) +test({ _noRemoveDependents: true }) + +function test(obj) { + const arg = Object.entries(obj).reduce((acc, [key, val]) => { + return `${acc} ${key}=${val}` + }, ``) + cp.execSync( + `npm run build && npx cross-env NODE_ENV=test arg="${arg}" jest`, + { + stdio: 'inherit', + } + ) +} diff --git a/__tests__/server/plural.js b/__tests__/server/plural.js index 67043cc9b..316471807 100644 --- a/__tests__/server/plural.js +++ b/__tests__/server/plural.js @@ -2,6 +2,8 @@ const assert = require('assert') const _ = require('lodash') const request = require('supertest') const jsonServer = require('../../src/server') +const { parseArgv } = require('../../src/server/utils') +const cliArg = parseArgv(process.env.arg) describe('Server', () => { let server @@ -87,7 +89,7 @@ describe('Server', () => { ] server = jsonServer.create() - router = jsonServer.router(db) + router = jsonServer.router(db, cliArg) server.use(jsonServer.defaults()) server.use(jsonServer.rewriter(rewriterRules)) server.use(router) @@ -95,7 +97,10 @@ describe('Server', () => { describe('GET /db', () => { test('should respond with json and full database', () => - request(server).get('/db').expect('Content-Type', /json/).expect(200, db)) + request(server) + .get('/db') + .expect('Content-Type', /json/) + .expect(...(cliArg._noDbRoute ? [404, {}] : [200, db]))) }) describe('GET /:resource', () => { @@ -370,7 +375,7 @@ describe('Server', () => { test('should respond with 404 if resource is not found', () => request(server) .get('/posts/9001') - .expect('Content-Type', /json/) + .expect('Content-Type', cliArg._noDataNext ? /html/ : /json/) .expect(404, {})) }) @@ -567,7 +572,7 @@ describe('Server', () => { request(server) .put('/posts/9001') .send({ id: 1, body: 'bar' }) - .expect('Content-Type', /json/) + .expect('Content-Type', cliArg._noDataNext ? /html/ : /json/) .expect(404, {})) }) @@ -603,7 +608,7 @@ describe('Server', () => { request(server) .patch('/posts/9001') .send({ body: 'bar' }) - .expect('Content-Type', /json/) + .expect('Content-Type', cliArg._noDataNext ? /html/ : /json/) .expect(404, {})) }) @@ -625,13 +630,13 @@ describe('Server', () => { test('should respond with empty data, destroy resource and dependent resources', async () => { await request(server).del('/posts/1').expect(200, {}) assert.strictEqual(db.posts.length, 1) - assert.strictEqual(db.comments.length, 3) + assert.strictEqual(db.comments.length, cliArg._noRemoveDependents ? 5 : 3) }) test('should respond with 404 if resource is not found', () => request(server) .del('/posts/9001') - .expect('Content-Type', /json/) + .expect('Content-Type', cliArg._noDataNext ? /html/ : /json/) .expect(404, {})) }) diff --git a/package.json b/package.json index a6f18d3c5..a2316cf01 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,7 @@ ], "scripts": { "prepare": "husky install", - "test": "npm run build && cross-env NODE_ENV=test jest", + "test": "npm run build && cross-env NODE_ENV=test jest && node __tests__/arg.js.run", "start": "babel-node -- src/cli/bin db.json -r routes.json", "lint": "eslint . --ignore-path .gitignore", "fix": "npm run lint -- --fix", diff --git a/src/cli/index.js b/src/cli/index.js index d817f9d7b..80919e85a 100644 --- a/src/cli/index.js +++ b/src/cli/index.js @@ -74,6 +74,21 @@ module.exports = function () { description: 'Path to config file', default: 'json-server.json', }, + _noDbRoute: { + type: 'boolean', + description: 'Do not use the /db route', + default: false, + }, + _noDataNext: { + type: 'boolean', + description: 'Enter a middleware when there is no data', + default: false, + }, + _noRemoveDependents: { + type: 'boolean', + description: 'Do not clear data without dependencies', + default: false, + }, }) .boolean('watch') .boolean('read-only') diff --git a/src/cli/run.js b/src/cli/run.js index c4238f473..ec6ca3af0 100644 --- a/src/cli/run.js +++ b/src/cli/run.js @@ -38,14 +38,15 @@ function prettyPrint(argv, object, rules) { function createApp(db, routes, middlewares, argv) { const app = jsonServer.create() - const { foreignKeySuffix } = argv - const router = jsonServer.router( db, - foreignKeySuffix ? { foreignKeySuffix } : undefined, + argv, ) const defaultsOpts = { + _noRemoveDependents: argv._noRemoveDependents, + _noDataNext: argv._noDataNext, + _noDbRoute: argv._noDbRoute, logger: !argv.quiet, readOnly: argv.readOnly, noCors: argv.noCors, diff --git a/src/server/defaults.js b/src/server/defaults.js index d4cd55df5..46818a167 100644 --- a/src/server/defaults.js +++ b/src/server/defaults.js @@ -12,7 +12,17 @@ module.exports = function (opts) { const defaultDir = path.join(__dirname, '../../public') const staticDir = fs.existsSync(userDir) ? userDir : defaultDir - opts = Object.assign({ logger: true, static: staticDir }, opts) + opts = Object.assign( + { + noGzip: undefined, + noCors: undefined, + readOnly: undefined, + bodyParser: undefined, // true / false / object + logger: true, + static: staticDir, + }, + opts + ) const arr = [] @@ -65,9 +75,12 @@ module.exports = function (opts) { } // Add middlewares - if (opts.bodyParser) { + if (opts.bodyParser && typeof opts.bodyParser !== `object`) { arr.push(bodyParser) } + if (opts.bodyParser && typeof opts.bodyParser === `object`) { + arr.push(opts.bodyParser) + } return arr } diff --git a/src/server/index.js b/src/server/index.js index 1f2933d8b..2750ef8b0 100644 --- a/src/server/index.js +++ b/src/server/index.js @@ -1,6 +1,9 @@ const express = require('express') module.exports = { + lib: { + express, + }, create: () => express().set('json spaces', 2), defaults: require('./defaults'), router: require('./router'), diff --git a/src/server/router/index.js b/src/server/router/index.js index 6b59d8441..3b32d147c 100644 --- a/src/server/router/index.js +++ b/src/server/router/index.js @@ -13,7 +13,17 @@ const singular = require('./singular') const mixins = require('../mixins') module.exports = (db, opts) => { - opts = Object.assign({ foreignKeySuffix: 'Id', _isFake: false }, opts) + opts = Object.assign( + { + foreignKeySuffix: 'Id', + _isFake: false, + _noRemoveDependents: false, + _noDataNext: false, + _noDbRoute: false, + bodyParser: undefined, + }, + opts + ) if (typeof db === 'string') { db = low(new FileSync(db)) @@ -26,7 +36,7 @@ module.exports = (db, opts) => { // Add middlewares router.use(methodOverride()) - router.use(bodyParser) + router.use(typeof opts.bodyParser === `object` ? opts.bodyParser : bodyParser) validateData(db.getState()) @@ -40,14 +50,19 @@ module.exports = (db, opts) => { router.db = db // Expose render - router.render = (req, res) => { + router.render = (req, res, next) => { + if (!res.locals.data) { + res.status(404) + res.locals.data = {} + } res.jsonp(res.locals.data) } // GET /db - router.get('/db', (req, res) => { - res.jsonp(db.getState()) - }) + !opts._noDbRoute && + router.get('/db', (req, res) => { + res.jsonp(db.getState()) + }) // Handle /:parent/:parentId/:resource router.use(nested(opts)) @@ -81,13 +96,12 @@ module.exports = (db, opts) => { throw new Error(msg) }).value() - router.use((req, res) => { - if (!res.locals.data) { - res.status(404) - res.locals.data = {} + router.use((req, res, next) => { + if (opts._noDataNext && !res.locals.data) { + next() + } else { + router.render(req, res, next) } - - router.render(req, res) }) router.use((err, req, res, next) => { diff --git a/src/server/router/plural.js b/src/server/router/plural.js index 0e73dc399..bf038ea7e 100644 --- a/src/server/router/plural.js +++ b/src/server/router/plural.js @@ -313,10 +313,12 @@ module.exports = (db, name, opts) => { resource = db.get(name).removeById(req.params.id).value() // Remove dependents documents - const removable = db._.getRemovable(db.getState(), opts) - removable.forEach((item) => { - db.get(item.name).removeById(item.id).value() - }) + if (opts._noRemoveDependents === false) { + const removable = db._.getRemovable(db.getState(), opts) + removable.forEach((item) => { + db.get(item.name).removeById(item.id).value() + }) + } } if (resource) { diff --git a/src/server/utils.js b/src/server/utils.js index c84b36af9..2b5877134 100644 --- a/src/server/utils.js +++ b/src/server/utils.js @@ -1,7 +1,27 @@ module.exports = { + parseArgv, getPage, } +function parseArgv(arr) { + arr = typeof arr === 'string' ? arr.trim().split(/\s+/) : arr + return (arr || process.argv.slice(2)).reduce((acc, arg) => { + let [k, ...v] = arg.split(`=`) + v = v.join(`=`) + acc[k] = + v === `` + ? true + : /^(true|false)$/.test(v) + ? v === `true` + : /[\d|.]+/.test(v) + ? isNaN(Number(v)) + ? v + : Number(v) + : v + return acc + }, {}) +} + function getPage(array, page, perPage) { const obj = {} const start = (page - 1) * perPage