Contribute on GitHub

Getting started


package.json

{
  "dependencies": {
    "express-restify-mongoose": "^5.0.0",
    "mongoose": "^4.0.0"
  }
}

From the command line

npm install express-restify-mongoose --save

While the source and examples are now written in ES2015, the module is transpiled and published as ES5 using Babel and remains fully compatible with Node 0.10 and newer.

Express 4 app

This snippet…

const express = require('express')
const bodyParser = require('body-parser')
const methodOverride = require('method-override')
const mongoose = require('mongoose')
const restify = require('express-restify-mongoose')
const app = express()
const router = express.Router()

app.use(bodyParser.json())
app.use(methodOverride())

mongoose.connect('mongodb://localhost:27017/database')

restify.serve(router, mongoose.model('Customer', new mongoose.Schema({
  name: { type: String, required: true },
  comment: { type: String }
})))

app.use(router)

app.listen(3000, () => {
  console.log('Express server listening on port 3000')
})

…automatically generates those endpoints.

GET http://localhost/api/v1/Customer/count
GET http://localhost/api/v1/Customer
POST http://localhost/api/v1/Customer
DELETE http://localhost/api/v1/Customer

GET http://localhost/api/v1/Customer/:id
GET http://localhost/api/v1/Customer/:id/shallow
PUT http://localhost/api/v1/Customer/:id
POST http://localhost/api/v1/Customer/:id
PATCH http://localhost/api/v1/Customer/:id
DELETE http://localhost/api/v1/Customer/:id

Usage with request

const request = require('request')

request.get({
  url: '/api/v1/Model',
  qs: {
    query: JSON.stringify({
      $or: [{
        name: '~Another'
      }, {
        $and: [{
          name: '~Product'
        }, {
          price: '<=10'
        }]
      }],
      price: 20
    })
  }
})

Querying


All the following parameters (sort, skip, limit, query, populate, select and distinct) support the entire mongoose feature set.

When passing values as objects or arrays in URLs, they must be valid JSON.

Sort

Use 1 for ascending and -1 for descending order.

GET /Customer?sort=name
GET /Customer?sort=-name
GET /Customer?sort={"name":1}
GET /Customer?sort={"name":-1}

Skip

GET /Customer?skip=10

Limit

Only overrides options.limit if the queried limit is lower.

GET /Customer?limit=10

Query

Supports all operators ($regex, $gt, $gte, $lt, $lte, $ne, etc.)

GET /Customer?query={"name":"Bob"}
GET /Customer?query={"name":{"$regex":"^(Bob)"}}
GET /Customer?query={"age":{"$gt":12}}
GET /Customer?query={"age":{"$gte":12}}
GET /Customer?query={"age":{"$lt":12}}
GET /Customer?query={"age":{"$lte":12}}
GET /Customer?query={"age":{"$ne":12}}

Populate

Population is the process of automatically replacing the specified paths in the document with document(s) from other collection(s).

restify.serve(router, mongoose.model('Invoice', new mongoose.Schema({
  customer: [{ type: mongoose.Schema.Types.ObjectId }],
  products: [{ type: mongoose.Schema.Types.ObjectId }]
})))

Works with create, read and update operations.

GET/POST/PUT /Invoices?populate=customer
GET/POST/PUT /Invoices?populate={"path":"customer"}
GET/POST/PUT /Invoices?populate=[{"path":"customer"},{"path":"products"}]

Select

GET /Customer?select=name
GET /Customer?select=-name
GET /Customer?select={"name":1}
GET /Customer?select={"name":0}

Distinct

If the field is private or protected and the request does not have appropriate access, an empty array is returned.

GET /Customer?distinct=name

Reference


serve

const uri = restify.serve(router, model[, options])

// uri = '/api/v1/Model'

router: express.Router() instance (Express 4), app object (Express 3) or server object (restify)

model: mongoose model

options: object typedefaultversion

When version is unspecified, the feature is available in the initial major release (4.0.0)

