Home Reference Source

sequelize-resource-controller

npm version Build Status codecov node License

sequelize-resource-controller is a REST Resource abstraction layer, wich handles all the burden of creating transactions, handling rollbacks and formatting responses for your Resource based Api with sequelize.

Provides support for Restify and Express, two of the most popular http frameworks for nodejs.

Installation

npm install --save sequelize-resource-controller

Testing

Run all tests

npm test

Run unit tests only

npm run unit-test

Run integ tests only

npm run integ-test

Generate ESDOC Documentation

npm run docs

Examples

Basic Example (Todo List Api)

The basic example is just a straighforward todo Api with very few files(one file for the database & models, and one for each implementation).

Endpoint verb Description
/todos POST Creates a new TODO
/todos GET Gets all TODOS
/todos/:todo_id GET Get a single TODO by id
/todos/:todo_id PUT Updates a Todo
/todos/:todo_id DELETE Deletes a single todo

To test this api use todo-api.postman_collection

You can check the example Here

Advanced Example(Points of interest)

The advanced example uses node-lite-router to publish the endpoints of the api, and basically represents a points of interest api(POI).

Endpoint verb Description
/v1/points-of-interest POST Creates a point of interest
/v1/points-of-interest GET Gets all the points of interest in a paginated fashion
/v1/points-of-interest/:poi_id PUT Updates a point of interest
/v1/points-of-interest/:poi_id DELETE Deletes a point of interest
/v1/points-of-interest/:poi_id GET Finds a particular a point of interest

To test this api use poi-api.postman_collection

You can check the example Here

Why sequelize-resource-controller(My motivations)?

When writing CRUD based REST Apis with a relational database backend, you'll often find yourself loosing time with some stuff like implementing transactions, writing validations, and just every step possible to convert your database model into an Resource representation.

This module will make sure your code looks standarized and CRUD looks like a trivial thing.

This also addresses to reach the Richardson's Rest Maturity Model by implementing for you the fancy parts(Basically the Verbs, the status codes, and the Hateoas stuff).

This module also uses google API design in terms of resource responses, error handling and internal service names(with the exception of our delete reserved word :P).

Notes

  • This is the second module of a series of node-modules that I am creating with the main goal of creating node based microservices stacks. This will address the Relational database CRUD APi approach with some database per service around here.

  • It was based on a module that I've previously coded back in 2016, this module has a cleaner way of doing things and has less messy parts. It allows you to take more control on some things. Also has support for restify wich is cool.

  • This module was written using async/await so you it is easier to handle the tricky parts.

  • This module has some cool eslinting around.

Documentation

You can check ESDOC documentation

ROADMAP

  • I18N support: Will be cool and nice to have translation messages for the supported errors and responses.

  • Extensible support for List endpoint: Basically override the findAll params to include associations or something like that.

Getting Started

The following steps will help you on getting started on sequelize-resource-controller.

Install The module

npm install --save sequelize-resource-controller

Create your database models

Sequelize Resource controller needs a sequelize instance and your database models to convert them into resources.

The following is a recommended practice to use

Choose an implementation

To choose an implementation you just have to extract it from the module itself, whether you are using object property deconstructing or plain property access

Choose Express Implementation

const { express } = require('sequelize-resource-controller');
//OR
const expressController = require('sequelize-resource-controller').express;

Choose Restify Implementation

const { restify } = require('sequelize-resource-controller');
//OR
const restifyController = require('sequelize-resource-controller').restify;

Choose Raw Implementation

const { resourceController } = require('sequelize-resource-controller');
//OR
const resourceController = require('sequelize-resource-controller').resourceController;

Resource Controller

The raw implementation its just the internal abstraction layer of your model to manage the CRUD of your sequelize model.

Import the ResourceController

const { resourceController } = require('sequelize-resource-controller');

Creating an instance

const myResource = new resourceController(sequelizeInstance, sequelizeModel);

Inserting a model

To insert a model you just have to use the create method, it will receive as parameter an object with the model data. It will return a response object with two properties:

  • error: Any exception caught in model creation(including validations)
  • response: The created model instance(Sequelize.Model instance)

const {error,response} = await myResource.create({
  name: 'Document your module'
});
//response will be a Sequelize.Model Instance

console.log(response);

console.log(response.get({ plain:true }));

Finding Models

To find the models you have to use findAll method, this method internally uses findAll sequelize's method and takes optional parameters:

  • limit: Pagination limit(default to 10)
  • offset: THe offset of the paging(default is 0)
  • sortOrder:The sort order used in the query(default unused)
  • sortField: The sort attributes used in the query(default unused)

This method will return a response object with two properties:

  • error: Any exception caught in model querying
  • response: a complex object with pagination hinting and the rows found in the query(as plain instances)
