1.0.17 • Published 5 years ago

sequelize-modeler v1.0.17

Weekly downloads
6
License
MIT
Repository
github
Last release
5 years ago

#sequelize-modeler

License: MIT npm version node

sequelize-modeler is powerful set of tools built in around Sequelize and Express focused in REST API functionalities such as aliased fields, dynamic database connections, powerful query filtering and formatting

##Install

$ npm install --save sequelize-modeler

##Usage

####Defining a Model:

// 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,
                }
                ...
            },
        }
    }
}

####Initializing a Model:

Quick start:

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:

Catalog.setConnection('your id',sequelizeInstance1)
Catalog.setConnection('other id',sequelizeInstance2)

using a model:


Getting a custom formatted instance:

For the following Model Fields:

...
    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

let instance = await theModel.findOne({...})
console.log(instance.format())

Output would be

{
    "number": 0,
    "date": "Sunday, February 14th 2010, 3:25:50 pm"
}

Getting your model meta-data:

theModel.getFields()

Output would look like this

[
    {
        "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:

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
      [{ "foo":"bar" }, { "foo":"barbacue" }, { "foo":"bombardier" }] 
      it also applies to numeric fields ?number=12
      [{ "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.

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:

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:

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:

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 :

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

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')

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

// 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

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:

{
    one: 15,
    two: "Sunday, February 14th 2010, 3:25:50 pm",
    thisCoulmnDoesNotExist: null,
    href: "http://example.com/theModel/15"
}
1.0.17

5 years ago

1.0.16

5 years ago

1.0.15

5 years ago

1.0.13

5 years ago

1.0.12

5 years ago

1.0.11

5 years ago

1.0.10

5 years ago

1.0.9

5 years ago

1.0.8

5 years ago

1.0.7

5 years ago

1.0.6

5 years ago

1.0.5

5 years ago

1.0.4

5 years ago

1.0.3

5 years ago

1.0.2

5 years ago

1.0.1

5 years ago

1.0.0

5 years ago