Coder Social home page Coder Social logo

saintedlama / restify-mongoose Goto Github PK

View Code? Open in Web Editor NEW
110.0 8.0 34.0 1.71 MB

Restify-Mongoose provides a resource abstraction to expose mongoose models as REST resources.

License: BSD 2-Clause "Simplified" License

JavaScript 100.00%
restify mongoose javascript rest-api

restify-mongoose's Introduction

Restify-Mongoose

NPM

Build Status Coverage Status Dependencies Status devDependency Status

Restify-Mongoose provides a resource abstraction for restify to expose mongoose models as REST resources.

Getting started

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

Resources

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

Queries

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

Paginate

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.

Link Header

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.

Total Count Header

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

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

Filter

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

Projection

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

Output format

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

Populating referenced documents

The returned results can use mongoose's "populate" query modifier to populated referenced documents within models.

Referenced documents can be populated in three ways:

query parameter

Adding populate=[referenced_field] to the query string will populate the referenced_field, if it exists.

Resource option

// e.g.
var notes = restifyMongoose(Note, {populate: 'author'});

query / detail method options

// e.g.
server.get('/notes', notes.query({populate: 'author'}))
server.get('/notes/:id', notes.detail({populate: 'author'}))

Populating multiple fields

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

Contribute

Contribution welcome! Read the contribution guideline first.

restify-mongoose's People

Contributors

christophwalcher avatar djensen47 avatar edsadr avatar ismarslomic avatar jmwohl avatar kolbma avatar mancvso avatar mpareja avatar neamar avatar saintedlama avatar tiago-marques avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

restify-mongoose's Issues

remove parseCommaParam function in populate

query = query.populate(parseCommaParam(populate));

My suggestion is to remove the parseCommaParam function because the benefit is greater to use the population function like populate({ path: 'author', select: 'name' })...

Band.
  find({}).
  populate({ path: 'members', select: 'name' }).
  exec(function(error, bands) {
    // Won't work, foreign field `band` is not selected in the projection
  });

Source https://mongoosejs.com/docs/populate.html

Allow transformation for insert & update [Discussion]

This is what I'm working on now. Actually, it's done, I just need to write the tests.

Resource.prototype.insert = function (transform) {
  var self = this;
  var emitInsert = emitEvent(self, 'insert');

  return function(req, res, next) {
    if (transform && typeof transform === 'function') {
      transform(req, res);  
    }
    self.Model.create(req.body, function (err, model) {
      if (err) {
        return onError(err, next);
      }

      res.header('Location', req.url + '/' + model._id);
      res.send(200, model);

      emitInsert(model, next);
    });
  };
};

The idea is that sometimes on an insert or update, you need to modify the data that will be saved. Specifically, an authenticated user POSTing a new note shouldn't have to pass in their own id if we already have it on every request. Thus the transform function.

I wanted to run this by you before I finished the tests and submitted a pull request.

self.options.filter is wrong

Whenever occurs the "self.options.filter" it should be changed to "options.filter". If a pull request is needed I'd be glad to suggest it. Please, let me know and thanks for your appreciated work.

NPM Release of v0.2.6

@saintedlama Can you please do NPM release of v0.2.6. I have done all preparation in GitHub, but I dont have access to the NPM package to do the release in NPM.

sort and select as options in query configurations

is there any reason why sort and select are only available as part of a request get parameters and cannot be instantiated on the options object initialising the query method?
Could behave just as populate does

Chaining [Discussion]

Like Express, Restify allows chaining on path definitions. It would be great if I could pass a chain of callback functions to the quick mapping.

The use case I have in mind is checking authorization on requests.

I use a plugin handler to do the initial authentication handling. Since not all APIs need to be protected, I have a callback function that I use to "protect" a particular path. The path ends up looking like this.

  var opts = {
    filter: function userFilter(req, res) {
      return {user: req.user.userId};
    }
  }

  var notes = restifyMongoose(Rule, opts);
  server.get('/notes/:id', authenticate, notes.detail());
  server.get('/rules', authenticate, rules.query());

