parkes-controller

0.2.5 • Public • Published

Parkes Controller

A framework for quickly setting up RESTful and CRUD compliant APIs for sequelize models.

Dependencies

Parkes Controller is built for koa 2 and requires async/await in node 7.6 Parkes Controller interfaces to Sequelize Models.

Getting Started

npm install --save raisely/parkes-controller

const parkesController = require('parkes-controller');

const User = sequelize.define('User', ...); // define the sequalize resouce

// declare the controller model
class UserController extends ParkesController {

  // define custom handlers for CRUD requests...
  show(ctx, next) {
    super(context);
    console.log('Retrieved user resource: ', ctx.state.data)
    await next();
  }

  // hook into specific CRUD events...
  async afterIndex(ctx, collection) {
      // do something cool during the request...
  }

  async beforeCreate(ctx, newRecord) {
    // Validate values in newRecord
  }
}

// init the controller (with options)
const userController = new UserController('user', options);

// hook up to your resource router
const users = new restUp.resource('user', userController);

// and then bind to your app
app.use(users.routes);

Parkes Controller works in conjunction with ParkesRouter and ParkesPresenter for routing requests and returning responses. Neither of these are strictly necessary, you can set up routes manually, just make sure you have some middleware that takes ctx.state.data and puts it on ctx.body.

Initializing a controller

Generally you'll want to extend the parkes controller to provide additional methods or override certain actions.

class MyController extends ParkesController {

    // you can omit the constructor...
    constructor(name, options){
        super(name, options);
        // your custom controller props
    }

    // CRUD hooks, modify REST-ful requests before and after models are generated
    async beforeShow(ctx) {}
    async afterShow(ctx, model) {}

    async beforeIndex(ctx) {}
    async afterIndex(ctx, collection) {}

    async beforeCreate(ctx, rawModel) {}
    async afterCreate(ctx, newModel, rawModel) {}

    async beforeUpdate(ctx, oldModel) {}
    async afterUpdate(ctx, newModel) {}

    async beforeDestroy(ctx, oldModel) {}
    async afterDestroy(ctx, deadModel) {}
}

controller = new MyController('user', options);

Name String

The name of the model for the controller to create an API for

Options Object

Is an object with options for ParkesController which is also passed to RestHandler

Option Default Description
authorize undefined A hook to authorize api calls. The controller will throw if this is not set, set it to false if you don't want any authorization.
authorizationScope undefined Callback function for binding dynamic model associations by the ctx.state.user value
models (required) Object containing all of your sequelize models (they should have singular names, ie User not Users)
resourceIdColumn 'uuid' Name of the column to be used for a resource id by the api
include [] Default includes to be used with find or findAll (can be overridden per call)
filterAttributes [] An array of attribute names that can be specified in the HTTP query that will cause a corresponding where clause in the sequelize query
scopeModels [] The presence of these keys in the query or params will filter findAll queries by joining on that model. eg You could specify ['user'] when defining the posts controller to allow API calls to request all posts by a specific user either via /users/:user/posts or /posts?user=:user
search ['name'] Array of fields to compare ?q= text against
restricted ['id', 'uuid', 'password', 'permission', 'internal', 'privateKey', 'publicKey', 'userId', 'organisationId'] Array of fields that may not be changed by create/update requests
allowed [] Allow one of the default restricted fields without redefining the rest
defaultPageLength 100 Default limit for pagination

Hooks

As shown in the above example, Parkes Controller allows you to bind events to before and after a primary database action occors within a request. This allows you to modify the requests before a response is generated.

Hook Parameters

ctx represents a Koa context object, while the second paramater represents a single Sequelize model or collection of models (unless otherwise stated).

Hook Name Returns Description
async beforeShow (ctx) Before a single model is fetched
async afterShow (ctx, model) After a single model is fetched
async beforeIndex (ctx) Before a model collection is fetched
async afterIndex (ctx, collection) After a model collection is fetched
async beforeCreate (ctx, rawModel) Before a model is added to the database (raw model)
async afterCreate (ctx, newModel) After a model has been added to the database (full model)
async beforeUpdate (ctx, oldModel, rawModel) Before a single model is updated
async afterUpdate (ctx, newModel) After a single model is updated
async beforeDestroy (ctx, oldModel) Before a single model is destroyed (or disabled)
async afterDestroy (ctx, deadModel) After a single model is destroyed (or disabled)

Authorization

A function for authorizing requests with the following signature. This is required. If you really don't want to authorize requests, set this to false.

function(ctx, { model, parent, scopes, isPrivate })
Paramters Type Description
ctx Context of the Koa request
model Sequelize class or instance The model that the request is accessing, usually the record that is being viewed/updated/deleted. For create this will be the class of the object to be created. See below for index
parent Sequelize instance In the case of create it's often necessary to know the record that the object will be a child of before creating the record (eg a user can only add an item to their shop )
scopes String[] Records that a findAll request is being scoped by
isPrivate boolean true if the request query contains ?private=1

In the case of index, authorize is called twice, the first time model will be the class to be indexed, allowing you to prevent fetching large arrays from the DB if the user has no access to that model. Once the records are retrieved, model will contain the array of sequelize records.

ParkesController and RestHandler

ParkesController delegates the bulk of the work to RestHandler which handles generating and executing sequelize queries. This allows the controller methods to remain fairly lean - generally they're of the form findRecord, authorize, beforeHook, update, afterHook. If you need more customisation of an action than the hooks can provide, then you can override the controller method and still make use of this.rest to delegate to the RestHandler.

Event emitters

The RestHandler provides the EventEmitter interface, EventEmitter allowing you to hook non-blocking events in the request. The names and parameters of the events are the same as the hooks (above), but do not block the completion of the request (and so cannot be used to modify the HTTP response).

class MyController extends ParkesController {
    constructor(name, options){
        super(name, options);

        this.rest.on('beforeCreate', (ctx, rawModel) => {
            // your non-blocking event code here...
        });
    }
}

Please not that unlike async hooks, the event emitters should not be used to modify requests or models. As EventEmitter events will not be synchronous with the HTTP request.

Package Sidebar

Install

npm i parkes-controller

Weekly Downloads

9

Version

0.2.5

License

KWPL

Unpacked Size

47.3 kB

Total Files

18

Last publish

Collaborators

  • raiselyengineering