Skip to content

Instantly share code, notes, and snippets.

@foxbunny
Last active December 21, 2015 05:39
Show Gist options
  • Save foxbunny/6258907 to your computer and use it in GitHub Desktop.
Save foxbunny/6258907 to your computer and use it in GitHub Desktop.
Highly customizable helper class for generating ExpressJS routes that provide a REST interface for Mongoose models
###!
Router
======
@author Branko Vukelic <[email protected]>
@license MIT
@version 0.0.1
###
###
Router is a highly customizable helper class for generating ExpressJS routes
that provide a REST interface for Mongoose models. Its aim is to be
compatible with BackboneJS, but it doesn't do anything Backbone-specific.
Basic usage
-----------
To create a new router object, simply call the constructor passing it the
model constructor and the base URL at which it shoudl be routed. Note that
there is no support for nested resources at this moment.
Here's a simple example:
Book = require('./models/book'); # model
Router = require('./lib/router');
bookRouter = Router(Book, 'books'); # slashes are automatically appended
Now, call the router's `route` method, and pass it the express app instance:
bookRouter.route(app);
This should create a following set of routes:
GET /books/ # get all books
POST /books/ # create a new book
GET /books/:id # get one book with specified id
PUT /books/:id # update a book with specified id
DELETE /books/:id # delete a book with specified id
Custom id field
---------------
The `:id` parameter can be any key on the model. By default, it's the `_id`
key. You can override this by passing the name of the field you want to use as
the third parameter to the constructor. Not that this doesn't change the
route signatures.
bookRouter = Router(Book, 'books', 'slug');
The above router uses the 'slug' field as the `:id` parameter.
Middleware
----------
You can pass in an array of middleware functions to the `Router` constructor.
The middlewares are then used on all routes. See the section on advanced
customizaton for information on how to specify different middleware per
route.
bookRouter = Router(Book, 'books', 'slug', [authRequired]);
Advanced customization
----------------------
The Router constructor is organized in a way that allows you to easily change
its behavior either partially or completely. This is accomplished by handling
different aspects of route handling into separate methods that you can
overload in your instances.
Here is a simple example that changes the way lists of documents are returned.
bookRouter = Router(Book, 'books')
bookRouter.getAll = function(cb) {
this.Model.where('year').gt(2008).exec(cb);
};
Look in source code for other ways to overload the methods and customize the
router.
###
class Router
###
Router(Model, baseUrl, [idField, middleware])
---------------------------------------------
@param Model {Function} Mongoose model constructor
@param baseUrl {String} Base URL of the routes
@param idField {String} Model key to use as ID in routes
@param middlewares {Array} Array of middleware functions to use on routes
###
constructor: (@Model, @baseUrl, @idField='_id', @middleware=[]) ->
@baseUrl = @ensureSlash @baseUrl
@baseItemUrl = @baseUrl + ':id'
NOT_FOUND: 404
BAD_REQUEST: 400
OK: 200
###
Router.prototype.ensureSlash(path)
----------------------------------
Make sure the path has both leading and trailing slash.
@param path {String} Path
###
ensureSlash: (path) ->
if path[0] isnt '/'
path = "/#{path}"
if path[path.length - 1] isnt '/'
path = "#{path}/"
path
###
Router.prototype.getAll(cb)
---------------------------
Fetch all objects in the list route handler and call a callback.
@param cb {Function} Callback function
###
getAll: (cb) ->
@Model.find {}, cb
###
Router.prototype.getOne(cb)
---------------------------
Fetch all objects in the list route handler and call a callback.
@param cb {Function} Callback function
###
getOne: (id, cb) ->
@Model.findOne {id: id}, cb
###
Router.prototype.processError(err)
----------------------------------
Process the error object returned from Mongoose.
This method just returns the error object that is passed to it by default.
It exists so developers can overload it and customize its behavior.
@param err {Object} Error object returned by Mongoose
###
processError: (err) ->
err
###
Router.prototype.getDocument(req, res, next)
--------------------------------------------
This is amiddleware that fetches a single document from the database and
attaches it to request object as `doc` attribute.
By default, this method will not handle any errors, and it will return a
HTTP 404 if the document with specified ID doesn't exist.
@param req {Object} Request object
@param res {Object} Response object
@param next {Function} Callback function
###
getDocument: (req, res, next) =>
# Build query object manually since there's no better way
query = {}
query[@idField] = req.params.id
# Find the object and either return response or call the callback
@Model.findOne query, (err, doc) =>
return next(err) if err
return res.json @NOT_FOUND, {err: 'Not found'} if not doc
req.doc = doc
next()
###
Router.prototype.getListMiddleware()
------------------------------------
Returns a set of middleware for the list route.
@return {Array} Middleware for the route
###
getListMiddleware: () ->
@middleware
###
Router.prototype.getCreateMiddleware()
--------------------------------------
Returns a set of middleware for the create route.
@return {Array} Middleware for the route
###
getCreateMiddleware: () ->
@middleware
###
Router.prototype.getShowMiddleware()
------------------------------------
Returns a set of middleware for the show route.
@return {Array} Middleware for the route
###
getShowMiddleware: () ->
@middleware.concat @getDocument
###
Router.prototype.getUpdateMiddleware()
--------------------------------------
Returns a set of middleware for the update route.
@return {Array} Middleware for the route
###
getUpdateMiddleware: () ->
@middleware.concat @getDocument
###
Router.prototype.getDeleteMiddleware()
--------------------------------------
Returns a set of middleware for the delete route.
@return {Array} Middleware for the route
###
getDeleteMiddleware: () ->
@middleware.concat @getDocument
###
Router.prototype.list(req, res, next)
-------------------------------------
Handler for the list route:
GET /collection/
@param req {Object} Request object
@param res {Object} Response object
@param next {Function} Callback function
###
list: (req, res, next) =>
@getAll (err, docs=[]) =>
return next(err) if err
res.json @OK, docs
###
Router.prototype.create(req, res, next)
---------------------------------------
Handler for the create route:
POST /collection/
@param req {Object} Request object
@param res {Object} Response object
@param next {Function} Callback function
###
create: (req, res, next) =>
@Model.create req.body, (err, doc) =>
return res.json @BAD_REQUEST, {err: @processError err} if err
res.json @OK, doc
###
Router.prototype.show(req, res, next)
-------------------------------------
Handler for the show route:
GET /collection/:id
@param req {Object} Request object
@param res {Object} Response object
@param next {Function} Callback function
###
show: (req, res, next) =>
res.json @OK, req.doc
###
Router.prototype.update(req, res, next)
---------------------------------------
Handler for the update route:
PUT /collection/:id
@param req {Object} Request object
@param res {Object} Response object
@param next {Function} Callback function
###
update: (req, res, next) =>
req.doc.set req.body
req.doc.save (err, doc) =>
if err
return res.json @BAD_REQUEST, {err: @processError err}
res.json @OK, req.doc
###
Router.prototype.del(req, res, next)
------------------------------------
Handler for the delete route:
DELETE /collection/:id
@param req {Object} Request object
@param res {Object} Response object
@param next {Function} Callback function
###
del: (req, res, next) =>
req.doc.remove (err) =>
return next(err) if err
res.json @OK, {id: req.params.id}
###
Router.prototype.route(app)
---------------------------
Set up routing for a given app.
@param app {Object} Express application instance
@return {Object} Same application instance returned for chaining
###
route: (app) ->
# GET /base/ - list
app.get @baseUrl, @getListMiddleware(), @list
# POST /base/ - create
app.post @baseUrl, @getCreateMiddleware(), @create
# GET /base/:id - show
app.get @baseItemUrl, @getShowMiddleware(), @show
# PUT /base/:id - update
app.put @baseItemUrl, @getUpdateMiddleware(), @update
# DELETE /base/:id - delete
app.del @baseItemUrl, @getDeleteMiddleware(), @del
app
module.exports = Router
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment