sequelize-modeler
Version:
Extended model class for sequelize with rest api.
390 lines (328 loc) • 12.3 kB
Markdown
[](https://opensource.org/licenses/MIT) [](https://badge.fury.io/js/sequelize-modeler) [](https://www.npmjs.com/package/sequelize-modeler)
----
sequelize-modeler is powerful set of tools built in around [Sequelize](https://sequelize.org/) and [Express](https://expressjs.com/) focused in REST API functionalities such as aliased fields, dynamic database connections, powerful query filtering and formatting
```
$ npm install --save sequelize-modeler
```
```js
// SomeModel.js
const Model = require('sequelize-modeler').Model
module.exports = class SomeModel extends Model{
static init(sequelize, DataTypes, connectionId){
let config = {
options:{
tableName: 'MyTable',
primaryKey: 'myPrimaryKey',
dateFormat: 'YYYY-DD-MM HH:mm:ss', // (optional) any moment format
connectionId,
sequelize,
},
fields: {
// field, alias, label and other properties are all optional, you can define your model normaly and they will be auto generated
MyColumn:{
field: 'MyColumn',
as: 'MyColumnCustomName',
label: 'Name The Column Should be Showed With',
type: DataTypes.STRING,
defaultValue: 'Some Value',
allowNull: false,
}
...
},
}
}
}
```
----
Quick start:
```js
const Catalog = require('sequelize-modeler').Catalog
// initialize your sequelize instance
const sequelize = new Sequelize(...options)
// register your connection to the catalog
// connectionId will be a generated unique id
const connectionId = Catalog.setConnection(sequelize)
// initialize your models
// all models are returned mapped with their classnames and ready to use
const {MyModel, MyOtherModel, ...etc} = Catalog.initialize([...ModelClasses], connectionId)
MyModel.findAll().then(instances => {
//do stuff here
})
```
Defining your own connection ids:
```js
Catalog.setConnection('your id',sequelizeInstance1)
Catalog.setConnection('other id',sequelizeInstance2)
```
----
__Getting a custom formatted instance:__
For the following Model Fields:
```js
...
fields:{
NumberColumn:{
field: 'NumberColumn',
as: 'number',
type: DataTypes.INTEGER,
},
DateColumn:{
field: 'DateColumn',
as: 'date',
type: DataTypes.DATE,
format: 'dddd, MMMM Do YYYY, h:mm:ss a'
},
}
...
```
You have to call the `format()` method of an instance
```js
let instance = await theModel.findOne({...})
console.log(instance.format())
```
Output would be
```json
{
"number": 0,
"date": "Sunday, February 14th 2010, 3:25:50 pm"
}
```
__Getting your model meta-data:__
```js
theModel.getFields()
```
Output would look like this
```json
[
{
"visible": true,
"filterable": true,
"key": "columnCustomName", // field.as
"label": "UI Label",
"dataType": "decimal",
"required": true,
"autoIncrement": false
},
{
"visible": true,
"filterable": true,
"key": "otherField",
"label": "otherField",
"dataType": "char",
"required": true,
"autoIncrement": false
},
{
"visible": true,
"filterable": true,
"key": "dateColumn",
"label": "Date",
"dataType": "date",
"format": "dddd, MMMM Do YYYY, h:mm:ss a",
"required": true,
"autoIncrement": false
},
]
```
__Generate an api endpoint:__
```js
const router = require('express').Router()
router.use('/theModel',theModel.generateApi())
```
The `.generateApi()` method returns an express router with:
- `[GET] '/theModel'` responds with a paginated list of all rows
- `[GET] '/theModel/:id'` responds with a single row with the matching id (primaryKey)
- `[GET] '/theModel/meta'` responds with some meta-data of the model like th field list, number of rows, visible and filterable fields also according to the request query (more on that here)
- `[POST] '/theModel'` creates an instance, saves it and returns it (response is false if creation fails)
- `[PUT] '/theModel/:id'` edits the instance and returns the new value
- `[DELETE] '/theModel/:id'` deletes the instance and responds with true or false accordingly
All enpoints responses and expected data is aliased with the corresponding configuration.
Supported query parameters for `[GET]`:
- ___Fields :___ `?fields=field1,field2,field3` specifies wich fields the instances are be sent with
- ___Order :___ `?order_by=field1(asc),field2(desc),field3` specifies how data should be ordered before pagination, with priority given by the order sent (`field1 > field2 > field3`). direction defaults to `asc`
- ___Pagination :___ `?limit=30&offset=2` where limit defines page size, and offset defines number of page, the given example would return rows 31 to 60. `limit` defaults to `25` and `offset` defaults to `1`
- ___Filtering :___
- ___simple filter :___ `?foo=bar&exclusive=true` will return all rows where `foo` column conains `bar`. Useful for filtering data tables or autocomplete inputs
```json
[{ "foo":"bar" }, { "foo":"barbacue" }, { "foo":"bombardier" }]
```
it also applies to numeric fields `?number=12`
```json
[{ "number":12 }, { "number":33123 }, { "number":12999 }]
```
- ___exclusive filter :___ `?foo=bar` will return all rows where `foo` column equals to `bar`, all values are converted to their respective type with their respective formatting declared in the model configuration
- ___power filtering :___ Allows for complex querying `?field1(and,contains)=value1,field2(and,>)=value2,field3(or,<>)=value3`
where the expected format is `fieldName(condition,operator)=value`, if the power format is matched at least once the api will expect it on each of the fields. numeric fields are supported by all operators.
- ___conditions :___
- `and`
- `or`
- ___operators :___
- `eq` : equal to
- `<` : lower than
- `>` : greater than
- `<eq` : lower than or equal to
- `>eq` : greater than or equal to
- `<>` : not equal to
- `contains` : contains
- `!contains` : not contains
- `startsWith` : starts with
- `!startsWith` : not starts with
- `endWith` : ends with
- `!endWith` : not ends with
__Adding extra middlewares :__
Middlewares can be added to all different generated endpoints either before or after them, if the response should be sent, just add `next:true` to the options and the reponse will be carried to the next middleware as `req.body._carried_` this can be either an instance, a list of instances, an error, or `undefined` depending on the endpoint and if was successful or failed.
```js
const router = require('express').Router()
// we will add this middleware before the generated get endpoint
let justReds = (req, res, next) => {
req.query.color = 'red'
req.query.exclusive = false
// do whatever you want here
next()
}
router.use('/theModel',theModel.generateApi({
middlewares:{
get:[{place:'before', handler: justReds}]
}
}))
```
Example using carried data:
```js
const router = require('express').Router()
const afterMiddleware = (req, res, next) => {
let createdInstanceOrError = req.body._carried_
// handle you output here
}
router.use('/theModel',theModel.generateApi({
next: true,
middlewares:{
post:[{place:'after', handler: afterMiddleware}]
}
}))
```
__Other options :__
`config.model` - Model to be used (`Model | Function | string`)
`config.connection` - Connection to be used when model is string (`Connection | string`)
Examples of dynamic model:
```js
const { Model, Catalog } = require('sequelize-modeler')
const modelList = require('path/to/list/of/model/classes') // list of not initialized models
const connection = Catalog.getConnection('myId') // active connection
for(let model of modelList){
// models will be initialized in their respective generated routers
router.use(`${model.name}`,Model.generateApi({connection, model}))// an api fo every model 🚀
}
```
Using the model name:
```js
const { Model, Catalog } = require('sequelize-modeler')
let model = 'SomeModelName'
let connection = Catalog.getConnection('myId') // active connection
router.use(`${model}`,Model.generateApi({connection, model}))
```
Using a Function to infer the model :
```js
const { Model, Catalog } = require('sequelize-modeler')
const model = (req,res,next) => { // req, res, next passed from express
let model
// do stuff...
// populate model with some initialized model
return model
}
// note that connection is no longer required, since you return an initialized model
router.use(`${model}`,Model.generateApi({model}))
```
`config.failed` - callback to be excecuted when creation fails
```js
const router = require('express').Router()
const failed = (req,res,next) => {
// instance can be undefined
// this will be excecuted when something expected fails, like an insert, or edition
}
router.use('/theModel',theModel.generateApi({failed}))
```
`config.failed` - callbback to be excecuted in case of an internal error, by deafult `res.status(500).send('Internal Server Error')`
```js
const router = require('express').Router()
const error = (req,res,next,error) => {
// this will be excecuted when something unexpected like sequelize throwing an error
}
router.use('/theModel',theModel.generateApi({error}))
```
`config.empty` - Map of values to assign to null fields of each different dataType, or for every dataType
```js
// empty map
{
integer: number
decimal: number,
bigint: number,
float: number,
real: number,
double: number,
string: string,
text: string,
citext: string,
date: Moment,
dateonly: Moment,
boolean: boolean,
enum: [any],
array: [any],
json: Object,
jsonb: Object,
blob: BlobPart,
uuid: string,
cidr: any,
inet: any,
macaddr: any,
range: [any],
geometry: any,
all: any,
}
```
`config.apiname` - Name used to build href of models, all instances have a `href: http://example.com/apiname/:id`, you get to change the `apiname` part of it
`config.next` - Toggles the excecution of `next()`, defaults to `false`
`config.middlewares` - Extra optional middlwares to be added `{[{place:'before'|'after', handler:Middleware}]}` place indicates if middlware must be excecuted before or after the generated one, see example above
`config.get` - Toggles the `[GET]` method, defaults to `true`
`config.getOne` - Toggles the `[GET] /:id` endpoint, defaults to `true`
`config.post` - Toggles the `[POST]` method, defaults to `true`
`config.put` - Toggles the `[PUT]` method, defaults to `true`
`config.delet` - Toggles the `[DELETE]` method, defaults to `true`
`config.meta` - Toggles the `/meta` endpoint, defaults to `true`
`config.fields` - List of fields to be used by the model, all instances and meta-data will be mapped to the given field list
```js
const router = require('express').Router()
// you don't need to put every atribute of each field, just what you want
let fields = [
{
"key": "Column1",
"as": "one",
"dataType": "decimal",
},
{
"key": "Column2",
"as": "two",
"dataType": "date",
"format": "dddd, MMMM Do YYYY, h:mm:ss a",
},
{
"key": "Column56",
"as": "thisCoulmnDoesNotExist",
"dataType": "string",
}
]
router.use('/theModel',theModel.generateApi({fields}))
```
Output instances would be like this:
```js
{
one: 15,
two: "Sunday, February 14th 2010, 3:25:50 pm",
thisCoulmnDoesNotExist: null,
href: "http://example.com/theModel/15"
}
```