Examples

Hello World Quick Start:

A Simple CRUD Example

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/"
    }
  }
}

Express example

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);
            

Percolator example

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);
});