prefix

string/api

Path to prefix to the REST endpoint.

version

string/v1

API version that will be prefixed to the rest path. If prefix or version contains /:id, then that will be used as the location to search for the id.

Example

Generates /api/v1/Entities/:id/Model and /api/v1/Entities/Model for all pertinent methods.

version: '/v1/Entities/:id'

idProperty

string_id

findById will query on the given property.

restify

booleanfalse

Enable support for restify instead of express.

name

stringmodel name

Endpoint name

allowRegex

booleantrue

Whether or not regular expressions should be executed. Setting it to true will protect against ReDoS, see issue #195 for details.

runValidators

booleanfalse

Whether or not mongoose should run schema validators when using findOneAndUpdate. For more information, read the mongoose docs.

readPreference

stringprimary

Determines the MongoDB nodes from which to read. For more information, read the mongoose docs.

totalCountHeader

boolean|stringfalse

When totalCountHeader: true, execute a count query on GET /Model requests ignoring limit and skip and setting the result in the a response header. It can also be set to a string to allow for a custom header. This is useful when it’s necessary to know in advance how many matching documents exist.

Examples

Boolean

totalCountHeader: true

Response:

Headers: {
  'X-Total-Count': 5
}

String

totalCountHeader: 'X-Custom-Count-Header'

Response:

Headers: {
  'X-Custom-Count-Header': 5
}

private

array

Array of fields which are only to be returned by queries that have private access.

Example

Defined in options

private: ['topSecret', 'fields']

Defined in mongoose schema

new Schema({
  topSecret: { type: String, access: 'protected' },
  fields: { type: String, access: 'protected' }
})

protected

array

Array of fields which are only to be returned by queries that have private or protected access.

Examples

Defined in options

protected: ['somewhatSecret', 'keys']

Defined in mongoose schema

new Schema({
  somewhatSecret: { type: String, access: 'protected' },
  keys: { type: String, access: 'protected' }
})

lean

booleantrue

Whether or not mongoose should use .lean() to convert results to plain old JavaScript objects. This is bad for performance, but allows returning virtuals, getters and setters.

findOneAndUpdate

booleantrue

Whether to use .findOneAndUpdate() or .findById() and then .save(), allowing document middleware to be called. For more information regarding mongoose middleware, read the docs.

findOneAndRemove

booleantrue

Whether to use .findOneAndRemove() or .findById() and then .remove(), allowing document middleware to be called. For more information regarding mongoose middleware, read the docs.

preMiddleware

(req, res, next) => {}

Middleware that runs before preCreate, preRead, preUpdate and preDelete.

Example
preMiddleware: (req, res, next) => {
  performAsyncLogic((err) => {
    next(err)
  })
}

preCreate

(req, res, next) => {}

Middleware that runs before creating a resource.

preCreate: (req, res, next) => {
  performAsyncLogic((err) => {
    next(err)
  })
}

preRead

(req, res, next) => {}

Middleware that runs before reading a resource.

preRead: (req, res, next) => {
  performAsyncLogic((err) => {
    next(err)
  })
}

preUpdate

(req, res, next) => {}

Middleware that runs before updating a resource.

preUpdate: (req, res, next) => {
  performAsyncLogic((err) => {
    next(err)
  })
}

When findOneAndUpdate: false, the document is available which is useful for authorization as well as setting values.

findOneAndUpdate: false,
preUpdate: (req, res, next) => {
  if (req.erm.document.user !== req.user._id) {
    return res.sendStatus(401)
  }

  req.erm.document.set('lastRequestAt', new Date())

  next()
}

preDelete

(req, res, next) => {}

Middleware that runs before deleting a resource.

preDelete: (req, res, next) => {
  performAsyncLogic((err) => {
    next(err)
  })
}

When findOneAndRemove: false, the document is available which is useful for authorization as well as performing non-destructive removals.

