npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

parkes-controller

v0.2.5

Published

Library for fast REST API generation in koa

Downloads

36

Readme

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.