From 25b2e436489c1557f6595811ba95207857d2853a Mon Sep 17 00:00:00 2001 From: Typicode Date: Tue, 18 Mar 2014 20:25:13 +0100 Subject: [PATCH 01/15] Use restify --- bin/cli.js | 82 -------------- package.json | 4 +- routes/read-only.js | 71 ------------ routes/read-write.js | 83 -------------- server.js | 96 ---------------- src/server.js | 127 +++++++++++++++++++++ src/utils.js | 60 ++++++++++ test/read-only.js | 138 ----------------------- test/{read-write.js => server.js} | 177 +++++++++++++++--------------- test/static.js | 16 +-- utils/db-mixins.js | 86 --------------- utils/logger.js | 11 -- utils/utils.js | 11 -- 13 files changed, 283 insertions(+), 679 deletions(-) delete mode 100755 bin/cli.js delete mode 100644 routes/read-only.js delete mode 100644 routes/read-write.js delete mode 100644 server.js create mode 100644 src/server.js create mode 100644 src/utils.js delete mode 100644 test/read-only.js rename test/{read-write.js => server.js} (60%) delete mode 100644 utils/db-mixins.js delete mode 100644 utils/logger.js delete mode 100644 utils/utils.js diff --git a/bin/cli.js b/bin/cli.js deleted file mode 100755 index 8089406..0000000 --- a/bin/cli.js +++ /dev/null @@ -1,82 +0,0 @@ -#!/usr/bin/env node -var program = require('commander'), - request = require('superagent'), - server = require('../server'), - logger = require('../utils/logger'), - moment = require('moment'), - fs = require('fs'), - pkg = require('../package.json'), - options = {}; - -function loadFile(file, cb) { - var path = process.cwd() + '/' + file, - db; - - if (/\.json$/.test(file)) db = require(path); - if (/\.js$/.test(file)) db = require(path).run(); - - cb(db); -} - -function loadURL(url, cb) { - logger.info('Fetching ' + url + '...') - request - .get(url) - .end(function(error, res) { - if (error) { - logger.error(error); - } else { - cb(JSON.parse(res.text)); - } - }); -} - -function saveDbOnCommand(app) { - console.assert(app, 'expected app object'); - - process.stdin.resume(); - process.stdin.setEncoding('utf8'); - logger.notice('To save live database at any moment, enter `s`'); - - process.stdin.on('data', function (userInput) { - if (userInput.trim().toLowerCase() == 's') { - var liveDB = app.db(); - var now = moment().format('YYYY-MM-DD-HH:mm:ss') - var filename = 'json-server.' + now + '.json'; - console.assert(liveDB, 'expected live db object'); - fs.writeFileSync(filename, - JSON.stringify(liveDB, null, 2), - 'utf-8'); - console.log('saved db to', filename); - } - }); -} - -function onDatabaseLoaded(db) { - var app = server.run(db, options); - saveDbOnCommand(app); - return app; -} - -program - .version(pkg.version) - .option('-f --file ', 'load db from a js or json file') - .option('-u --url ', 'load db from a URL') - .option('-p --port [port]', 'server port') - .option('--read-only', 'read only mode'); - -program.on('--help', function () { - var examples = - ' Examples:\n\n' + - ' json-server --file db.json\n' + - ' json-server --file seed.js\n' + - ' json-server --url http://example.com/db.json\n' - console.log(examples); -}); - -program.parse(process.argv); - -if (program.port) options.port = program.port; -if (program.readOnly) options.readOnly = true; -if (program.file) loadFile(program.file, onDatabaseLoaded); -if (program.url) loadURL(program.url, onDatabaseLoaded); diff --git a/package.json b/package.json index 06f7d5b..5ded30f 100644 --- a/package.json +++ b/package.json @@ -15,7 +15,9 @@ "superagent": "~0.15.7", "underscore": "~1.5.2", "underscore.inflections": "~0.2.1", - "moment": "~2.4.0" + "moment": "~2.4.0", + "low": "^0.4.2", + "restify": "^2.6.3" }, "devDependencies": { "supertest": "~0.8.1", diff --git a/routes/read-only.js b/routes/read-only.js deleted file mode 100644 index da0b2f7..0000000 --- a/routes/read-only.js +++ /dev/null @@ -1,71 +0,0 @@ -var _ = require('underscore'), - utils = require('../utils/utils'), - db = {}; - -_.mixin(require('../utils/db-mixins')); - -exports.setDatabase = function(object) { - db = object; -} - -exports.database = function(req, res) { - res.jsonp(db) -} - -// GET /:resource?attr=&attr= -exports.list = function(req, res) { - var collection = db[req.params.resource], - properties = {}, - result; - - Object.keys(req.query).forEach(function (key) { - var value = req.query[key]; - properties[key] = utils.toNative(value); - }); - - if (_(properties).isEmpty()) { - result = collection; - } else { - result = _(collection).where(properties); - } - - res.jsonp(result); -} - -// GET /:parent/:parentId/:resource -exports.nestedList = function(req, res) { - var properties = {}, - resource; - - // Set parentID - properties[req.params.parent.slice(0, - 1) + 'Id'] = +req.params.parentId; - - // Filter using parentID - resource = _.where(db[req.params.resource], properties); - - res.jsonp(resource); -} - -// GET /:resource/:id -exports.show = function(req, res) { - var resource = _.get(db, req.params.resource, +req.params.id); - - res.jsonp(resource); -} - -exports.create = function(req, res) { - req.body.id = Math.round(new Date().getTime() / 1000); - res.jsonp(req.body); -} - -exports.update = function(req, res) { - var resource = _.get(db, req.params.resource, +req.params.id), - clonedResource = _.clone(resource), - result = _.extend(clonedResource, req.body); - - res.jsonp(result); -} - -exports.destroy = function(req, res) { - res.send(204) -} diff --git a/routes/read-write.js b/routes/read-write.js deleted file mode 100644 index 61f89a8..0000000 --- a/routes/read-write.js +++ /dev/null @@ -1,83 +0,0 @@ -var _ = require('underscore'), - utils = require('../utils/utils'), - db = {}; - -_.mixin(require('../utils/db-mixins')); - -exports.setDatabase = function(object) { - db = object; -} - -exports.database = function(req, res) { - res.jsonp(db) -} - -exports.db = function() { - return db; -} - -// GET /:resource?attr=&attr= -exports.list = function(req, res) { - var collection = db[req.params.resource], - properties = {}, - result; - - Object.keys(req.query).forEach(function (key) { - var value = req.query[key]; - properties[key] = utils.toNative(value); - }); - - if (_(properties).isEmpty()) { - result = collection; - } else { - result = _(collection).where(properties); - } - - res.jsonp(result); -} - -// GET /:parent/:parentId/:resource -exports.nestedList = function(req, res) { - var properties = {}, - resource; - - // Set parentID - properties[req.params.parent.slice(0, - 1) + 'Id'] = +req.params.parentId; - - // Filter using parentID - resource = _.where(db[req.params.resource], properties); - - res.jsonp(resource); -} - -// GET /:resource/:id -exports.show = function(req, res) { - var resource = _.get(db, req.params.resource, +req.params.id); - - res.jsonp(resource); -} - -// POST /:resource -exports.create = function(req, res) { - var resource = _.create(db, req.params.resource, req.body); - - res.jsonp(resource); -} - -// PUT /:resource/:id -// PATCH /:resource/:id -exports.update = function(req, res) { - _.update(db, req.params.resource, +req.params.id, req.body); - var resource = _.get(db, req.params.resource, +req.params.id); - - res.jsonp(resource); -} - -// DELETE /:resource/:id -exports.destroy = function(req, res) { - _.remove(db, req.params.resource, +req.params.id); - _.clean(db); - - res.send(204); -} - diff --git a/server.js b/server.js deleted file mode 100644 index f1dc744..0000000 --- a/server.js +++ /dev/null @@ -1,96 +0,0 @@ -var express = require('express'), - cors = require('cors'), - http = require('http'), - path = require('path'), - fs = require('fs'), - _ = require('underscore'), - logger = require('./utils/logger'); - - -var defaultOptions = { - port: process.env.PORT || 3000, - readOnly: false -} - -function createApp(db, options) { - // Create app - var app = express(), - options = options || {}, - routes; - - // Configure all environments - app.use(express.favicon()); - app.use(express.logger('dev')); - app.use(express.json()); - app.use(express.urlencoded()); - app.use(express.methodOverride()); - - - // Configure development - if ('development' == app.get('env')) { - app.use(express.errorHandler()); - } - - // Configure using options provided - app.set('port', options.port); - routes = options.readOnly ? './routes/read-only' : './routes/read-write'; - routes = require(routes); - - // Use default or user public directory - // Note: should be done before CORS and app.router setting - if (fs.existsSync(process.cwd() + '/public')) { - app.use(express.static(process.cwd() + '/public')); - } else { - app.use(express.static(path.join(__dirname, './public'))); - } - - // Enable CORS for everything - app.use(cors()); - app.options('*', cors()); - - // Set app.router - app.use(app.router); - - // Set API entry points - app.get('/db', routes.database) - app.get('/:resource', routes.list); - app.get('/:parent/:parentId/:resource', routes.nestedList); - app.get('/:resource/:id', routes.show); - app.post('/:resource', routes.create); - app.put('/:resource/:id', routes.update); - app.patch('/:resource/:id', routes.update); - app.del('/:resource/:id', routes.destroy); - - // Set database - routes.setDatabase(db); - app.db = routes.db; - - // And done! Ready to serve JSON! - return app; -} - -function run(db, options) { - options = _.defaults(options || {}, defaultOptions); - - var app = createApp(db, options); - - if (_.isEmpty(db)) { - logger.error('No resources found!'); - } else { - logger.success('Available resources'); - for (var prop in db) { - logger.url(options.port, prop); - } - } - - http - .createServer(app) - .listen((options.port), function(){ - logger.success('Express server listening on port ' + options.port); - logger.success('Congrats! Open http://localhost:' + options.port); - }); - return app; -} - -exports.createApp = createApp; -exports.run = run; diff --git a/src/server.js b/src/server.js new file mode 100644 index 0000000..5af9ab5 --- /dev/null +++ b/src/server.js @@ -0,0 +1,127 @@ +var _ = require('underscore') +var low = require('low') +var restify = require('restify') +var utils = require('./utils') + +low._.createId = utils.createId + +var server = restify.createServer() + +server.use(restify.acceptParser(server.acceptable)) +server.use(restify.queryParser()) +server.use(restify.bodyParser()) +server.use(restify.CORS()) +server.use(restify.jsonp()) +server.use(restify.gzipResponse()) + +routes = {} + +// GET /db +routes.db = function(req, res, next) { + res.send(low.db) + next() +} + +// GET /:resource?attr=&attr= +routes.list = function(req, res, next) { + var properties = {} + var query + + Object.keys(req.query).forEach(function (key) { + var value = req.query[key] + properties[key] = utils.toNative(value) + }) + + if (_(properties).isEmpty()) { + query = low(req.params.resource) + } else { + query = low(req.params.resource).where(properties) + } + + res.send(query.value()) + next() +} + +// 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.send(resource) + next() +} + +// GET /:resource/:id +routes.show = function(req, res, next) { + var resource = low(req.params.resource) + .get(+req.params.id) + .value() + + res.send(resource) + next() +} + +// POST /:resource +routes.create = function(req, res, next) { + var resource = low(req.params.resource) + .insert(req.body) + .value() + + res.send(resource) + next() +} + +// 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.send(resource) + next() +} + +// DELETE /:resource/:id +routes.destroy = function(req, res, next) { + try { + low(req.params.resource).remove(+req.params.id) + utils.clean() + + res.send(204) + next() + } catch(e) { + console.trace(e) + } +} + +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.get('/', restify.serveStatic({ + directory: './public', + default: 'index.html' +})); + +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 \ No newline at end of file diff --git a/src/utils.js b/src/utils.js new file mode 100644 index 0000000..45bc2aa --- /dev/null +++ b/src/utils.js @@ -0,0 +1,60 @@ +var low = require('low') +var _ = require('underscore') +_.mixin(require('underscore.inflections')) + + +// Turns string to native. +// Example: +// 'true' -> true +// '1' -> 1 +function toNative(value) { + if (value === 'true' || value === 'false') { + return value === 'true' + } else if (!isNaN(+value)) { + return +value + } else { + return value + } +} + +// Creates incremental id. +function createId(coll) { + if (_.isEmpty(coll)) { + return 1 + } else { + return _.max(coll, function(doc) { + return doc.id + }).id + 1 + } +} + +// Removes empty relations +function clean() { + var toBeRemoved = [] + + _(low.db).each(function(coll, collName) { + _(coll).each(function(doc) { + _(doc).each(function(value, key) { + if (/Id$/.test(key)) { + var reference = _.pluralize(key.slice(0, - 2)) + if (!_.isUndefined(low(reference).get(doc[key]).value())) { + toBeRemoved.push({ + collName: collName, + id: doc.id + }) + } + } + }) + }) + }) + + _(toBeRemoved).each(function(item) { + low(item.collName).remove(item.id); + }) +} + +module.exports = { + toNative: toNative, + createId: createId, + clean: clean +} \ No newline at end of file diff --git a/test/read-only.js b/test/read-only.js deleted file mode 100644 index aca0e33..0000000 --- a/test/read-only.js +++ /dev/null @@ -1,138 +0,0 @@ -var request = require('supertest'), - assert = require('assert'), - server = require('../server'), - fixture = require('./fixture'), - db, - app; - -describe('Read only routes', function() { - - beforeEach(function() { - db = fixture(); - app = server.createApp(db, { readOnly: true }); - }); - - describe('GET /:resource', function() { - it('should respond with json and resources and corresponding resources', function(done) { - request(app) - .get('/posts') - .expect('Content-Type', /json/) - .expect(db.posts) - .expect(200, done); - }); - }); - - describe('GET /:resource?attr=&attr=', function() { - it('should respond with json and filter resources', function(done) { - request(app) - .get('/comments?postId=1&published=true') - .expect('Content-Type', /json/) - .expect([db.comments[0]]) - .expect(200, done); - }); - }); - - describe('GET /:parent/:parentId/:resource', function() { - it('should respond with json and corresponding nested resources', function(done) { - request(app) - .get('/posts/1/comments') - .expect('Content-Type', /json/) - .expect([db.comments[0], db.comments[1]]) - .expect(200, done); - }); - }); - - - describe('GET /:resource/:id', function() { - it('should respond with json and corresponding resource', function(done) { - request(app) - .get('/posts/1') - .expect('Content-Type', /json/) - .expect(db.posts[0]) - .expect(200, done); - }); - }); - - describe('GET /db', function() { - it('should respond with json and full database', function(done) { - request(app) - .get('/db') - .expect('Content-Type', /json/) - .expect(db) - .expect(200, done); - }); - }); - - describe('POST /:resource', function() { - it('should respond with fake json and not create a resource', function(done) { - request(app) - .post('/posts') - .send({body: '...'}) - .expect('Content-Type', /json/) - .expect(200) - .end(function(err, res){ - if (err) return done(err); - assert(res.body.hasOwnProperty('id')); - assert.equal(res.body.body, '...'); - assert.equal(db.posts.length, 2); - done() - }); - }); - }); - - describe('PUT /:resource/:id', function() { - it('should respond with fake json and not update resource', function(done) { - request(app) - .put('/posts/1') - .send({id: 999, body: '...'}) - .expect('Content-Type', /json/) - .expect({id: 999, body: '...'}) - .expect(200) - .end(function(err, res){ - if (err) return done(err); - // Checking that first post wasn't updated - assert.deepEqual(db.posts[0], {id: 1, body: 'foo'}); - done() - }); - }); - }); - - describe('PATCH /:resource/:id', function() { - it('should respond with fake json and not update resource', function(done) { - request(app) - .patch('/posts/1') - .send({body: '...'}) - .expect('Content-Type', /json/) - .expect({id: 1, body: '...'}) - .expect(200) - .end(function(err, res){ - if (err) return done(err); - // Checking that first post wasn't updated - assert.deepEqual(db.posts[0], {id: 1, body: 'foo'}); - done() - }); - }); - }); - - describe('DELETE /:resource/:id', function() { - it('should respond with empty data and not destroy resource', function(done) { - request(app) - .del('/posts/1') - .expect(204) - .end(function(err, res){ - if (err) return done(err); - assert.equal(db.posts.length, 2); - assert.equal(db.comments.length, 4); - done() - }); - }); - }); - - describe('OPTIONS /:resource/:id', function() { - it('should respond with empty data and not destroy resource', function(done) { - request(app) - .options('/posts/1') - .expect(204, done); - }); - }); -}); \ No newline at end of file diff --git a/test/read-write.js b/test/server.js similarity index 60% rename from test/read-write.js rename to test/server.js index 388d4da..28b16ad 100644 --- a/test/read-write.js +++ b/test/server.js @@ -1,130 +1,129 @@ -var request = require('supertest'), - assert = require('assert'), - server = require('../server'), - routes = require('../routes/read-write'), - fixture = require('./fixture'), - db, - app; +var request = require('supertest') +var assert = require('assert') +var low = require('low') +var server = require('../src/server') +var fixture = require('./fixture') +var db -describe('Read write routes', function() { +describe('Server', function() { beforeEach(function() { - db = fixture(); - app = server.createApp(db); - }); - - describe('GET /:resource', function() { - it('should respond with json and corresponding resources', function(done) { - request(app) - .get('/posts') - .expect('Content-Type', /json/) - .expect(db.posts) - .expect(200, done); - }); - }); - - describe('GET /:resource?attr=&attr=', function() { - it('should respond with json and filter resources', function(done) { - request(app) - .get('/comments?postId=1&published=true') - .expect('Content-Type', /json/) - .expect([db.comments[0]]) - .expect(200, done); - }); - }); - - describe('GET /:parent/:parentId/:resource', function() { - it('should respond with json and corresponding nested resources', function(done) { - request(app) - .get('/posts/1/comments') - .expect('Content-Type', /json/) - .expect([ - db.comments[0], - db.comments[1] - ]) - .expect(200, done); - }); - }); - - describe('GET /:resource/:id', function() { - it('should respond with json and corresponding resource', function(done) { - request(app) - .get('/posts/1') - .expect('Content-Type', /json/) - .expect(db.posts[0]) - .expect(200, done); - }); - }); + low.db = fixture() + server + }) describe('GET /db', function() { it('should respond with json and full database', function(done) { - request(app) + request(server) .get('/db') .expect('Content-Type', /json/) - .expect(db) - .expect(200, done); - }); - }); + .expect(low.db) + .expect(200, done) + }) + }) + + describe('GET /:resource', function() { + it('should respond with json and corresponding resources', function(done) { + request(server) + .get('/posts') + .expect('Content-Type', /json/) + .expect(low.db.posts) + .expect(200, done) + }) + }) + + describe('GET /:resource?attr=&attr=', function() { + it('should respond with json and filter resources', function(done) { + request(server) + .get('/comments?postId=1&published=true') + .expect('Content-Type', /json/) + .expect([low.db.comments[0]]) + .expect(200, done) + }) + }) + + describe('GET /:parent/:parentId/:resource', function() { + it('should respond with json and corresponding nested resources', function(done) { + request(server) + .get('/posts/1/comments') + .expect('Content-Type', /json/) + .expect([ + low.db.comments[0], + low.db.comments[1] + ]) + .expect(200, done) + }) + }) + + describe('GET /:resource/:id', function() { + it('should respond with json and corresponding resource', function(done) { + request(server) + .get('/posts/1') + .expect('Content-Type', /json/) + .expect(low.db.posts[0]) + .expect(200, done) + }) + }) describe('POST /:resource', function() { it('should respond with json and create a resource', function(done) { - request(app) + request(server) .post('/posts') .send({body: 'foo'}) .expect('Content-Type', /json/) .expect({id: 3, body: 'foo'}) .expect(200) .end(function(err, res){ - if (err) return done(err); - assert.equal(db.posts.length, 3); - done(); - }); - }); - }); + if (err) return done(err) + assert.equal(low.db.posts.length, 3) + done() + }) + }) + }) describe('PUT /:resource/:id', function() { it('should respond with json and update resource', function(done) { - request(app) + request(server) .put('/posts/1') .send({id: 1, body: 'foo'}) .expect('Content-Type', /json/) .expect({id: 1, body: 'foo'}) .expect(200) .end(function(err, res){ - if (err) return done(err); - assert.deepEqual(db.posts[0], {id: 1, body: 'foo'}); - done(); - }); - }); - }); + if (err) return done(err) + assert.deepEqual(low.db.posts[0], {id: 1, body: 'foo'}) + done() + }) + }) + }) describe('PATCH /:resource/:id', function() { it('should respond with json and update resource', function(done) { - request(app) + request(server) .patch('/posts/1') .send({body: 'bar'}) .expect('Content-Type', /json/) .expect({id: 1, body: 'bar'}) .expect(200) .end(function(err, res){ - if (err) return done(err); - assert.deepEqual(db.posts[0], {id: 1, body: 'bar'}); - done(); - }); - }); - }); + if (err) return done(err) + assert.deepEqual(low.db.posts[0], {id: 1, body: 'bar'}) + done() + }) + }) + }) describe('DELETE /:resource/:id', function() { it('should respond with empty data, destroy resource and dependent resources', function(done) { - request(app) + request(server) .del('/posts/1') .expect(204) .end(function(err, res){ - if (err) return done(err); - assert.equal(db.posts.length, 1); - assert.equal(db.comments.length, 2); - done(); - }); - }); - }); -}); \ No newline at end of file + if (err) return done(err) + assert.equal(low.db.posts.length, 1) + assert.equal(low.db.comments.length, 2) + done() + }) + }) + }) +}) \ No newline at end of file diff --git a/test/static.js b/test/static.js index f623d76..88dcf93 100644 --- a/test/static.js +++ b/test/static.js @@ -1,18 +1,12 @@ -var request = require('supertest'), - assert = require('assert'), - server = require('../server'), - routes = require('../routes/read-write'), - app; +var request = require('supertest') +var assert = require('assert') +var server = require('../src/server') describe('Static routes', function() { - beforeEach(function() { - app = server.createApp({}, routes); - }); - describe('GET /', function() { it('should respond with html', function(done) { - request(app) + request(server) .get('/') .expect('Content-Type', /html/) .expect(200, done); @@ -21,7 +15,7 @@ describe('Static routes', function() { describe('GET /stylesheets/style.css', function() { it('should respond with css', function(done) { - request(app) + request(server) .get('/stylesheets/style.css') .expect('Content-Type', /css/) .expect(200, done); diff --git a/utils/db-mixins.js b/utils/db-mixins.js deleted file mode 100644 index d06a395..0000000 --- a/utils/db-mixins.js +++ /dev/null @@ -1,86 +0,0 @@ -(function(root) { - - var _ = root._ || require('underscore'); - - if (!root._) { - _.mixin(require('underscore.inflections')); - } - - function get(db, table, id) { - return _.find(db[table], function (row) { - return row.id === id - }); - } - - function exist(db, table, id) { - return !_.isUndefined(_.get(db, table, id)); - } - - function createId(db, table) { - if (_.isEmpty(db[table])) { - return 1; - } else { - return _.max(db[table], function(row) { - return row.id; - }).id + 1; - } - } - - function create(db, table, obj) { - var clone = _.clone(obj); - - if (_.isUndefined(clone.id)) clone.id = _.createId(db, table); - - db[table].push(clone); - - return clone; - } - - function update(db, table, id, attrs) { - var row = get(db, table, id), - updatedRow = _.extend(row, attrs), - index = _.indexOf(db[table], row); - - db[table][index] = updatedRow; - } - - function clean(db) { - var toBeRemoved = []; - - _(db).each(function(table, tableName) { - _(table).each(function(row) { - _(row).each(function(value, key) { - if (/Id$/.test(key)) { - var reference = _.pluralize(key.slice(0, - 2)); - if (!_.exist(db, reference, row[key])) { - toBeRemoved.push({ - tableName: tableName, - id: row.id - }); - } - } - }); - }); - }); - - _(toBeRemoved).each(function(row) { - _.remove(db, row.tableName, row.id); - }); - } - - function remove(db, table, id) { - var newTable = _.reject(db[table], function(row) { - return row.id === id; - }); - - db[table] = newTable; - } - - _.get = get; - _.exist = exist; - _.createId = createId; - _.create = create; - _.update = update; - _.clean = clean; - _.remove = remove; -})(this); \ No newline at end of file diff --git a/utils/logger.js b/utils/logger.js deleted file mode 100644 index 20a1200..0000000 --- a/utils/logger.js +++ /dev/null @@ -1,11 +0,0 @@ -var logan = require('logan'); - -logan.set({ - error: ['%', 'red'], - success: ['%', 'green'], - info: ['%', 'grey'], - notice: ['%', 'yellow'], - url: [' http://localhost:%/'.grey + '%'.cyan, '.'] -}) - -module.exports = logan \ No newline at end of file diff --git a/utils/utils.js b/utils/utils.js deleted file mode 100644 index c70615f..0000000 --- a/utils/utils.js +++ /dev/null @@ -1,11 +0,0 @@ -function toNative(value) { - if (value === 'true' || value === 'false') { - return value === 'true'; - } else if (!isNaN(+value)) { - return +value; - } else { - return value; - } -} - -exports.toNative = toNative; \ No newline at end of file From c3fa967a0a6abfd8cec1f16649efe766cdd07599 Mon Sep 17 00:00:00 2001 From: Typicode Date: Wed, 19 Mar 2014 10:31:54 +0100 Subject: [PATCH 02/15] Fix static files --- package.json | 4 +++- src/command.js | 64 ++++++++++++++++++++++++++++++++++++++++++++++++++ src/server.js | 23 ++++++++++-------- 3 files changed, 80 insertions(+), 11 deletions(-) create mode 100644 src/command.js diff --git a/package.json b/package.json index 5ded30f..21f111f 100644 --- a/package.json +++ b/package.json @@ -17,7 +17,9 @@ "underscore.inflections": "~0.2.1", "moment": "~2.4.0", "low": "^0.4.2", - "restify": "^2.6.3" + "restify": "^2.6.3", + "yargs": "^1.2.1", + "ecstatic": "^0.4.13" }, "devDependencies": { "supertest": "~0.8.1", diff --git a/src/command.js b/src/command.js new file mode 100644 index 0000000..b61e0cf --- /dev/null +++ b/src/command.js @@ -0,0 +1,64 @@ +var yargv = require('yargs') +var request = require('request') +var low = require('low') +var server = require('./server') + +function show() { + for (var prop in low.db) { + console.log(server.url + '/' + prop) + } +} + +function start(port) { + server.listen(port, function() { + console.log('%s listening at %s', server.name, server.url); + }) +} + +function run() { + var argv = yargv.usage('Usage: $0 ') + .demand(1) + .default('port', 3000) + .argv + + var source = argv._[0] + + if (/\.json$/.test(source)) { + var path = process.cwd() + '/' + source + low.path = path + low.db = require(path); + show() + start() + } + + if (/\.js$/.test(source)) { + var path = process.cwd() + '/' + source + low.db = require(path).run(); + show() + 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) + } + }) + show() + start() + } + + process.stdin.resume() + process.stdin.setEncoding('utf8') + + process.stdin.on('data', function (chunk) { + if (chunk.trim().toLowerCase() === 's') { + low.save('db-' + Date.now() + '.json') + } + }) +} + +module.exports.run = run diff --git a/src/server.js b/src/server.js index 5af9ab5..f8b454f 100644 --- a/src/server.js +++ b/src/server.js @@ -1,11 +1,14 @@ -var _ = require('underscore') -var low = require('low') -var restify = require('restify') -var utils = require('./utils') +var _ = require('underscore') +var low = require('low') +var ecstatic = require('ecstatic') +var restify = require('restify') +var utils = require('./utils') low._.createId = utils.createId -var server = restify.createServer() +var server = restify.createServer({ + name: 'JSON Server' +}) server.use(restify.acceptParser(server.acceptable)) server.use(restify.queryParser()) @@ -16,6 +19,11 @@ server.use(restify.gzipResponse()) routes = {} +server.get(/^\/$|.*(.css)/, restify.serveStatic({ + 'directory': __dirname + '/../public', + 'default': 'index.html' +})) + // GET /db routes.db = function(req, res, next) { res.send(low.db) @@ -112,11 +120,6 @@ server.put('/:resource/:id', routes.update) server.patch('/:resource/:id', routes.update) server.del('/:resource/:id', routes.destroy) -server.get('/', restify.serveStatic({ - directory: './public', - default: 'index.html' -})); - server.on('after', function (req, res, route, err) { var latency = Date.now() - req.time() console.log('%s %s %s - %sms', From edf553c0ebdacb2261f12cdb290e679b56c501dd Mon Sep 17 00:00:00 2001 From: Typicode Date: Wed, 19 Mar 2014 15:13:36 +0100 Subject: [PATCH 03/15] Going back to express --- package.json | 10 ++---- public/favicon.ico | Bin 0 -> 318 bytes public/images/logo.png | Bin 2723 -> 0 bytes public/index.html | 44 ++++++++++++----------- public/stylesheets/style.css | 47 +++++-------------------- src/command.js | 38 ++++++++++---------- src/server.js | 66 ++++++++++++++--------------------- 7 files changed, 81 insertions(+), 124 deletions(-) create mode 100644 public/favicon.ico delete mode 100644 public/images/logo.png diff --git a/package.json b/package.json index 21f111f..effa268 100644 --- a/package.json +++ b/package.json @@ -8,18 +8,14 @@ "test": "test" }, "dependencies": { - "commander": "~2.0.0", "cors": "~2.1.0", - "express": "~3.4.4", - "logan": "~0.0.2", + "express": "^3.4.8", "superagent": "~0.15.7", "underscore": "~1.5.2", "underscore.inflections": "~0.2.1", - "moment": "~2.4.0", - "low": "^0.4.2", - "restify": "^2.6.3", + "low": "^0.5.0", "yargs": "^1.2.1", - "ecstatic": "^0.4.13" + "chalk": "^0.4.0" }, "devDependencies": { "supertest": "~0.8.1", diff --git a/public/favicon.ico b/public/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..bfdd40dc7202fd8bcd83e37b2d3cd8036e947b6e GIT binary patch literal 318 zcmZQzU<5(|0RbS%!l1#(z#zuJz@P!d0zj+)#2|4HXaJKC0wgya!2kaY4FBpG7!Ecw bFzo-!zyRWd@b~wq7$y!<15yjp0}=xOPhK7B literal 0 HcmV?d00001 diff --git a/public/images/logo.png b/public/images/logo.png deleted file mode 100644 index 8fd3cc15cf312dc3e8b13c5af161ba64a7c2579e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2723 zcmV;U3S9MxP)xN3WG*sy2qIX&MyTYEz}|s8q&i$I5!B zRn=|k7^9`-rjR8e1PJ5?a*@PwY$x6yFCjm2IM-|YP4fFnmMtTG`T6<0=kq?l=P~&G zem}aDlX)cEWUUv@wLnob6uZRd>NaR;7q5D+cn7eXV4Ov~+f6Xp3QjvXqaZ6gn}t;g{Shxa(6W9!9;lLZkQ4|g1~%O15f5d zgLicA&J!*0_AL0$1jWI4I^bu;@OmkX6+e#wcs?Dr-w76jf?#UeVAV-D*2tLDf_V5~ zHcYfC1m>a(mK}!@7h_sZJN#oVq(>?M=JX9%aY8s1bqS~GXLDgz%QJ<*YJ&%k3fqLG$DD0~`;UmtqpqHcGsB@Am>Cno9#i6irN?wA{dqf9oEZANJ7_T1 zJh0+8)OZxJfDwMa5d6!<%asdv8P&M83b(M;HBXSicsgL!3AI*V_E>mYGh9T*!iE@i z?ytlJ-#a;Yr|qD?Y$=8#3hh%O?eJzcq;~uM#o4eXN^JgA(BTGnsW?cJ|A#fO`c!as z^fn8m+rVxJyv2?(i}U;mHpokYhZ8`1RMG-y4fmgRHxpF$sQ=4lCn8#>i7}0e)hHgBfyev8)kxm%@%} zXp+<#6D)8lcVLds0fAX^Orn?A0FTaueaZ4#V^KM*D1gfb*g7A!#mT#NStTqy3uThv zEKP)e&XX|5yi+CDb)S|3dn7?=Xogq5fjRk5+5!G{c=;%-FNI=BV^L}{>`svv=k>2z z*jF{o9&^J7nTI4Eav5OLLf8?lS?Rgpqk5?DKwCepvcnH`9^o)O-v~!rp(DWGN{@pa zJ4~>MVI@IHtMKs#VV*m_>_$)gE!HREel z8O$tz+F-0G{7~8;UPEa>%Vxl5j?U#DWSF2t(uTUi4ckg!SLP6gvJ#g#qnE9C&3X9; z6+wa`0qUW0sMa(n*>M=Zy)3-)g9h`Pi@^_^s8racjny!-mmEcOu1V;CZQ`lDrmml{dEh>fjZqd z;g*)cEij`sDt{zN%RmRCkUsAYrq(O}k!@XrS7~e4@x2j5Vgp@jsHjB)rdVH%DWhU7 zm~$<77ZAHMG3lL!mTBVU7wA;eW@!v%Igh`-ebbA#Rzz&bmUQC=;xiJOD6>7Wi?hZm>}4=~Ur)>8@TgW0bpLS};J} zOM!U`*P}F9;IWjO_v(POAVkwVnbLQu$3AD1Eg7QxlWb`_VU|^0Fg7b_w-&M!QCSSI zd1~MP-Y1x8QSfY<D?;Z5#ya0!%p zvB?KnUx+HiK+qW!5M#A?Uup&;(XBjq; z02X%p!&Sq9n15UEW6^iO&UpEQkG9;fx#Vee%=74|00tlR2<*fE54-RoG`^5$q~e zX!&G>4cYKkw_eh473Q9X3koH~+ngaP{|(}&4P3u=LT1@NW+_Tgw_fm%=& zj)Px02ie00TMFx%2XenwsD_LImQ00pH}?c>F4%fWe#Y0KYI+nLz85A$1Q`qnD0xSP zPfb_g{mP-7p@oJShuR80ozoSWuB)fw%)&-Pjn^CU$;-?b_;^lNfSRr@#ao1JW)5RB zHS7YVXPRL7@zGNjez=lVpj1jp-_ll5eo-&!E3c%lltbk7NcbX01Qm=`<;Dve$ch1% zcDg^&3I}uGg>?1Y_`%-MZ<#87`(T&EK_AMx>yvBn!!sgt;AmE&1$NJZhb9C8crd_# zaD4tKU)1Cpjaoh){&4re9Fj_b>D1q<;e|q|93`u^R2#gSIWX6~(qKU3s@Zy8)THtG zML2tsI7kYqK&1_x})2z*9AqQP{k+zmS~iSrVd zqDkzcOd=R>W4b7h81mI#IKXtO&I|8UiXy0AHK+k(LA;11UNgD(2+1)4Myqozu(uNa zULy(+gi?1!i)VwLNa-1MKjy(`Rn;a65PV(>2WsI`tK92Pje@)c@!V-%!tGCTDYJ1^ z5lna0dqs~bY7y_nEl}MCE$t#BUW2zQOJTH0cz0t=5N(D@5yFS%w2Noxopw=rUU`qg dNB}by^?%KDnDJJmj1>R?002ovPDHLkV1o6|4$}Yt diff --git a/public/index.html b/public/index.html index 8f8b172..025f97f 100644 --- a/public/index.html +++ b/public/index.html @@ -1,28 +1,37 @@ JSON Server - +
- - -

