brainy.io

brainy is a set of tools for sharing Backbone code between the client and the server. brainy allows you to write a Backbone client application without regard for the server. it creates the server for you.

because it relies on REST and Backbone best practices, if you are familiar with both, you can create a brainy application.

introduction

when writing a front-end web application with Backbone, you are typically targeting some remote api. for rapid prototyping this poses two problems. first, you are rewriting models that likely have very similar definitions on the server. secondly, of course, it requires you have an api.

brainy solves these problems by analyzing your Backbone models and collections (resources), and creating the api they require.

if you've not already, try creating a brainy application.

api

when you start brainy it will create a RESTful API based on the (resources) it's given. collections and models expose different endpoints and support different HTTP methods. the endpoint names reflect url and urlRoot properties of collections and models respectively.

because your resources are shared between the client and server, the resource methods you define (fetch, save, etc) are run in both contexts. this is useful for scenarios like validation. this can be acheived by overriding save, destroy etc. to return errors if some condition is not satisfied, which will be verified by the client and the server.

models

when given a model, brainy will create the following HTTP endpoints for creating, reading, updating, and deleting models. we will use the following model definition for endpoint examples.

var Post = Backbone.Model.extend({
  idAttribute: '_id',
  urlRoot: '/posts'
});
POST /:urlRoot

creates and returns a new model, or 400 if the model is invalid. model attributes should be passed in through the request body.

$ curl -X POST -d 'text=foo&username=catshirt' /posts
  {
    "text": "foo",
    "username": "catshirt",
    "_id": "512a40f1163dcb4bce000001"
  }
GET /:urlRoot/:id

returns a single model, or 404 if not found. additional parameters passed through in the querystring are interpreted as a mongodb query. if a query is used, the model will only be returned if it matches that query.

$ curl -X GET /posts/512a40f1163dcb4bce000001
  {
    "text": "foo",
    "username": "catshirt",
    "_id": "512a40f1163dcb4bce000001"
  }
PUT /:urlRoot/:id

updates and returns the updated model, or 400 if the model is invalid. differs from PATCH in that PUT requires all attributes of the model to be sent. model attributes should be passed in through the request body.

$ curl -X PUT -d 'text=putted&username=catshirt' /posts/512a40f1163dcb4bce000001
  {
    "text": "putted",
    "username": "catshirt",
    "_id": "512a40f1163dcb4bce000001"
  }
PATCH /:urlRoot/:id

updates and returns the updated model, or 400 if the model is invalid. differs from PUT in that PATCH can accept a subset of attributes. model attributes should be passed in through the request body.

$ curl -X PUT -d 'text=patched' /posts/512a40f1163dcb4bce000001
  {
    "text": "patched",
    "username": "catshirt",
    "_id": "512a40f1163dcb4bce000001"
  }
DELETE /:urlRoot/:id
$ curl -X DELETE /posts/512a40f1163dcb4bce000001
  OK

deletes a model and returns 200, or 404 if the model is not found.

collections

when given a collection, brainy will create an HTTP endpoint for reading and querying models as a collection. we will use the following collection definition for endpoint examples.

var Posts = Backbone.Collection.extend({
  url: '/posts',
  model: Post
});
GET /:url

returns the entire collection of models, or [] if empty. additional parameters passed in the querystring are interpreted as a mongodb query. if a query is used, the collection will only return models that satisfy the query.

$ curl -X GET /posts
  [{
    "text": "foo",
    "username": "catshirt",
    "_id": "512a40f1163dcb4bce000001"
  }]
$ curl -X GET /posts?text[$regex]=boo
  []

resources

resources are simply any Backbone model or collection. because brainy doesn't modify Backbone api in any way, resources will behave as expected in the browser. brainy instead creates an additional context on the server in which your resources can operate.

idAttribute

because brainy's primary data store is mongodb, you should assign _id to your model's idAttribute.

var Post = Backbone.Model.extend({
  idAttribute: '_id'
});

urlRoot

similar to a collection's url, urlRoot is required for all models. urlRoot is used to infer a mongodb collection name and expose HTTP endpoints.

var Post = Backbone.Model.extend({
  idAttribute: '_id',
  urlRoot: '/posts'
});

url

per usual Backbone practice, collections all require a url property or function. brainy relies on this well for two purposes: inferring a mongodb collection name, and exposing HTTP endpoints.

var PostsCollection = Backbone.Collection.extend({
  urlRoot: '/posts',
  model: Post
});

fetch

resource fetch() functions behave as you'd expect them to in Backbone, but the nature of the HTTP api audments this function with an additional querying feature. by nature of jQuery's ajax method, the `data` value of `options` is serialized as a query string. the brainy api uses this query string to execute mongodb queries, meaning you can search collections through fetch:

var posts = new PostsCollection;
posts.fetch({
  data: {
    text: {
      $regex: 'boo'
    }
  }
});

validate

validate, like all resource functions, runs on both the client and the server. if a model invalidates on the client, the request is not sent. if the request is sent, validate is run again by the server. this means your api can validate requests sent from any client.

var Post = Backbone.Model.extend({
  idAttribute: '_id',
  urlRoot: '/posts',
  validate: function(attrs) {
    var err = undefined;
    if (!attrs.text) {
      err = 'text cannot be empty';
    }
    return err;
  }
});

in the example above, validate behaves as expected on the client, triggering the 'invalid' event on the model. validate on the server however, instead returns a 400 response code with the invalidation error in the response body.

Backbone only supports synchronous validation. this means asynchronous validation (querying other resources, etc) cannot be acheived through validate. a fair work around to this is to add your asynchronous checks to save or fetch, only calling the super function if your check passes.