I would prefer to do something like this:

restifyMongoose(models.Note, opts, authenticate, f1, f2, f3).serve('/notes', server);

Just like Restify allows, the constructor would allow you to pass or "chain" as many functions as you need.

Thoughts?

Page size 100 limit ?

wow, I use this package every day, however recently i got a request that i need to grab more than 100, ex. 1000 or even 5000 records. Is there a way that i can change this behavior quickly without changing source code.

Thank you.

Documentation request: auth and access control

In many APIs in the wild there are some common requirements which are not mentioned in the README:

  • Authentication: The ability for a user to prove who they are to the server. (Can be stateless, e.g. with JWT.)

  • Access control: The ability for the system to restrict access of some data and operations to certain users.

I do not expect restify-mongoose to provide implementations of these features, but if restify-mongoose can support these behaviours, it would be helpful to see them documented in the README.

For example, something like this could be reassuring, and could increase adoption:

// If you want auth:
// server.use(restify.plugins.authorizationParser());

// If you want access control:
// server.get('/notes', notes.queryVisibleToUser());
// server.get('/notes/:id', checkUserCanView, notes.detail());
// server.post('/notes', checkUserCanAdd, notes.insert());
// server.patch('/notes/:id', checkUserCanModify, notes.insert());
// server.del('/notes/:id', checkUserIsOwner, notes.remove());

I am not sure if the code above would be the most appropriate solution. That's why I'm asking here!

Pass the options object to all functions

Right now options is only available for the details and query functions. The filter function, however, seems to be applied to all of the functions except insert.

For consistency, it seems that options should be available on update and delete as well.

If you agree, I can submit a pull request. Just didn't want to do the work if there was a good reason not too.

Feature Request: Implement the options.filter with support for async/promises

I don't have the data for the filter in request or response.
There is needed a mongoose query to get this data.
These queries are always async. So I get the result only in a callback or via promise.

No way to return a complete object by the called function:
https://github.com/saintedlama/restify-mongoose/blob/master/index.js#L267
query.where(self.options.filter(req, res));

I've to write a lot of code to get this to work...

This is TypeScript:

function createBookController(req: restify.Request): Promise<restifyMongoose> {
  return controlHelper.dependRequestUser(req)
    .then((user) => {
      const filterobj = { _creator: user._id };
      return createDynfilterFn(filterobj);
    })
    .then((dynfilter) => {
      const bookController = restifyMongoose(Book, {
        filter: dynfilter,
        listProjection: listProjection,
        detailProjection: detailProjection
      });
      log.debug('Created bookController with filter ' + JSON.stringify(dynfilter(undefined, undefined)));
      return bookController;
    });
}

export function query(req: restify.Request, res: restify.Response, next: restify.Next) {
  createBookController(req).then((bookController: restifyMongoose) => {
    bookController.query()(req, res, next);
  });
}

function createDynfilterFn(filterobj: Object): Function {
  const obj = filterobj;
  return (request: restify.Request, response: restify.Response) => {
    return obj;
  };
}

So it would be quite handy if I could give a promise to the filter option.
Or is there another solution?

A way to automate mongoose model methods mapping

It would be pretty cool to make restifyMongoose resource support custom model methods and improove restifyMongoose::serve function to automatically generate api endpoint for those methods.
For example:
When we have model...

const mongoose = require('mongoose');
const Schema = mongoose.Schema;

const _schema = new Schema({
  title:  String,
  toggle: { type: Boolean, default: false }
});

_schema.methods.switch = function () {
  this.toggle = !this.toggle;
  this.save();
};

var Toggler = mongoose.model('Toggler', _schema);

... it would be great to generate endpoint /Toggler/:id/switch wich will call appropriate method.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.