- Congrats! You're successfully running JSON Server. +

+


- -

Resources

+ +

+ + Congrats! You're successfully running JSON Server. + +

+ +
+ +

Routes

+

+ Here are the resources that JSON Server has loaded: +

    loading, please wait...

- To view database current state: + You can view database current state at any time:

  • db @@ -30,26 +39,19 @@

-

Requests

- Resources can be accessed in various ways. -

-

- JSON Server supports: -

    -
  • GET, POST, PUT, PATCH, DESTROY and OPTIONS verbs.
  • -
  • JSONP or CORS cross domain requests.
  • -
+ You can use GET, POST, PUT... and access your resources from anywhere + using CORS and JSONP.

-

Documentation

+

Documentation

View README on GitHub.

-

Issues

+

Issues

Please go here.

diff --git a/public/stylesheets/style.css b/public/stylesheets/style.css index 1464e6c..e46e5a1 100644 --- a/public/stylesheets/style.css +++ b/public/stylesheets/style.css @@ -1,43 +1,12 @@ -body { - padding: 50px; - font: 14px "Lucida Grande", Helvetica, Arial, sans-serif; +img { + padding-top: 50px; + padding-bottom: 20px; } -h1 { - font-weight: lighter; +li { + list-style-type: square; } -a { - /*color: #00B7FF;*/ - color: #3D72D1; -} - -hr { - border: 1px #EEE solid; -} - -a.logo { - display: block; - text-align: center; - margin-bottom: 50px; -} - -.alert-success { - font-size: 18px; -} - -/*.logo { - background: #00B7FF; - color: white; - font-size: 48px; - width: 100px; - height: 100px; - text-align: center; - vertical-align: middle; - line-height: 90px; - border-radius: 100px; - transform:rotate(90deg); - -ms-transform:rotate(90deg); - -webkit-transform:rotate(90deg); - margin-bottom: 50px; -}*/ \ No newline at end of file +h4 { + padding-top: 20px; +} \ No newline at end of file diff --git a/src/command.js b/src/command.js index b61e0cf..25d8573 100644 --- a/src/command.js +++ b/src/command.js @@ -1,40 +1,40 @@ -var yargv = require('yargs') + +var chalk = require('chalk') var request = require('request') var low = require('low') var server = require('./server') -function show() { - for (var prop in low.db) { - console.log(server.url + '/' + prop) - } +function hello() { + console.log( + chalk.green('\n{^ ^} Heya!\n') + ) } function start(port) { - server.listen(port, function() { - console.log('%s listening at %s', server.name, server.url); - }) + for (var prop in low.db) { + console.log('http://localhost:' + port + '/' + chalk.green(prop)) + } + + server.listen(port) } -function run() { - var argv = yargv.usage('Usage: $0 ') - .demand(1) - .default('port', 3000) - .argv +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); - show() - start() + start(argv.port) } if (/\.js$/.test(source)) { var path = process.cwd() + '/' + source low.db = require(path).run(); - show() start(argv.port) } @@ -47,10 +47,12 @@ function run() { low.db = JSON.parse(res.text) } }) - show() - start() + 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') diff --git a/src/server.js b/src/server.js index f8b454f..530900f 100644 --- a/src/server.js +++ b/src/server.js @@ -1,33 +1,31 @@ -var _ = require('underscore') -var low = require('low') -var ecstatic = require('ecstatic') -var restify = require('restify') -var utils = require('./utils') +var express = require('express') +var http = require('http') +var path = require('path') +var _ = require('underscore') +var low = require('low') +var utils = require('./utils') low._.createId = utils.createId -var server = restify.createServer({ - name: 'JSON Server' -}) +var server = express() -server.use(restify.acceptParser(server.acceptable)) -server.use(restify.queryParser()) -server.use(restify.bodyParser()) -server.use(restify.CORS()) -server.use(restify.jsonp()) -server.use(restify.gzipResponse()) +server.set('port', process.env.PORT || 3000) +server.use(express.logger('dev')) +server.use(express.json()) +server.use(express.urlencoded()) +server.use(express.methodOverride()) +server.use(express.static(path.join(__dirname, '../public'))) +server.use(server.router) + +if ('development' == server.get('env')) { + server.use(express.errorHandler()); +} routes = {} -server.get(/^\/$|.*(.css)/, restify.serveStatic({ - 'directory': __dirname + '/../public', - 'default': 'index.html' -})) - // GET /db routes.db = function(req, res, next) { - res.send(low.db) - next() + res.jsonp(low.db) } // GET /:resource?attr=&attr= @@ -46,8 +44,7 @@ routes.list = function(req, res, next) { query = low(req.params.resource).where(properties) } - res.send(query.value()) - next() + res.jsonp(query.value()) } // GET /:parent/:parentId/:resource @@ -63,8 +60,7 @@ routes.nestedList = function(req, res, next) { .where(properties) .value() - res.send(resource) - next() + res.jsonp(resource) } // GET /:resource/:id @@ -73,8 +69,7 @@ routes.show = function(req, res, next) { .get(+req.params.id) .value() - res.send(resource) - next() + res.jsonp(resource) } // POST /:resource @@ -83,8 +78,7 @@ routes.create = function(req, res, next) { .insert(req.body) .value() - res.send(resource) - next() + res.jsonp(resource) } // PUT /:resource/:id @@ -94,21 +88,15 @@ routes.update = function(req, res, next) { .update(+req.params.id, req.body) .value() - res.send(resource) - next() + res.jsonp(resource) } // DELETE /:resource/:id routes.destroy = function(req, res, next) { - try { - low(req.params.resource).remove(+req.params.id) - utils.clean() + low(req.params.resource).remove(+req.params.id) + utils.clean() - res.send(204) - next() - } catch(e) { - console.trace(e) - } + res.send(204) } server.get('/db', routes.db) From a72900c658f2de7a32971ec8d8bb94a4e1c3c7f0 Mon Sep 17 00:00:00 2001 From: typicode Date: Thu, 20 Mar 2014 11:21:38 +0100 Subject: [PATCH 04/15] Fix --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index effa268..455d54b 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,7 @@ "superagent": "~0.15.7", "underscore": "~1.5.2", "underscore.inflections": "~0.2.1", - "low": "^0.5.0", + "low": "^0.4.2", "yargs": "^1.2.1", "chalk": "^0.4.0" }, From ae8b5dbc08033a26963c51b1b798adda18109ea9 Mon Sep 17 00:00:00 2001 From: typicode Date: Thu, 20 Mar 2014 15:15:54 +0100 Subject: [PATCH 05/15] Update .travis.yml --- .travis.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index df63076..6e5919d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,4 +1,3 @@ language: node_js node_js: - "0.10" - - "0.8" From 4c09c06501d9209ea5e4eb9ebb1a91b1711052c3 Mon Sep 17 00:00:00 2001 From: Typicode Date: Fri, 21 Mar 2014 13:41:28 +0100 Subject: [PATCH 06/15] Add 0.11 to travis --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index 6e5919d..18ae2d8 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,3 +1,4 @@ language: node_js node_js: + - "0.11" - "0.10" From 1092fe81e38b087c015560df798c5a802d83b6c3 Mon Sep 17 00:00:00 2001 From: Typicode Date: Fri, 21 Mar 2014 13:59:10 +0100 Subject: [PATCH 07/15] Add logo --- public/images/json.png | Bin 0 -> 278 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 public/images/json.png diff --git a/public/images/json.png b/public/images/json.png new file mode 100644 index 0000000000000000000000000000000000000000..57176594d16cfa49fc5c9900d567deaa24ba2c21 GIT binary patch literal 278 zcmeAS@N?(olHy`uVBq!ia0vp^4nSFdgVk6Dz*+>&Yj?OQ;h#hxyXAsP4H-b&)htPbq0KF}edXEjqY4XE+?8ph3M>efacEALll zV(1G0ve#k$!Mnddy()J9Z>z*`K Date: Fri, 21 Mar 2014 14:00:12 +0100 Subject: [PATCH 08/15] Clean --- README.md | 2 +- test/fixture.js | 18 ------------------ test/server.js | 44 ++++++++++++++++++++++++++++++++++++++------ test/static.js | 25 ------------------------- 4 files changed, 39 insertions(+), 50 deletions(-) delete mode 100644 test/fixture.js delete mode 100644 test/static.js diff --git a/README.md b/README.md index 0faddb2..cdf3a55 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ [![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) -![](http://i.imgur.com/dLeJmw6.png) +![](https://raw.githubusercontent.com/typicode/json-server/master/public/images/logo.png) # JSON Server diff --git a/test/fixture.js b/test/fixture.js deleted file mode 100644 index 453be2c..0000000 --- a/test/fixture.js +++ /dev/null @@ -1,18 +0,0 @@ -// Small database to be used during tests -module.exports = function() { - var db = {}; - - db.posts = [ - {id: 1, body: 'foo'}, - {id: 2, body: 'bar'} - ] - - db.comments = [ - {id: 1, published: true, postId: 1}, - {id: 2, published: false, postId: 1}, - {id: 3, published: false, postId: 2}, - {id: 4, published: false, postId: 2}, - ] - - return db; -} \ No newline at end of file diff --git a/test/server.js b/test/server.js index 28b16ad..2a6100c 100644 --- a/test/server.js +++ b/test/server.js @@ -1,15 +1,25 @@ var request = require('supertest') -var assert = require('assert') -var low = require('low') -var server = require('../src/server') -var fixture = require('./fixture') +var assert = require('assert') +var low = require('low') +var server = require('../src/server') var db describe('Server', function() { beforeEach(function() { - low.db = fixture() - server + low.db = {} + + low.db.posts = [ + {id: 1, body: 'foo'}, + {id: 2, body: 'bar'} + ] + + low.db.comments = [ + {id: 1, published: true, postId: 1}, + {id: 2, published: false, postId: 1}, + {id: 3, published: false, postId: 2}, + {id: 4, published: false, postId: 2}, + ] }) describe('GET /db', function() { @@ -126,4 +136,26 @@ describe('Server', function() { }) }) }) + + describe('Static routes', function() { + + describe('GET /', function() { + it('should respond with html', function(done) { + request(server) + .get('/') + .expect('Content-Type', /html/) + .expect(200, done); + }); + }); + + describe('GET /stylesheets/style.css', function() { + it('should respond with css', function(done) { + request(server) + .get('/stylesheets/style.css') + .expect('Content-Type', /css/) + .expect(200, done); + }); + }); + + }) }) \ No newline at end of file diff --git a/test/static.js b/test/static.js deleted file mode 100644 index 88dcf93..0000000 --- a/test/static.js +++ /dev/null @@ -1,25 +0,0 @@ -var request = require('supertest') -var assert = require('assert') -var server = require('../src/server') - -describe('Static routes', function() { - - describe('GET /', function() { - it('should respond with html', function(done) { - request(server) - .get('/') - .expect('Content-Type', /html/) - .expect(200, done); - }); - }); - - describe('GET /stylesheets/style.css', function() { - it('should respond with css', function(done) { - request(server) - .get('/stylesheets/style.css') - .expect('Content-Type', /css/) - .expect(200, done); - }); - }); - -}); \ No newline at end of file From ca775b754492921d22e1e2f4ee011d6c186c0cbe Mon Sep 17 00:00:00 2001 From: Typicode Date: Fri, 21 Mar 2014 17:18:54 +0100 Subject: [PATCH 09/15] Update --- README.md | 64 ++++++++++------------ bin/index.js | 11 ++++ db.json | 20 +++++++ package.json | 8 +-- src/{command.js => cli.js} | 107 ++++++++++++++++++++++--------------- src/help.txt | 14 +++++ src/routes.js | 81 ++++++++++++++++++++++++++++ src/server.js | 102 +++++------------------------------ test/server.js | 10 ++++ 9 files changed, 245 insertions(+), 172 deletions(-) create mode 100644 bin/index.js create mode 100644 db.json rename src/{command.js => cli.js} (55%) create mode 100644 src/help.txt create mode 100644 src/routes.js 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) From e1715af4fd0dae132486af1d88c27f590e8c9a08 Mon Sep 17 00:00:00 2001 From: Typicode Date: Wed, 26 Mar 2014 14:00:28 +0100 Subject: [PATCH 10/15] Add local public directory --- src/server.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/server.js b/src/server.js index dcab263..eda759a 100644 --- a/src/server.js +++ b/src/server.js @@ -15,7 +15,13 @@ server.use(express.logger('dev')) server.use(express.json()) server.use(express.urlencoded()) server.use(express.methodOverride()) -server.use(express.static(path.join(__dirname, '../public'))) + +if (fs.existsSync(process.cwd() + '/public')) { + app.use(express.static(process.cwd() + '/public')); +} else { + app.use(express.static(path.join(__dirname, './public'))); +} + server.use(cors()) server.use(server.router) From e3bcd755f84efd9ceebc7a833b138ee0e52eaf04 Mon Sep 17 00:00:00 2001 From: Typicode Date: Wed, 26 Mar 2014 14:00:39 +0100 Subject: [PATCH 11/15] Update README.md --- README.md | 33 ++++++++++----------------------- 1 file changed, 10 insertions(+), 23 deletions(-) diff --git a/README.md b/README.md index 8f59400..992ffc6 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@

- +

# JSON Server @@ -15,11 +15,6 @@ Created with :heart: for front-end developers who need a flexible back-end for q ### Command line interface -```bash -$ json-server --file db.json -$ curl -i http://localhost:3000/posts/1 -``` - ```javascript // db.json { @@ -29,16 +24,21 @@ $ curl -i http://localhost:3000/posts/1 } ``` +```bash +$ json-server --file db.json +$ curl -i http://localhost:3000/posts/1 +``` + ### Node module ```javascript var server = require('json-server'); -server.low.db = { +server.setDB({ posts: [ { id: 1, body: 'foo' } ] -} +}); server.get('/another/route', function(req, res, next) { // ... @@ -150,6 +150,8 @@ PATCH /:resource/:id DEL /:resource/:id ``` +To slice resources, add `_start` and `_end` query parameters to route. + For routes usage information, have a look at [JSONPlaceholder](https://github.com/typicode/jsonplaceholder) code examples. ``` @@ -158,27 +160,12 @@ GET /db Returns database state. - ``` GET / ``` Returns default index file or content of ./public/index.html (useful if you need to set a custom home page). - -## Support - -If you like the project, please tell your friends about it, star it or give feedback :) It's very much appreciated! - -For project updates or to get in touch, [@typicode](http://twitter.com/typicode). You can also send me a mail. - -## Test - -```bash -$ npm install -$ npm test -``` - ## Articles * [Fast prototyping using Restangular and Json-server](http://bahmutov.calepin.co/fast-prototyping-using-restangular-and-json-server.html) From ef1cf9b2e61633e93ceaf01f17d271db9208dab8 Mon Sep 17 00:00:00 2001 From: Typicode Date: Wed, 26 Mar 2014 14:00:50 +0100 Subject: [PATCH 12/15] Bump --- package.json | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 16895de..d534ca5 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "json-server", - "version": "0.2.0", + "version": "0.3.0", "description": "Serves JSON files through REST routes.", "main": "./src/server.js", "bin": "./bin/index.js", @@ -44,7 +44,8 @@ "testing", "rest", "data", - "dummy" + "dummy", + "sandbox" ], "author": "Typicode ", "license": "MIT", From 84303f99ee810f69e0dc5c9c5b1ecec17aa9a2af Mon Sep 17 00:00:00 2001 From: Typicode Date: Wed, 26 Mar 2014 14:04:19 +0100 Subject: [PATCH 13/15] Update README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 992ffc6..5719e7c 100644 --- a/README.md +++ b/README.md @@ -34,11 +34,11 @@ $ curl -i http://localhost:3000/posts/1 ```javascript var server = require('json-server'); -server.setDB({ +server.low.db = { posts: [ { id: 1, body: 'foo' } ] -}); +} server.get('/another/route', function(req, res, next) { // ... From 7b5ca69ef15181b906f9cc46b6bee1350b51de1c Mon Sep 17 00:00:00 2001 From: Typicode Date: Wed, 26 Mar 2014 14:12:48 +0100 Subject: [PATCH 14/15] Fix server --- src/server.js | 5 +++-- test/server.js | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/server.js b/src/server.js index eda759a..c8ec484 100644 --- a/src/server.js +++ b/src/server.js @@ -1,3 +1,4 @@ +var fs = require('fs') var express = require('express') var cors = require('cors') var http = require('http') @@ -17,9 +18,9 @@ server.use(express.urlencoded()) server.use(express.methodOverride()) if (fs.existsSync(process.cwd() + '/public')) { - app.use(express.static(process.cwd() + '/public')); + server.use(express.static(process.cwd() + '/public')); } else { - app.use(express.static(path.join(__dirname, './public'))); + server.use(express.static(path.join(__dirname, './public'))); } server.use(cors()) diff --git a/test/server.js b/test/server.js index 7c7e373..56f462c 100644 --- a/test/server.js +++ b/test/server.js @@ -52,7 +52,7 @@ describe('Server', function() { }) }) - describe.only('GET /:resource?_start=&_end=', function() { + describe('GET /:resource?_start=&_end=', function() { it('should respond with sliced array', function(done) { request(server) .get('/comments?_start=1&_end=2') From a69f706fb64e1517f82defa63f3bcc43bde31464 Mon Sep 17 00:00:00 2001 From: typicode Date: Wed, 2 Apr 2014 04:39:17 +0200 Subject: [PATCH 15/15] Update package.json --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index d534ca5..4866fbd 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,7 @@ "superagent": "~0.15.7", "underscore": "~1.5.2", "underscore.inflections": "~0.2.1", - "low": "^0.4.2", + "lowdb": "^0.1.0", "yargs": "^1.2.1", "chalk": "^0.4.0", "minimist": "0.0.8",