• Stars
    star
    115
  • Rank 299,889 (Top 7 %)
  • Language
    JavaScript
  • Created over 9 years ago
  • Updated 10 months ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

An minimalistic Express.js library that will reflect a Mongoose model onto a RESTful interface with a splash of Swagger.io love.

Resource.js - A simple Express library to reflect Mongoose models to a REST interface with a splash of Swagger.io love.

NPM version NPM download Build Status Coverage Status

Resource.js is designed to be a minimalistic Express library that reflects a Mongoose model to a RESTful interface. It does this through a very simple and extensible interface.

Installation

You can install Resource.js using NPM.

npm install --save resourcejs

Usage

Provided the following code

var express = require('express');
var bodyParser = require('body-parser');
var mongoose = require('mongoose');
var Resource = require('resourcejs');

mongoose.connect('mongodb://localhost/myapp');

// Create the app.
var app = express();

// Use the body parser.
app.use(bodyParser.urlencoded({extended: true}));
app.use(bodyParser.json());

// Create the schema.
var ResourceSchema = new mongoose.Schema({
  title: {
    type: String,
    required: true
  },
  description: {
    type: String
  },
  count: {
    type: Number
  }
});

// Create the model.
var ResourceModel = mongoose.model('Resource', ResourceSchema);

// Create the REST resource.
Resource(app, '', 'resource', ResourceModel).rest();

The following rest interface would then be exposed.

  • /resource - (GET) - List all resources.
  • /resource - (POST) - Create a new resource.
  • /resource/:id - (GET) - Get a specific resource.
  • /resource/:id - (PUT) - Replaces an existing resource.
  • /resource/:id - (PATCH) - Updates an existing resource.
  • /resource/:id - (DELETE) - Deletes an existing resource.

Parameters

The Resource object takes 4 arguments.

Resource(app, route, name, model)
  • app - This is the Express application.
  • route - This is the route to "mount" this resource onto. For example, if you were doing nested resources, this could be '/parent/:parentId'
  • name - The name of the resource, which will then be used for the URL path of that resource.
  • model - The Mongoose Model for this interface.

Only exposing certain methods

You can also expose only a certain amount of methods, by instead of using the rest method, you can use the specific methods and then chain them together like so.

// Do not expose DELETE.
Resource(app, '', 'resource', ResourceModel).get().put().post().index();

Adding Before and After handlers

This library allows you to handle middleware either before or after the request is made to the Mongoose query mechanism. This allows you to either alter the query being made or, provide authentication.

For example, if you wish to provide basic authentication to every endpoint, you can use the before callback attached to the rest method like so.

npm install basic-auth-connect
var basicAuth = require('basic-auth-connect');

...
...

Resource(app, '', 'resource', ResourceModel).rest({
  before: basicAuth('username', 'password')
});

You can also target individual methods so if you wanted to protect POST, PUT, and DELETE but not GET and INDEX you would do the following.

Resource(app, '', 'resource', ResourceModel).rest({
  beforePut: basicAuth('username', 'password'),
  beforePost: basicAuth('username', 'password'),
  beforeDelete: basicAuth('username', 'password')
});

You can also do this by specifying the handlers within the specific method calls like so.

Resource(app, '', 'resource', ResourceModel)
  .get()
  .put({
    before: basicAuth('username', 'password'),
    after: function(req, res, next) {
      console.log("PUT was just called!");
    }
  })
  .post({
  	before: basicAuth('username', 'password')
  });

After Handlers: The after handlers allow you to modify the contents of the resource before it is handed over to the client. It does this by setting a resource object on the res object. This resource object follows the following schema.

  • status: The status code that will be sent to the client.
  • error: Any error that may have been caused within the request.
  • item: The resource item that is going to be sent to the client.

For example, if you have a resource that has a title that is sent to the user, you could change that title by doing the following.

Resource(app, '', 'resource', ResourceModel).get({
  after: function(req, res, next) {
    res.resource.item.title = 'I am changing!!';
    next();
  }
});

Virtual Resources

Virtual resources are not represented by mongodb documents. Instead they are generated by functions acting on existing mongodb documents, typically via the mongodb aggregate pipeline.

Resource.js supports this feature by passing options to the resource.virtual method. The virtual method expects at least the path and the before option params to be set:

  • path : Set to the name of the virtual resource. This will be used in the generated url.
  • before: Set to a function that will be used to generate the virtual resource.

This will result in a generated REST end-point with the following pattern:

  • /[resource-name]/virtual/[virtual-resource-name]

