Restify-Mongoose provides a resource abstraction for restify to expose mongoose models as REST resources.
First you'll need to install restify-mongoose via npm
npm install restify-mongoose
Second step is to wire up mongoose and restify using restify-mongoose
var restify = require('restify');
var restifyMongoose = require('restify-mongoose');
var mongoose = require('mongoose');
var server = restify.createServer({
name: 'restify.mongoose.examples.notes',
version: '1.0.0'
});
server.use(restify.plugins.acceptParser(server.acceptable));
server.use(restify.plugins.queryParser());
server.use(restify.plugins.bodyParser());
// Create a simple mongoose model 'Note'
var NoteSchema = new mongoose.Schema({
title : { type : String, required : true },
date : { type : Date, required : true },
tags : [String],
content : { type: String }
});
var Note = mongoose.model('notes', NoteSchema);
// Now create a restify-mongoose resource from 'Note' mongoose model
var notes = restifyMongoose(Note);
// Serve resource notes with fine grained mapping control
server.get('/notes', notes.query());
server.get('/notes/:id', notes.detail());
server.post('/notes', notes.insert());
server.patch('/notes/:id', notes.update());
server.del('/notes/:id', notes.remove());
server.listen(3000, function () {
console.log('%s listening at %s', server.name, server.url);
});
To map resources or resource functionality to restify REST endpoints/routes restify-mongoose offers two approaches:
- 'fine grained mapping' control via list, detail, new, update and delete functions
- 'quick mapping' via serve method
Fine grained mapping
In the above getting started example we used fine grained mapping control. Restify-mongoose defines the functions query
,
detail
, insert
, update
and remove
that return restify route handlers and can be used like this:
// Serve resource notes with fine grained mapping control
server.get('/notes', notes.query());
server.get('/notes/:id', notes.detail());
server.post('/notes', notes.insert());
server.patch('/notes/:id', notes.update());
server.del('/notes/:id', notes.remove());
For every ´id´ dependent function the restify route has to define a :id
placeholder to allow restify-mongoose to access
id parameters. Id dependent functions are detail
, update
and delete
.
Query String
Setting a queryString
will make restify-mongoose use the string as the field name to conduct its searches in the detail
update
& remove
functions.
If not set it will use the default behavior of using mongos _id
field.
// Now create a restify-mongoose resource from 'Note' mongoose model and set queryString to 'myField'
var notes = restifyMongoose(Note, {queryString: 'myField'});
// these functions will now conduct searches with the field 'myField'. (defaults to '_id')
server.get('/notes/:id', notes.detail());
server.patch('/notes/:id', notes.update());
server.del('/notes/:id', notes.remove());
Quick mapping
// Serve resource notes with quick mapping
restifyMongoose(models.Note).serve('/api/notes', server);
Maps urls
- GET '/api/notes' to
query
function - GET '/api/notes/:id' to
detail
function - POST '/api/notes' to
insert
function - DELETE '/api/notes/:id' to
remove
function - PATCH '/api/notes/:id' to
update
function
You can also pass an options object to the serve
method to attach handlers before and after the request.
For example, to use restify-jwt:
// Serve resource notes with quick mapping with JWT auth
restifyMongoose(models.Note).serve('/api/notes', server, { before: jwt({secret: 'some-secret'}) } );
Query parameters are passed by query string parameter q.
Query parameters are parsed as JSON objects and passed to mongoose where query function.
To filter a notes resource by title to match term "first" append the q query parameter to the URL:
http://localhost:3000/notes?q={"title":"first"}
Requests that return multiple items in query
will be paginated to 100 items by default. You can set the pageSize
(number min=1) by adding it to the options.
var options = {
pageSize: 2
};
var notes = restifyMongoose(Note, options);
or as query string parameter pageSize
(which will have the presedence)
http://localhost:3000/notes?pageSize=2
You can specify further pages with the p parameter and a page number.
http://localhost:3000/notes?p=1
An additional restriction to page sizes can be made with maxPageSize
option (default value is 100) that defines the
maximum allowed page size to avoid unbound queries.
The pagination info is included in the Link header. It is important to follow these Link header values instead of constructing your own URLs.
link:
<http://example.com/notes?p=0>; rel="first",
<http://example.com/notes?p=1>; rel="prev",
<http://example.com/notes/?p=3>; rel="next",
<http://example.com/notes/?p=4>; rel="last"
Linebreak is included for readability.
You can set the baseUrl
by adding it to the options.
var options = {
baseUrl: 'http://example.com'
};
The possible rel
values are:
- next - Shows the URL of the immediate next page of results.
- last - Shows the URL of the last page of results.
- first - Shows the URL of the first page of results.
- prev - Shows the URL of the immediate previous page of results.
The total number of results/resources returned in query
is sent in the X-Total-Count Header
and is not affected by
pagination (setting pageSize
and p parameter). It does take in account filter and query parameter ( q ).
Sort parameters are passed by query string parameter sort.
Sort parameters can be separated by comma or space. They will be passed directly to mongoose sort query function.
To sort a notes resource by title descending append the sort query parameter to the URL:
http://localhost:3000/notes?sort=-title
You can also define a default sort in the options object. This option will by ignored if a sort query parameter exists.
Using in the constructor:
var notes = restifyMongoose(Note, {sort: '-title'});
notes.serve('/notes', restifyServer);
Using for query or detail methods:
var notes = restifyMongoose(Note);
note.query({sort: '-title'});
## Select Fields
To restrict selected columns you can pass a query string parameter __select__.
Select fields can be separated by comma or space. They will be passed to [mongoose select query function](http://mongoosejs.com/docs/api.html#query_Query-select).
To select only title and date the fields of a notes resource append the __select__ query parameter to the URL:
http://localhost:3000/notes?select=title,date
You can also define select fields in the options object. This will make the the __select__ query parameter be ignored.
Using in the constructor:
```javascript
var notes = restifyMongoose(Note, {select: 'title'});
notes.serve('/notes', restifyServer);
Using for query or detail methods:
var notes = restifyMongoose(Note);
note.detail({select: 'title,date,tags'});
note.query({select: 'title date'});
Results can be filtered with a function, which is set in the options object of the constructor or on the query
and detail
function.
The function takes two parameters: the request object and the response object. The return value of the function is a query that is passed directly to the mongoose where query function.
For instance, you can use a filter to display only results for a particular user:
var filterUser = function(req, res) {
return {user: req.user};
}
var notes = restifyMongoose(Note, {filter: filterUser});
A projection is a function, used by the query
and detail
operations, which takes the request object, the result model, and a callback. This function should invoke the callback exactly once. This callback takes an error and a model item as it's two parameters. Use null
for the error is there is no error.
For instance, the default detail and list projections are as follows:
function (req, item, cb) {
cb(null, item);
};
A projection is useful if you need to manipulate the result item before returning it in the response. For instance, you may not want to return the passwordHash for a User data model.
// If this is the schema
var UserSchema = new Schema({
username: String,
email: String,
passwordHash: String
});
// This is a projection translating _id to id and not including passwordHash
var userProjection = function(req, item, cb) {
var user = {
id: item._id,
username: item.username,
email: item.email
};
cb(null, user);
};
Projection functions are specified in the options for the resitfy-mongoose contructor, the query function, or the detail function.
For the construtor, the options are listProjection
and detailProjection
var users = restifyMongoose(User, {listProjection: userProjection, detailProjection: userProjection});
users.serve('/users', restifyServer);
For both query and detail, the option is projection
var users = restifyMongoose(User);
users.detail({projection: userProjection});
users.query({projection: userProjection});
The output format can be changed to a more compatible one with the json-api standard to use the API with frameworks like Ember.
var users = restifyMongoose(User, {outputFormat: 'json-api'});
users.serve('/users', restifyServer);
``
Also you can specify a custom model name like this:
```javascript
var users = restifyMongoose(User, {outputFormat: 'json-api', modelName: 'admins'});
users.serve('/users', restifyServer);
The returned results can use mongoose's "populate" query modifier to populated referenced documents within models.
Referenced documents can be populated in three ways:
Adding populate=[referenced_field]
to the query string will populate the referenced_field
, if it exists.
// e.g.
var notes = restifyMongoose(Note, {populate: 'author'});
// e.g.
server.get('/notes', notes.query({populate: 'author'}))
server.get('/notes/:id', notes.detail({populate: 'author'}))
Multiple referenced documents can be populated by using a comma-delimited list of the desired fields in any of the three methods above.
// e.g.
var notes = restifyMongoose(Note, {populate: 'author,contributors'});
Contribution welcome! Read the contribution guideline first.