sequelize-resource-controller
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.