For example, defining a virtual resource called avg-weight for a resource called elephant will give a url of:

  • /elephant/virtual/avg-weight

The shape of json data returned is determined by a before function. This function will act on an existing document to return a virtual resource of arbitrary shape. Typically a mongodb aggregate function will be used here although any valid model query is in fact allowed.

For example, to set up two virtual resources, max-price and max-stock, for a resource called product you would write code similar to the following:

var express = require('express');
var bodyParser = require('body-parser');
var mongoose = require('mongoose');
var resource = require('resourcejs');

// Create the app.
var app = express();

// Use the body parser.
app.use(bodyParser.urlencoded({extended: true}));
app.use(bodyParser.json());

//Create the Schema
var productSchema = new Schema({
  name: String,
  price: Number,
  stock: Number
});

//Create the model
var productModel = mongoose.model('product', productSchema);

You can then define a couple of aggregate functions called max-price and max-stock using the mongoose model.

//Define the virtual resource aggregate functions
var maxPrice = function(req, res, next) {
  req.modelQuery = productModel.aggregate().group({
    _id: null,
    maxPrice: {
      $max: '$price'
    }
  });
  return next();
};

var maxStock = function(req, res, next) {
  req.modelQuery = productModel.aggregate().group({
    _id: null,
    maxStock: {
      $max: '$stock'
    }
  }).select;
  return next();
};

You can then setup the product via resource.js by passing in the path and the before function for each virtual resource, like this:

//Create the virtual Product resources
resource(app, '', 'product', productModel)
  .virtual({
    path: 'max-price',
    before: maxPrice
  })
  .virtual({
    path: 'max-stock',
    before: maxStock
  });

Finally you can retrieve the virtual resources using their generated urls:

#####max-price

  • /product/virtual/max-price

returns the max-price virtual resource as json:

{
  _id:null,
  maxPrice:123
}

#####max-stock

  • /product/virtual/max-stock

returns the max-stock virtual resource as json:

{
  _id:null,
  maxStock:321
}

Calling the PATCH method

ResourceJS fully implements the JSON-Patch spec RFC-6902. This allows for partial updates to be made directly to a resource and is therefore a very efficient way of updating a resource.

With JSON-Patch you can also test whether a resource is suitable for a updating and if it is then only update the fields you actually need to update. You can apply an arbitrary sequence of tests and actions (see the spec RFC-6902 for more details) and if any one should fail all the changes are rolled back and the resource is left untouched.

For example, using the Resource schema above, we will increment just the numeric count field but only if the count value is the same as the value we are currently holding, in other words - only update the value if nobody else has updated it in the meantime.

This example uses the request npm package

request = require('request')

function increaseCount(currentCount, resourceId, next) {
  var options, patch;
  patch = [
    {
      "op": "test",
      "path": "/count",
      "value": currentCount
    }, {
      "op": "replace",
      "path": "/count",
      "value": currentCount + 1
    }
  ];
  options = {
    method: 'PATCH',
    uri: "/resource/" + resourceId,
    body: patch,
    json: true
  };
  return request(options, function(err, response, data) {
    return next(data);
  });
}
});

Adding custom queries

Using the method above, it is possible to provide some custom queries in your before middleware. We can do this by adding a modelQuery to the req object during the middleware. This query uses the Mongoose query mechanism that you can see here http://mongoosejs.com/docs/api.html#query_Query-where.

For example, if we wish to show an index that filters ages greater than 18, we would do the following.

Resource(app, '', 'user', UserModel).rest({
  before: function(req, res, next) {
    req.modelQuery = this.model.where('age').gt(18);
  }
});

Passing write options on PUT, POST, PATCH, and DELETE requests

It is possible to pass a set of options to the underlying Document.save() and Document.remove() commands. This can be useful when plugins expect data to be passed in as options. We can do this by adding a writeOptions object to the req object during middleware. This uses the Mongoose mechanism that you can see here https://mongoosejs.com/docs/api.html#document_Document-save.

For example, a set of options can be added by doing the following.

Resource(app, '', 'user', UserModel).rest({
  before: function(req, res, next) {
    req.writeOptions = { actingUserId: req.user.id };
  }
});

Nested Resources

With this library, it is also pretty easy to nest resources. Here is an example of how to do it.

var express = require('express');
var bodyParser = require('body-parser');
var mongoose = require('mongoose');
var Resource = require('../Resource');

// Create the app.
var app = express();

// Use the body parser.
app.use(bodyParser.urlencoded({extended: true}));
app.use(bodyParser.json());

// Parent model
var Parent = mongoose.model('Parent', new mongoose.Schema({
  name: {
    type: String,
    required: true
  }
}));

