server.js
in your project directory, and copy this code below into it:var Percolator = require('percolator').Percolator; var server = new Percolator(); server.route('/', { GET : function(req, res){ res.object({message : 'Hello World!'}).send(); }}); server.listen(function(err){ console.log('server is listening on port ', server.port); });
Run the server:
node server.js
See your "Hello World" output at http://localhost:3000/ and be completely floored by the greatest API of all time. Or not.
The output json look like this:{ message: "Hello World!", _links: { self: { href: "http://localhost:3000/" } } }You'll notice that Percolator automatically adds a link to the document itself. This is because it tries to help you create a surfable "Hypermedia" API where every endpoint is accessible from a link in another endpoint, and where everything that can be done with the API is described in the API itself.
The following is a CRUD (Create-Read-Update-Delete) example using both Percolator and Express for comparison's sake. This example shows a collection of 'links' as part of a bookmarking system that keeps urls and tags that might describe them.
With either example, the collection at /links will look like this:
{ "_items": { "12341234": { "id": "12341234", "url": "http://percolatorjs.com/", "tags": [ "rest", "json", "api", "framework" ], "_links": { "self": { "href": "http://localhost:3000/links/12341234" } } } }, "_links": { "create": { "href": "http://localhost:3000/links", "method": "POST", "schema": { "description": "A notable url to be saved and optionally tagged.", "type": "object", "properties": { "url": { "title": "url", "type": "string", "required": true }, "tags": { "title": "tags that can be applied to this url", "type": "array", "required": false } } } }, "parent": { "href": "http://localhost:3000/" } } }
With either example, an individual member of the collection at /links/{id} will look like this:
{ "id": "12341234", "url": "http://percolatorjs.com/", "tags": [ "rest", "json", "api", "framework" ], "_links": { "parent": { "href": "http://localhost:3000/links/" } } }
var SomeDB = require('./SomeDB'); var express = require('express'); var app = express(); app.use(express.bodyParser()); var db = new SomeDB(); app.set('db', db); app.set('port', port); var port = 3000; var schema = { description : "A notable url to be saved and optionally tagged.", type : "object", properties : { url : { title : "url", type : "string", required : true }, tags : { title : "tags that can be applied to this url", type : "array", required : false } } }; function absoluteUrl(req, relativeUrl){ return 'http://' + req.host + ':' + port + relativeUrl.replace(/\/\//, '/'); } function internalServerError(req, res, err){ res.status(500); return res.send({ "error": { "type": 500, "message": "Internal Server Error", "detail": err } }); } function badRequestError(req, res, err){ res.status(400); return res.send({ "error" : { "type":400, "message":"Bad Request", "detail": err } }); } function methodNotSupportedError(req, res){ res.status(405); return res.send({ "error": { "type": 405, "message": "Method Not Supported", "detail": req.method } }); } function notFoundError(req, res){ res.status(404); return res.send({ "error": { "type": 404, "message": "Not Found", "detail": req.url } }); } app.get('/links', function(req, res){ db.find(function(err, objects){ if (err){ return internalServerError(req, res, err); } var collection = { _items : objects, _links : { create : { href : absoluteUrl(req, req.url), method : 'POST', schema : schema }, parent : { href : absoluteUrl(req, req.url.replace(/\/[^\/]+\/?$/, '/')) } } }; for(var index in collection._items){ var item = collection._items[index]; item._links = { self : { href : absoluteUrl(req, req.url + '/' + item.id) } }; } res.send(collection); }); }); app.get('/links/:id', function(req, res){ db.findById(req.param('id'), function(err, foundObject){ if (err){ if (err === 'Not Found'){ return notFoundError(req, res); } return internalServerError(req, res, err); } foundObject._links = { parent : { href : absoluteUrl(req, req.url.replace(/\/[^\/]+\/?$/, '/')) } }; res.send(foundObject); }); }); app.del('/links/:id', function(req, res){ db.findById(req.param('id'), function(err, foundObject){ if (err){ if (err === 'Not Found'){ return notFoundError(req, res); } return internalServerError(req, res, err); } db.remove(id, function(err){ if (err){ return internalServerError(req, res, err); } res.status(204); }); }); }); app.post('/links', function(req, res){ if (!req.body.url || !req.body.tags){ return badRequestError(req, res, 'json failed schema validation.'); } db.insert(req.body, function(err, id){ if (err){ return internalServerError(err); } res.set('Location', (req.url + '/' + id).replace(/\/\//, '')); res.status(201); res.end(); }); }); app.put('/links/:id', function(req, res){ db.findById(req.param('id'), function(err, foundObject){ if (err){ if (err === 'Not Found'){ return notFoundError(req, res); } return internalServerError(req, res, err); } if (!req.body.url || !req.body.tags || !req.body.id){ return badRequestError(req, res, 'json failed schema validation.'); } db.update(req.body.id, req.body, function(err){ if (err){ return internalServerError(err); } res.send(''); }); }); }); app.del('/links', methodNotSupportedError); app.put('/links/', methodNotSupportedError); app.post('/links/:id', methodNotSupportedError); app.options('/links', function(req,res){ res.set('Allowed', 'OPTIONS,HEAD,GET,POST'); res.send({"allowed methods":["OPTIONS","HEAD","GET","POST"]}); }); app.options('/links/:id', function(req,res){ res.set('Allowed', 'OPTIONS,HEAD,GET,PUT,DELETE'); res.send({"allowed methods":["OPTIONS","HEAD","GET","PUT","DELETE"]}); }); app.listen(port); console.log('Listening on port ', port);
var Percolator = require('percolator').Percolator; var CRUDCollection = require('percolator').CRUDCollection; var SomeDB = require('./SomeDB'); var db = new SomeDB(); var schema = { description : "A notable url to be saved and optionally tagged.", type : "object", properties : { url : { title : "url", type : "string", required : true }, tags : { title : "tags that can be applied to this url", type : "array", required : false } } }; var linkCollection = new CRUDCollection({ schema : schema, create : function(req, res, obj, cb){ db.insert(obj, function(err, id){ if (err){ return res.status.internalServerError(err); } cb(); }); }, update : function(req, res, id, obj, cb){ db.update(id, obj, function(err){ if (err){ return res.status.internalServerError(err); } cb(); }); }, destroy : function(req, res, id, cb){ db.remove(id, function(err){ if (err){ return res.status.internalServerError(err); } cb(); }); }, list : function(req, res, cb){ db.find(function(err, objects){ return cb(err, objects); }); }, fetch : function(req, res, cb){ db.findById(req.uri.child(), function(err, foundObject){ if (err){ if (err === 'Not Found'){ return cb(true); } return res.status.internalServerError(err); } cb(null, foundObject); }); } }); var app = { port : 3001 }; var server = new Percolator(app); server.route('/links', linkCollection.handler); server.route('/links/:id', linkCollection.wildcard); server.listen(function(){ console.log(server.server.router.routes); console.log('Listening on port ', app.port); });