diff --git a/README.md b/README.md index cdf3a55..8f59400 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,6 @@ -[![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) - -![](https://raw.githubusercontent.com/typicode/json-server/master/public/images/logo.png) +

+ +

# 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. +[![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 ### Command line interface ```bash -$ cat db.json +$ json-server --file db.json +$ curl -i http://localhost:3000/posts/1 +``` + +```javascript +// db.json { "posts": [ { "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 ```javascript var server = require('json-server'); -var db = { +server.low.db = { posts: [ { 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. @@ -58,38 +64,25 @@ You can find a running demo here: http://jsonplaceholder.typicode.com. $ npm install -g json-server ``` - ## Usage ### Command line interface ```bash -json-server --help - Usage: json-server [options] + Usage: json-server [options] Options: - -h, --help output usage information - -V, --version output the version number - -f --file load db from a js or json file - -u --url load db from a URL - -p --port [port] server port - --read-only read only mode -``` + --version output version + --port set port -JSON Server can load JSON from multiple sources: + Exemples: -```bash -$ json-server --file db.json -$ json-server --file seed.js -$ 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 + json-server db.json + json-server seed.js + json-server http://example.com/db.json + ``` #### 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. - ### Node module #### run(db, [options]) @@ -149,7 +141,7 @@ By default, ```port``` is set to 3000 and ```readOnly``` to false. ``` GET /:resource -GET /:resource?attr=&attr=& +GET /:resource?filter=&filter=& GET /:parent/:parentId/:resource GET /:resource/:id POST /:resource @@ -189,4 +181,4 @@ $ npm test ## 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) diff --git a/bin/index.js b/bin/index.js new file mode 100644 index 0000000..b4fd9a8 --- /dev/null +++ b/bin/index.js @@ -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) \ No newline at end of file diff --git a/db.json b/db.json new file mode 100644 index 0000000..3641986 --- /dev/null +++ b/db.json @@ -0,0 +1,20 @@ +{ + "posts": [ + { + "id": 2, + "body": "bar" + } + ], + "comments": [ + { + "id": 1, + "published": true, + "postId": 1 + }, + { + "id": 2, + "published": false, + "postId": 1 + } + ] +} \ No newline at end of file diff --git a/package.json b/package.json index 455d54b..16895de 100644 --- a/package.json +++ b/package.json @@ -2,8 +2,8 @@ "name": "json-server", "version": "0.2.0", "description": "Serves JSON files through REST routes.", - "main": "server.js", - "bin": "./bin/cli.js", + "main": "./src/server.js", + "bin": "./bin/index.js", "directories": { "test": "test" }, @@ -15,7 +15,9 @@ "underscore.inflections": "~0.2.1", "low": "^0.4.2", "yargs": "^1.2.1", - "chalk": "^0.4.0" + "chalk": "^0.4.0", + "minimist": "0.0.8", + "update-notifier": "^0.1.8" }, "devDependencies": { "supertest": "~0.8.1", diff --git a/src/command.js b/src/cli.js similarity index 55% rename from src/command.js rename to src/cli.js index 25d8573..b090fff 100644 --- a/src/command.js +++ b/src/cli.js @@ -1,66 +1,87 @@ - +var fs = require('fs') var chalk = require('chalk') -var request = require('request') +var minimist = require('minimist') +var request = require('superagent') var low = require('low') var server = require('./server') -function hello() { - console.log( - chalk.green('\n{^ ^} Heya!\n') - ) +// Output version +function version() { + 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) { for (var prop in low.db) { 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( '\nEnter ' + chalk.green('`s`') + ' at any time to create a snapshot of the db\n' ) process.stdin.resume() process.stdin.setEncoding('utf8') - process.stdin.on('data', function (chunk) { 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 diff --git a/src/help.txt b/src/help.txt new file mode 100644 index 0000000..06fb12b --- /dev/null +++ b/src/help.txt @@ -0,0 +1,14 @@ + + Usage: json-server [options] + + Options: + + --version output version + --port set port + + Exemples: + + json-server db.json + json-server seed.js + json-server http://example.com/db.json + \ No newline at end of file diff --git a/src/routes.js b/src/routes.js new file mode 100644 index 0000000..3546ba2 --- /dev/null +++ b/src/routes.js @@ -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 \ No newline at end of file diff --git a/src/server.js b/src/server.js index 530900f..dcab263 100644 --- a/src/server.js +++ b/src/server.js @@ -1,9 +1,10 @@ var express = require('express') +var cors = require('cors') var http = require('http') var path = require('path') -var _ = require('underscore') var low = require('low') var utils = require('./utils') +var routes = require('./routes') low._.createId = utils.createId @@ -15,104 +16,25 @@ server.use(express.json()) server.use(express.urlencoded()) server.use(express.methodOverride()) server.use(express.static(path.join(__dirname, '../public'))) +server.use(cors()) server.use(server.router) if ('development' == server.get('env')) { 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 -routes.db = function(req, res, next) { - res.jsonp(low.db) -} +server.post( '/:resource' , routes.create) -// GET /:resource?attr=&attr= -routes.list = function(req, res, next) { - var properties = {} - var query +server.put( '/:resource/:id' , routes.update) +server.patch('/:resource/:id' , routes.update) - Object.keys(req.query).forEach(function (key) { - var value = req.query[key] - properties[key] = utils.toNative(value) - }) +server.del( '/:resource/:id' , routes.destroy) - if (_(properties).isEmpty()) { - 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 - ) -}) +server.low = low module.exports = server \ No newline at end of file diff --git a/test/server.js b/test/server.js index 2a6100c..7c7e373 100644 --- a/test/server.js +++ b/test/server.js @@ -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() { it('should respond with json and corresponding nested resources', function(done) { request(server)