foraker

0.0.4 • Public • Published

foraker NPM version Build Status Dependency Status Coverage percentage

Foraker provides a simple, drop-in controller that support filters, promises, subclassing, and more. It's easy to integrate into existing Express / middleware based apps, follows the "do one thing well" principle, and is thoroughly tested.

Note: this is alpha software, and still undergoing changes. Contributions welcome, consumers beware 😉

Install

$ npm install --save foraker

Basic Usage

// controllers/posts.js
import Controller from 'foraker';
 
export default Controller.extend({
  create(req, res) {
    // You can return promises from an action handler. Rejected promises will
    // call next(rejectionValue).
    return createNewPostRecord(req.body)
      .then((newPost) => {
        res.json(newPost);
      });
  }
});

Express / Connect Integration

Controllers are stand alone code - you need to wire them up to your Express (or otherwise Connect-compatible) routing layer to let them actually handle requests:

// app.js
import PostController from './controllers/posts';
 
// Controller singleton
let posts = new PostController();
 
app.post('/posts', posts.action('create'));

Filters

Filters operate much like Rails filters: they allow you to run code before or after an action handler runs.

Filters can be applied to specific actions, are inherited from superclasses, and can be skipped. They receive the req and res just like a regular action handler, and if they throw an error or send a response, the request handling stops there (i.e. later filters or actions are not run).

Here's a basic example of a controller with a single action (update), and a single before filter that applies to all actions on the controller which will authenticate the user:

// controllers/posts.js
import Controller from 'foraker';
 
export default Controller.extend({
 
  filters() {
    // Run the authenticate function (that is defined on this controller) before
    // any action for this controller runs.
    this.before('authenticate');
  },
 
  update(req, res) {
    /* update the post record ... */
  },
 
  authenticate(req, res) {
    if (req.headers['Authorization'] !== 'Secret Password') {
      // Errors throw will be caught, and passed into next()
      throw new Error('Unauthorized!');
    }
  }
 
});

In this more complex example, the filters are applied selectively using the only and except options:

  filters() {
    // The `only` option acts like a whitelist, so the notifyAuthor filter is
    // only run for the update action. `except` acts like a blacklist,
    // preventing the filter from running on specific actions.
    this.after('notifyAuthor', { only: 'update' });
  }

You can also pass the filter method directly in, rather than referencing it by name. This is useful if the filter method isn't a method defined on the controller class itself:

import Controller from 'foraker';
import authenticate from '../filters/authenticate';
 
export default Controller.extend({
  filters() {
    // If the filter is a string, it's assumed to be a method defined on the
    // controller itself. Alternatively, you can pass in a function directly:
    this.before(authenticate);
  }

Subclassing

Foraker Controllers use core-object to provide inheritance functionality. You can extend the base Controller class via Controller.extend, which will then be available on your subclass as well:

import Controller from 'foraker';
 
let BaseController = Controller.extend({ /* ... */ });
let SubclassedController = BaseController.extend({ /* ... */ });

core-object also provides convenient super functionality:

import Controller from 'foraker';
 
let NameController = Controller.extend({
  getName() {
    return 'Dave';
  }
});
 
let PoliteNameController = NameController.extend({
  getName() {
    return 'Mr. ' + this._super();
  }
});

Controllers with filters are intelligent about how they handle subclasses. Child classes will run the parent class filters as well:

import Controller from 'foraker';
 
let ApplicationController = Controller.extend({
  filters() {
    this.before('authenticate');
  },
  authenticate(req, res) {
    if (!req.user) {
      throw new Error('Unauthorized!');
    }
  }
});
 
let BooksController = ApplicationController.extend({
  create() {
    // ApplicationController.authenticate will run before this action does
  }
});

Parent class before filters will run prior to the child class before filters, and parent class after filters will run following the child class after filters.

License

MIT © Dave Wasmer

Package Sidebar

Install

npm i foraker

Weekly Downloads

4

Version

0.0.4

License

MIT

Last publish

Collaborators

  • davewasmer