This commit is contained in:
Typicode
2014-03-21 17:18:54 +01:00
parent 681e6fcf02
commit ca775b7544
9 changed files with 245 additions and 172 deletions

View File

@ -1,7 +1,6 @@
[![Build Status](https://travis-ci.org/typicode/json-server.png)](https://travis-ci.org/typicode/json-server) <p align="center">
[![NPM version](https://badge.fury.io/js/json-server.png)](http://badge.fury.io/js/json-server) <img height="56" width="64" src="http://i.imgur.com/JCxrqRw.png"/>
</p>
![](https://raw.githubusercontent.com/typicode/json-server/master/public/images/logo.png)
# JSON Server # JSON Server
@ -9,36 +8,43 @@ Give it a JSON or JS seed file and it will serve it through REST routes.
Created with :heart: for front-end developers who need a flexible back-end for quick prototyping and mocking. Created with :heart: for front-end developers who need a flexible back-end for quick prototyping and mocking.
[![Build Status](https://travis-ci.org/typicode/json-server.png)](https://travis-ci.org/typicode/json-server)
[![NPM version](https://badge.fury.io/js/json-server.png)](http://badge.fury.io/js/json-server)
## Examples ## Examples
### Command line interface ### Command line interface
```bash ```bash
$ cat db.json $ json-server --file db.json
$ curl -i http://localhost:3000/posts/1
```
```javascript
// db.json
{ {
"posts": [ "posts": [
{ "id": 1, "body": "foo" } { "id": 1, "body": "foo" }
] ]
} }
$ json-server --file db.json
$ curl -i http://localhost:3000/posts/1
``` ```
You can type 's' at any moment to save the current live JSON object to timestamped
file.
### Node module ### Node module
```javascript ```javascript
var server = require('json-server'); var server = require('json-server');
var db = { server.low.db = {
posts: [ posts: [
{ id: 1, body: 'foo' } { id: 1, body: 'foo' }
] ]
} }
server.run(db); server.get('/another/route', function(req, res, next) {
// ...
})
server.listen(3000);
``` ```
You can find a running demo here: http://jsonplaceholder.typicode.com. You can find a running demo here: http://jsonplaceholder.typicode.com.
@ -58,38 +64,25 @@ You can find a running demo here: http://jsonplaceholder.typicode.com.
$ npm install -g json-server $ npm install -g json-server
``` ```
## Usage ## Usage
### Command line interface ### Command line interface
```bash ```bash
json-server --help
Usage: json-server [options] Usage: json-server <source> [options]
Options: Options:
-h, --help output usage information --version output version
-V, --version output the version number --port <port> set port
-f --file <file> load db from a js or json file
-u --url <url> load db from a URL
-p --port [port] server port
--read-only read only mode
```
JSON Server can load JSON from multiple sources: Exemples:
```bash json-server db.json
$ json-server --file db.json json-server seed.js
$ json-server --file seed.js json-server http://example.com/db.json
$ json-server --url http://example.com/db.json
```
And be run in read-only mode (useful if deployed on a public server):
```bash
$ json-server --file db.json --read-only
``` ```
#### Input #### Input
@ -129,7 +122,6 @@ JSON Server expects JS files to export a ```run``` method that returns an object
Seed files are useful if you need to programmaticaly create a lot of data. Seed files are useful if you need to programmaticaly create a lot of data.
### Node module ### Node module
#### run(db, [options]) #### run(db, [options])
@ -149,7 +141,7 @@ By default, ```port``` is set to 3000 and ```readOnly``` to false.
``` ```
GET /:resource GET /:resource
GET /:resource?attr=&attr=& GET /:resource?filter=&filter=&
GET /:parent/:parentId/:resource GET /:parent/:parentId/:resource
GET /:resource/:id GET /:resource/:id
POST /:resource POST /:resource
@ -189,4 +181,4 @@ $ npm test
## Articles ## Articles
[Fast prototyping using Restangular and Json-server](http://bahmutov.calepin.co/fast-prototyping-using-restangular-and-json-server.html) * [Fast prototyping using Restangular and Json-server](http://bahmutov.calepin.co/fast-prototyping-using-restangular-and-json-server.html)

11
bin/index.js Normal file
View File

@ -0,0 +1,11 @@
#!/usr/bin/env node
var minimist = require('minimist')
var updateNotifier = require('update-notifier')
var cli = require('../src/cli')
var notifier = updateNotifier({packagePath: '../package'})
if (notifier.update) notifier.notify()
var argv = minimist(process.argv.slice(2))
cli.run(argv)

20
db.json Normal file
View File

@ -0,0 +1,20 @@
{
"posts": [
{
"id": 2,
"body": "bar"
}
],
"comments": [
{
"id": 1,
"published": true,
"postId": 1
},
{
"id": 2,
"published": false,
"postId": 1
}
]
}

View File

@ -2,8 +2,8 @@
"name": "json-server", "name": "json-server",
"version": "0.2.0", "version": "0.2.0",
"description": "Serves JSON files through REST routes.", "description": "Serves JSON files through REST routes.",
"main": "server.js", "main": "./src/server.js",
"bin": "./bin/cli.js", "bin": "./bin/index.js",
"directories": { "directories": {
"test": "test" "test": "test"
}, },
@ -15,7 +15,9 @@
"underscore.inflections": "~0.2.1", "underscore.inflections": "~0.2.1",
"low": "^0.4.2", "low": "^0.4.2",
"yargs": "^1.2.1", "yargs": "^1.2.1",
"chalk": "^0.4.0" "chalk": "^0.4.0",
"minimist": "0.0.8",
"update-notifier": "^0.1.8"
}, },
"devDependencies": { "devDependencies": {
"supertest": "~0.8.1", "supertest": "~0.8.1",

View File

@ -1,66 +1,87 @@
var fs = require('fs')
var chalk = require('chalk') var chalk = require('chalk')
var request = require('request') var minimist = require('minimist')
var request = require('superagent')
var low = require('low') var low = require('low')
var server = require('./server') var server = require('./server')
function hello() { // Output version
console.log( function version() {
chalk.green('\n{^ ^} Heya!\n') var pkg = require('../package.json')
) console.log(pkg.version)
} }
// Output help.txt with some colors
function help() {
var txt = fs.readFileSync(__dirname + '/help.txt').toString()
txt = txt.replace(/json-server/g, chalk.green('json-server'))
console.log(txt)
}
// Start server
function start(port) { function start(port) {
for (var prop in low.db) { for (var prop in low.db) {
console.log('http://localhost:' + port + '/' + chalk.green(prop)) console.log('http://localhost:' + port + '/' + chalk.green(prop))
} }
server.listen(port)
}
function run(argv) {
hello();
var source = argv._[0]
console.log('Loading database from ' + source + '\n')
if (/\.json$/.test(source)) {
var path = process.cwd() + '/' + source
low.path = path
low.db = require(path);
start(argv.port)
}
if (/\.js$/.test(source)) {
var path = process.cwd() + '/' + source
low.db = require(path).run();
start(argv.port)
}
if (/^http/.test(source)) {
request.get(source)
.end(function(err, res) {
if (err) {
console.error(err)
} else {
low.db = JSON.parse(res.text)
}
})
start(argv.port)
}
console.log( console.log(
'\nEnter ' + chalk.green('`s`') + ' at any time to create a snapshot of the db\n' '\nEnter ' + chalk.green('`s`') + ' at any time to create a snapshot of the db\n'
) )
process.stdin.resume() process.stdin.resume()
process.stdin.setEncoding('utf8') process.stdin.setEncoding('utf8')
process.stdin.on('data', function (chunk) { process.stdin.on('data', function (chunk) {
if (chunk.trim().toLowerCase() === 's') { if (chunk.trim().toLowerCase() === 's') {
low.save('db-' + Date.now() + '.json') var file = 'db-' + Date.now() + '.json'
low.save(file)
console.log('\nSaved snapshot to ' + chalk.green(file) + '\n')
} }
}) })
server.listen(port)
}
// Load source
function load(source, port) {
console.log(chalk.green('\n{^ ^} Heya!\n'))
console.log('Loading database from ' + source + '\n')
if (/\.json$/.test(source)) {
var path = process.cwd() + '/' + source
low.path = path
low.db = require(path);
start(port)
}
if (/\.js$/.test(source)) {
var path = process.cwd() + '/' + source
low.db = require(path).run();
start(port)
}
if (/^http/.test(source)) {
request
.get(source)
.end(function(err, res) {
if (err) {
console.error(err)
} else {
low.db = JSON.parse(res.text)
start(port)
}
})
}
}
// Uses minimist parsed argv
function run(argv) {
var source = argv._[0]
var port = argv.port || 3000
if (argv.version) return version()
if (source) return load(source, port)
help()
} }
module.exports.run = run module.exports.run = run

14
src/help.txt Normal file
View File

@ -0,0 +1,14 @@
Usage: json-server <source> [options]
Options:
--version output version
--port <port> set port
Exemples:
json-server db.json
json-server seed.js
json-server http://example.com/db.json

81
src/routes.js Normal file
View File

@ -0,0 +1,81 @@
var _ = require('underscore')
var low = require('low')
var utils = require('./utils')
var routes = {}
// GET /db
routes.db = function(req, res, next) {
res.jsonp(low.db)
}
// GET /:resource?attr=&attr=
// GET /:parent/:parentId/:resource
routes.list = function(req, res, next) {
var props = {}
var resource
var _start = req.query._start
var _end = req.query._end
delete req.query._start
delete req.query._end
if (req.params.parent) {
props[req.params.parent.slice(0, - 1) + 'Id'] = +req.params.parentId
}
for (var key in req.query) {
props[key] = utils.toNative(req.query[key])
}
if (_(props).isEmpty()) {
resource = low(req.params.resource).value()
} else {
resource = low(req.params.resource).where(props).value()
}
if (_start) {
resource = resource.slice(_start, _end)
}
res.jsonp(resource)
}
// GET /:resource/:id
routes.show = function(req, res, next) {
var resource = low(req.params.resource)
.get(+req.params.id)
.value()
res.jsonp(resource)
}
// POST /:resource
routes.create = function(req, res, next) {
var resource = low(req.params.resource)
.insert(req.body)
.value()
res.jsonp(resource)
}
// PUT /:resource/:id
// PATCH /:resource/:id
routes.update = function(req, res, next) {
var resource = low(req.params.resource)
.update(+req.params.id, req.body)
.value()
res.jsonp(resource)
}
// DELETE /:resource/:id
routes.destroy = function(req, res, next) {
low(req.params.resource).remove(+req.params.id)
utils.clean()
res.send(204)
}
module.exports = routes

View File

@ -1,9 +1,10 @@
var express = require('express') var express = require('express')
var cors = require('cors')
var http = require('http') var http = require('http')
var path = require('path') var path = require('path')
var _ = require('underscore')
var low = require('low') var low = require('low')
var utils = require('./utils') var utils = require('./utils')
var routes = require('./routes')
low._.createId = utils.createId low._.createId = utils.createId
@ -15,104 +16,25 @@ server.use(express.json())
server.use(express.urlencoded()) server.use(express.urlencoded())
server.use(express.methodOverride()) server.use(express.methodOverride())
server.use(express.static(path.join(__dirname, '../public'))) server.use(express.static(path.join(__dirname, '../public')))
server.use(cors())
server.use(server.router) server.use(server.router)
if ('development' == server.get('env')) { if ('development' == server.get('env')) {
server.use(express.errorHandler()); server.use(express.errorHandler());
} }
routes = {} server.get( '/db' , routes.db)
server.get( '/:resource' , routes.list)
server.get( '/:parent/:parentId/:resource' , routes.list)
server.get( '/:resource/:id' , routes.show)
// GET /db server.post( '/:resource' , routes.create)
routes.db = function(req, res, next) {
res.jsonp(low.db)
}
// GET /:resource?attr=&attr= server.put( '/:resource/:id' , routes.update)
routes.list = function(req, res, next) { server.patch('/:resource/:id' , routes.update)
var properties = {}
var query
Object.keys(req.query).forEach(function (key) { server.del( '/:resource/:id' , routes.destroy)
var value = req.query[key]
properties[key] = utils.toNative(value)
})
if (_(properties).isEmpty()) { server.low = low
query = low(req.params.resource)
} else {
query = low(req.params.resource).where(properties)
}
res.jsonp(query.value())
}
// GET /:parent/:parentId/:resource
routes.nestedList = function(req, res, next) {
var properties = {}
var resource
// Set parentID
properties[req.params.parent.slice(0, - 1) + 'Id'] = +req.params.parentId
// Filter using parentID
resource = low(req.params.resource)
.where(properties)
.value()
res.jsonp(resource)
}
// GET /:resource/:id
routes.show = function(req, res, next) {
var resource = low(req.params.resource)
.get(+req.params.id)
.value()
res.jsonp(resource)
}
// POST /:resource
routes.create = function(req, res, next) {
var resource = low(req.params.resource)
.insert(req.body)
.value()
res.jsonp(resource)
}
// PUT /:resource/:id
// PATCH /:resource/:id
routes.update = function(req, res, next) {
var resource = low(req.params.resource)
.update(+req.params.id, req.body)
.value()
res.jsonp(resource)
}
// DELETE /:resource/:id
routes.destroy = function(req, res, next) {
low(req.params.resource).remove(+req.params.id)
utils.clean()
res.send(204)
}
server.get('/db', routes.db)
server.get('/:resource', routes.list)
server.get('/:parent/:parentId/:resource', routes.nestedList)
server.get('/:resource/:id', routes.show)
server.post('/:resource', routes.create)
server.put('/:resource/:id', routes.update)
server.patch('/:resource/:id', routes.update)
server.del('/:resource/:id', routes.destroy)
server.on('after', function (req, res, route, err) {
var latency = Date.now() - req.time()
console.log('%s %s %s - %sms',
req.method, req.url, res.statusCode, latency
)
})
module.exports = server module.exports = server

View File

@ -52,6 +52,16 @@ describe('Server', function() {
}) })
}) })
describe.only('GET /:resource?_start=&_end=', function() {
it('should respond with sliced array', function(done) {
request(server)
.get('/comments?_start=1&_end=2')
.expect('Content-Type', /json/)
.expect(low.db.comments.slice(1, 2))
.expect(200, done)
})
})
describe('GET /:parent/:parentId/:resource', function() { describe('GET /:parent/:parentId/:resource', function() {
it('should respond with json and corresponding nested resources', function(done) { it('should respond with json and corresponding nested resources', function(done) {
request(server) request(server)