// Child model.
var Child = mongoose.model('Child', new mongoose.Schema({
  name: {
    type: String,
    required: true
  },
  parent: {
    type: mongoose.Schema.Types.ObjectId,
    ref: 'Parent',
    index: true,
    required: true
  }
}));

// The parent REST interface.
Resource(app, '', 'parent', Parent).rest();

// The child REST interface.
Resource(app, '/parent/:parentId', 'child', Child).rest({

  // Add a before handler to include filter and parent information.
  before: function(req, res, next) {
    req.body.parent = req.params.parentId;
    req.modelQuery = this.model.where('parent', req.params.parentId);
    next();
  }
});

This would now expose the following...

  • /parent - (GET) - List all parents.
  • /parent - (POST) - Create a new parent.
  • /parent/:parentId - (GET) - Get a specific parent.
  • /parent/:parentId - (PUT) - Updates an existing parent.
  • /parent/:parentId - (DELETE) - Deletes an existing parent.
  • /parent/:parentId/child - (GET) - List all children of a parent.
  • /parent/:parentId/child - (POST) - Create a new child.
  • /parent/:parentId/child/:childId - (GET) - Get a specific child per parent.
  • /parent/:parentId/child/:childId - (PUT) - Update a child for a parent.
  • /parent/:parentId/child/:childId - (DELETE) - Delete a child for a parent.

Filtering the results.

The index() that is created is capable of doing some complex filtering using Query arguments within the URL. They are described as the following.

Filter Query Example Description
equal equals /users?gender=male both return all male users
not equal ne /users?gender__ne=male returns all users who are not male (female and x)
greater than gt /users?age__gt=18 returns all users older than 18
greater than or equal to gte /users?age__gte=18 returns all users 18 and older (age should be a number property)
less than lt /users?age__lt=30 returns all users age 29 and younger
less than or equal to lte /users?age__lte=30 returns all users age 30 and younger
in in /users?gender__in=female,male returns all female and male users
nin nin /users?age__nin=18,21 returns all users who are not 18 or 21
exists=true exists /users?age__exists=true returns all users where the age is provided.
exists=false exists /users?age__exists=false returns all users where the age is not provided.
Regex regex /users?username__regex=/^travis/i returns all users with a username starting with travis
limit limit /users?limit=5 limits results to the specified amount
skip skip /users?skip=10 skip to the specified record in the result set
select select /users?select=first_name,last_name return only the specified fields

Adding Swagger.io v2 documentation

Along with auto-generating API's for your application, this library also is able to auto generate Swagger.io documentation so that your API's are well documented and can be easily used and understood by everyone.

Each Resource object has the ability to generate the Swagger docs for that resource, and this can then be combined to create the Swagger docs necessary to feed into the Swagger UI tools.

Getting the swagger documentation for a resource

var resource = Resource(app, '', 'resource', ResourceModel).rest();

// Print out the Swagger docs for this resource.
console.log(resource.swagger());

You can then use this to create a full specification for you API with all your resources by doing the following.

var _ = require('lodash');

// Define all our resources.
var resources = {
	user: Resource(app, '', 'user', UserModel).rest(),
	group: Resource(app, '', 'group', GroupModel).rest(),
	role: Resource(app, '', 'role', RoleModel).rest()
};

// Get the Swagger paths and definitions for each resource.
var paths = {};
var definitions = {};
_.each(resources, function(resource) {
  var swagger = resource.swagger();
  paths = _.assign(paths, swagger.paths);
  definitions = _.assign(definitions, swagger.definitions);
});

// Define the specification.
var specification = {
  swagger: '2.0',
  info: {
    description: '',
    version: '0.0.1',
    title: '',
    contact: {
      name: '[email protected]'
    },
    license: {
      name: 'MIT',
      url: 'http://opensource.org/licenses/MIT'
    }
  },
  host: 'localhost:3000',
  basePath: '',
  schemes: ['http'],
  definitions: definitions,
  paths: paths
};

// Show the specification at the URL.
app.get('/spec', function(req, res, next) {
	res.json(specification);
});

More Repositories

1

jsencrypt

A zero-dependency Javascript library to perform OpenSSL RSA Encryption, Decryption, and Key Generation.
JavaScript
6,537
star
2

makemeasandwich.js

A Node.js + Phantom.js command line application that will automatically order you a sandwich from Jimmy John's. ( http://xkcd.com/149 )
JavaScript
963
star
3

seamless.js

A Javascript library for working with seamless iframes.
JavaScript
215
star
4