const {error,response} = await myResource.findAll({ limit: 5, offset: 0});
console.table(response.rows);

Finding by Id

const todo_id = '';
const findOneResponse = await myResource.findOne({todo_id});
console.table(findOneResponse.response);

Update a model

const todo_id = '';
const findOneResponse = await myResource.findOne({todo_id});
const modelToBeUpdated = findOneResponse.response;

const {error,response} = await myResource.update(modelToBeUpdated, {
  name: 'This todo was updated dude',
  is_done: true
});

console.table(response);

Destroy a model

const todo_id = '';
const findOneResponse = await myResource.findOne({todo_id});
const victimModel = findOneResponse.response;

const {error,response} = await myResource.destroy(victimModel);

console.table(response);

express Implementation

Same as on the restify implementation you have to follow the given steps:

Basic definitions

const express = require('express');
const expressController = require('sequelize-resource-controller').express;
const { sequelize, models } = require('db');
const app = express();

Bind the endpoints using the Controller

const { todo } = models;
const yourController = new expressController(sequelize,todo);
const yourRouter = express.Router();
/**
 * It is explicitly mandatory to use bind(controller) to avoid scope errors
 **/
yourRouter.route('/').get(yourController.list.bind(yourController));
yourRouter.route('/').post(yourController.create.bind(yourController));
yourRouter.route('/:todo_id').get(yourController.getOne.bind(yourController));
/**
 * To use getOne and destroy you must use a middleware called
 * getOneMiddleware wich will handle the existence validation(convenient for cool 404 status codes)
 */
yourRouter.route('/:todo_id').put([
  yourController.getOneMiddleware.bind(yourController),
  yourController.update.bind(yourController)
]);

yourRouter.route('/:todo_id').destroy([
  yourController.getOneMiddleware.bind(yourController),
  yourController.destroy.bind(yourController)
]);

app.use('/v1/todos',yourRouter);

Start the server

app.listen(8000,() => {
  console.log('Ready to rock');
});

Restify Implementation

The following steps will get you started with sequelize-resource-controller for restify

Basic definitions

Import the required packages: restify, sequelize and anything else you need.

const restify = require('restify');
const { Router }  = require('restify-router');
const restifyController = require('sequelize-resource-controller').restify;
const { sequelize, models } = require('db');
const app = restify.createServer();

Bind the endpoints using the Controller

To perform the binding you just have to use every method in your restifyController instance as shown below:

const { todo } = models;
const yourController = new restifyController(sequelize,todo);
const yourRouter = new Router();
/**
 * It is explicitly mandatory to use bind(controller) to avoid scope errors
 **/
//Define a endpoint to query paginated elements of your model
yourRouter.get('/todos',yourController.list.bind(yourController));
//Define an endpoint to create a element of your model
yourRouter.post('/todos',yourController.create.bind(yourController));

//Define and endpoint to find one single element of your model by its id
yourRouter.get('/todos/:todo_id',yourController.getOne.bind(yourController));
/**
 * To use getOne and destroy you must use a middleware called
 * getOneMiddleware wich will handle the existence validation(convenient for cool 404 status codes)
 */
yourRouter.put('/todos/:todo_id', [
  yourController.getOneMiddleware.bind(yourController),
  yourController.update.bind(yourController)
]);
//Define an endpoint to delete a single element of your model by its id
yourRouter.del('/todos/:todo_id',[
  yourController.getOneMiddleware.bind(yourController),
  yourController.destroy.bind(yourController)
]);

yourRouter.applyRoutes(app);

Start the server

Just use your implementation start method on a given port and you are ready to go.

app.listen(8000,() => {
  console.log('Ready to rock');
});

Extending Controller

You can extend the existing implementations to add more functionality aside to the typical CRUD Resource methods.

Extension example

In this example we are using a ES6 class to extend Express implementation basically adding a new method called listPending.

class TodoResource extends ExpressController {

  constructor(sequelize,model) {
    super(sequelize,model);
  }

  async listPending(req,res) {
    try {
      const response = await this.model.findAll({
        where: {
          is_done: false
        }
      });
      //This way it will be compatible for express and restify
      //however you can just use res.status(..).send(..)
      this.sendResponse(res, 200,response);
    } catch(error) {
      //custom formating
      const resourceErr = this.resource.sequelizeError(error);
      this.sendResponse(res, resourceErr.code, resourceErr);
    }
  }
}

Afterwards you can easily publish the routes the same way as in the examples and add the new method:

yourRouter.route('/pending').get(yourController.listPending.bind(yourController));

This is convenient to basically getting started with the basic REST endpoints and then start adding some more complex endpoints for your api.