findOneAndRemove: false,
preDelete: (req, res, next) => {
  if (req.erm.document.user !== req.user._id) {
    return res.sendStatus(401)
  }

  req.erm.document.deletedAt = new Date()
  req.erm.document.save().then((doc) => {
    res.sendStatus(204)
  }).catch((err) => {
    options.onError(err, req, res, next)
  })
}

access

(req[, done]) => {}

Returns or yields ‘private’, ‘protected’ or ‘public’. It is called on GET, POST and PUT requests and filters out the fields defined in private and protected.

Examples

Sync

access: (req) => {
  if (req.isAuthenticated()) {
    return req.user.isAdmin ? 'private' : 'protected'
  } else {
    return 'public'
  }
}

Async

access: (req, done) => {
  performAsyncLogic((err, result) => {
    done(err, result ? 'public' : 'private')
  })
}

contextFilter

(model, req, done) => {}

Allows request specific filtering.

Example
contextFilter: (model, req, done) => {
  done(model.find({
    user: req.user._id
  }))
}

postCreate

(req, res, next) => {}

Middleware that runs after successfully creating a resource. The unfiltered document is available on req.erm.result.

postCreate: (req, res, next) => {
  const result = req.erm.result         // unfiltered document or object
  const statusCode = req.erm.statusCode // 201

  performAsyncLogic((err) => {
    next(err)
  })
}

postRead

(req, res, next) => {}

Middleware that runs after successfully reading a resource. The unfiltered document(s), or object(s) when lean: false, is available on req.erm.result.

postRead: (req, res, next) => {
  const result = req.erm.result         // unfiltered document, object or array
  const statusCode = req.erm.statusCode // 200

  performAsyncLogic((err) => {
    next(err)
  })
}

postUpdate

(req, res, next) => {}

Middleware that runs after successfully updating a resource. The unfiltered document, or object when lean: false, is available on req.erm.result.

postUpdate: (req, res, next) => {
  const result = req.erm.result         // unfiltered document or object
  const statusCode = req.erm.statusCode // 200

  performAsyncLogic((err) => {
    next(err)
  })
}

postDelete

(req, res, next) => {}

Middleware that runs after successfully deleting a resource.

postDelete: (req, res, next) => {
  const result = req.erm.result         // undefined
  const statusCode = req.erm.statusCode // 204

  performAsyncLogic((err) => {
    next(err)
  })
}

outputFn

(req, res) => {}4.3 (Promise support)

Function used to output the result. The filtered object is available on req.erm.result. Using the async version allows handling errors through onError.

Examples

Sync

outputFn: (req, res) => {
  const result = req.erm.result         // filtered object
  const statusCode = req.erm.statusCode // 200 or 201

  res.status(statusCode).json(result)
}

Async (using promises)

outputFn: (req, res) => {
  return performAsyncLogic().then() => {
    const result = req.erm.result         // filtered object
    const statusCode = req.erm.statusCode // 200 or 201

    return res.status(statusCode).json(result)
  })
}

Async (using async/await)

outputFn: async (req, res) => {
  const asyncLogicResult = await performAsyncLogic()

  const result = req.erm.result         // filtered object
  const statusCode = req.erm.statusCode // 200 or 201

  return res.status(statusCode).json(result)
}

postProcess

(req, res) => {}

Middleware that is called after output, useful for logging. The filtered object is available on req.erm.result.

Not guaranteed to execute after output if async operations are performed inside outputFn

postProcess: (req, res) => {
  const result = req.erm.result         // filtered object
  const statusCode = req.erm.statusCode // 200 or 201

  console.info(`${req.method} ${req.path} request completed with status code ${statusCode}`)
}

onError

(err, req, res, next) => {}serialize the entire error, except stack

Leaving this as default may leak information about your database

Function used to output an error.

Example
onError: (err, req, res, next) => {
  const statusCode = req.erm.statusCode // 400 or 404

  res.status(statusCode).json({
    message: err.message
  })
}

defaults

restify.defaults(options)

options: same as above, sets this object as the defaults for anything served afterwards