Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add some useful features #1553

Open
wants to merge 13 commits into
base: v0
Choose a base branch
from
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
**/*.log
__tests__/**/db-*.json
node_modules
tmp
lib
.DS_Store
.idea
db.json

.history
*.tgz
56 changes: 56 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -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)
Expand Down
16 changes: 16 additions & 0 deletions __tests__/arg.js.run
Original file line number Diff line number Diff line change
@@ -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',
}
)
}
19 changes: 12 additions & 7 deletions __tests__/server/plural.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -87,15 +89,18 @@ 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)
})

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', () => {
Expand Down Expand Up @@ -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, {}))
})

Expand Down Expand Up @@ -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, {}))
})

Expand Down Expand Up @@ -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, {}))
})

Expand All @@ -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, {}))
})

Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
15 changes: 15 additions & 0 deletions src/cli/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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')
Expand Down
7 changes: 4 additions & 3 deletions src/cli/run.js
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
17 changes: 15 additions & 2 deletions src/server/defaults.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 = []

Expand Down Expand Up @@ -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
}
3 changes: 3 additions & 0 deletions src/server/index.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
const express = require('express')

module.exports = {
lib: {
express,
},
create: () => express().set('json spaces', 2),
defaults: require('./defaults'),
router: require('./router'),
Expand Down
38 changes: 26 additions & 12 deletions src/server/router/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand All @@ -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())

Expand All @@ -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))
Expand Down Expand Up @@ -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) => {
Expand Down
10 changes: 6 additions & 4 deletions src/server/router/plural.js
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
20 changes: 20 additions & 0 deletions src/server/utils.js
Original file line number Diff line number Diff line change
@@ -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
Expand Down