meanapp

An example M.E.A.N web application with full CRUD features.
ApacheConf
162
star
5

jquery.go.js

An easy-to-use web testing and automation tool that uses the jQuery interface within Node.js to interact with the Phantom.js browser.
JavaScript
150
star
6

jquery.treeselect.js

A minimalistic jQuery hierarchy select widget used for selecting hierarchy structures in a treeview format.
JavaScript
101
star
7

drupal.api.js

An object oriented JavaScript API Library for RESTful Drupal CMS.
JavaScript
55
star
8

zombie-phantom

Provides a Zombie.js shim around the Phantom.js Headless Browser.
JavaScript
51
star
9

presentations

A list of all my presentations.
JavaScript
48
star
10

minplayer

A minimalistic, plugin-based "core" media player for the web.
JavaScript
24
star
11

dartminer

A Bitcoin miner written in the Dart language.
Dart
20
star
12

drupal.go.js

A node.js package to automate and test Drupal using the Phantom.js headless browser.
JavaScript
16
star
13

mediafront_demo

A demo Drupal 7 site to show off the mediafront module.
12
star
14

zerotomean

The example application for 0 to M.E.A.N presentation
JavaScript
10
star
15

flatiron-passport

A Passport.js integration with the Flatiron.js web framework.
JavaScript
10
star
16

groupselfie

A web application that allows you to take Group Selfies
JavaScript
9
star
17

drupaltouch

A Sencha Touch application for Drupal CMS
JavaScript
8
star
18

drupal_multimedia

A Drupal 8 with multimedia support.
PHP
7
star
19

youtube_playlist

This is an example jQuery Mobile widget for showing YouTube Playlists. Go to http://www.youtube.com/watch?v=RlrJthCmmU8 to watch the presentation.
JavaScript
6
star
20

ResultTree

A PHP class that will take a flat parent-child mapping array, and build a result tree non-recursively.
PHP
6
star
21

limelight

A PHP library for integrating with Limelight CDN
PHP
5
star
22

restPHP

A couple of simple helper classes to help in writing RESTful PHP Libraries.
PHP
5
star
23

pivotalphp

This is a GPLv3 PHP-CLI script for Pivotal Tracker
PHP
5
star
24

formiomean

An example M.E.A.N app using Angular 4 + Form.io
TypeScript
5
star
25

juckstrap

Unfortunately named static site generation utilizing Nunjucks + Bootstrap + Wintersmith.js.
JavaScript
4
star
26

cliphp

An easy to use CLI ( command line interface ) PHP class that allows for user input.
PHP
4
star
27

phptoolbox

The PHP Toolbox is a convenient way to execute and manage your PHP scripts.
PHP
3
star
28

jekyll-kickstart

A starter Jekyll site using BootStrap 3
JavaScript
3
star
29

travist.github.com

My github pages site.
HTML
3
star
30

media_feature

A Drupal 7 media feature module.
PHP
3
star
31

CachedRequest

An extension to PEAR's HTTP_Request2 class that implements extensible caching mechanisms.
PHP
3
star
32

ropemaze

The rope maze puzzle solved using brute force.
JavaScript
3
star
33

minplayer-flash

An MIT licensed, minimalistic, skinnable, plugin based Flash media player
ActionScript
3
star
34

clicpp

This is a helper class to assist with creating command line applications in C++ where you can easily gather settings for your application.
C++
2
star
35

rotating_banner_pan

A plugin for Drupal's Rotating Banner module that implements panning.
JavaScript
2
star
36

CCK

Content Construction Kit Clone
PHP
2
star
37

drupal-zombie

A presentation and examples for "Automating and Testing Drupal with Zombie.js"
JavaScript
2
star
38

pdfjs-viewer

A built bower version of PDFJS
JavaScript
2
star
39

fpManager

A plugin manager for Flash applications.
ActionScript
2
star
40

moviemate

A Chrome extension that merges functionality between YouTube Trailers and IMDB.
JavaScript
2
star
41

jquery.moreorless.js

A quick way to add "more" or "less" functionality to HTML elements using jQuery.
JavaScript
2
star
42

JQImage.js

A jQuery widget and wrapper class for working with Images.
JavaScript
2
star
43

limelight.js

A node.js application for authenticating Limelight API requests.
JavaScript
1
star
44

drupal-media-module

This is the Drupal Media module with media player integration.
JavaScript
1
star
45

generator-juckstrap

A Yeoman generator for juckstrap.
JavaScript
1
star
46

async-shell

A super simple async shell for Node.js
JavaScript
1
star