Nodulator

Master : Build Status

Develop: Build Status

NPM

NPM

Under heavy development

Concept

Nodulator is designed to make it more easy to create highly modulable applications, built with REST APIs and with integrated ORM in CoffeeScript.

You must understand express basics for routing

Open exemple.coffee to see a full working exemple

Released under GPLv2


Jump To


Philosophy

Nodulator is a project that is trying to make a big overlay to every traditionnal packages used to make REST client/server applications in CoffeeScript.
Its main goal is to give developers a complex REST routing system, an ORM and high-level modules, encapsulating every classic behaviour needed to create complex projects.

Its core provides everything needed to build powerfull and highly modulable REST APIs, and allow the developer to reuse his code through every projects.

With this framework, you will never loose 10 or 20 hours anymore boostraping a project from scratch or looking for the right technology to implement.
You will never have headache anymore trying to combine socket.io and passport to keep track of your session with your sockets (for exemple),
or you will never have to consider assets management,
and with the integrated Project Generation you will never need to manage your Nodulator modules dependencies.

You need to add authentication logic to your open/public API ? Look for Nodulator-Account !

You need to add socket.io support ? Look for Nodulator-Socket !

If you don’t find your desired module, just build it !

Nodulator is like a lego game, instead of learning how to use a given technology and how to combine it with thoses you often use,
it allows you to manipulate simple concepts like adding a Account concept to your application(for exemple), and so adding authentication and permission logic to your app.

Also, each brick or layer of a Nodulator application is highly linked to every others.
For exemple, when you add Nodulator-Account module to your app, if you have already included Nodulator-Angular it will automaticaly add everything needed
to handle angular authentication (it will add a separate view, some directives and a user service). Have you added Nodulator-Socket ?
So Nodulator-Angular will also be highly linked to your server’s models, by providing a socket interface to your server Resource.

Check the Jump To section !


Features


Compatible modules


Installation

Just run :

npm install nodulator

Or check the Project Generation section

After you can require Nodulator as a module :

Nodulator = require 'nodulator'

Quick Start

Here is the quickiest way to play around Nodulator

_ = require 'underscore'
Nodulator = require 'nodulator'

class PlayerRoute extends Nodulator.Route.DefaultRoute
  Config: ->

    # We create: GET => /api/1/{resource_name}/usernames
    # Get a list of every players' usernames
    @Get '/usernames', (req, res) =>

      # There is a @resource property, containing attached Resource class
      @resource.ListUsernames (err, usernames) ->
        return res.status(500).send err if err?

        res.status(200).send usernames

    # We call super() to apply Nodulator.Route.DefaultRoute behaviour
    # We called '/usernames' route before, so it won't be override by
    # default route GET => /api/1/{resource_name}/:id
    super()

    # We create: PUT => /api/1/{resource_name}/:id/levelUp
    @Put '/:id/levelUp', (req, res) =>

      # For DefaultRoute routes with '/:id/*',
      # Fetch the corresponding Resource and put the instance in @instance
      # (here it can be called 'req.player' but we want to stay generic)
      @instance.LevelUp (err, resource) ->
        return res.status(500).send err if err?

        res.status(200).send resource.ToJSON()

# We create a resource, and we attach the PlayerRoute
class PlayerResource extends Nodulator.Resource 'player', PlayerRoute

  # We create a LevelUp method
  LevelUp: (done) ->
    @level++
    @Save done

  # And a class method to get a list of usernames
  @ListUsernames: (done) ->
    @List (err, players) ->
      return done err if err?

      done null, _(players).pluck 'username'

# And we Init()
PlayerResource.Init()

Go inside your project folder, copy this POC in a test.coffee file and type in:

$> coffee test.coffee

It will run your project on port 3000 by default

Then open your favorite REST API Client (Postman for Chrome is my favorite)

and try the following routes :

(Assuming full url is always of the following form : "http://localhost:3000/api/1/[...]")
Each route is of the following form :

{VERB}  {URL}                       ({PARAMS})                       => {ANSWER}

POST    '/api/1/players'            {username: 'test1', level: 1}    => {id: 1, username: 'test1', level: 1}
POST    '/api/1/players'            {username: 'test2', level: 1}    => {id: 2, username: 'test2', level: 1}

GET     '/api/1/players'                                             => [{id: 1, username: 'test1', level: 1},
                                                                         {id: 2, username: 'test2', level: 1}]

GET     '/api/1/players/1'                                           => {id: 1, username: 'test1', level: 1}
GET     '/api/1/players/2'                                           => {id: 2, username: 'test2', level: 1}

PUT     '/api/1/players/2/levelUp'  {}                               => {id: 2, username: 'test2', level: 2}
PUT     '/api/1/players/2/levelUp'  {}                               => {id: 2, username: 'test2', level: 3}

GET     '/api/1/players/usernames'                                   => ['test1', 'test2']

PUT     '/api/1/players/2'          {username: 'notAUsername'}       => {id: 2, username: 'notAUsername', level: 3}

GET     '/api/1/players/usernames'                                   => ['test1', 'notAUsername']

DELETE  '/api/1/players/1'          {}                               => {id: 1, username: 'test1', level: 1}

GET     '/api/1/players/usernames'                                   => ['notAUsername']

Configuration

First of all, the configuration process is absolutly optional.

If you don’t give Nodulator a config, it will assume you want to use SqlMem DB system, with no persistance at all. Usefull for heavy tests periods.

If you prefere to use a persistant system, here is the procedure :

Nodulator = require 'nodulator'

Nodulator.Config
  dbType: 'Mongo'     # You can select 'SqlMem' or 'Mongo' or 'Mysql'
  dbAuth:             # Fields needed if Mongo or Mysql
    host: 'localhost'
    database: 'test'
    port: 27017       # From there, can be ignored. Default values taken
    user: 'test'      # |
    pass: 'test'      # |_

Nodulator provides 2 main Objects :

Nodulator.Resource
Nodulator.Route

Resource

Basics

A Resource is a class permitting to retrive and save a model from a DB.

Here is an exemple of creating a Resource

PlayerResource = Nodulator.Resource 'player'

PlayerResource.Init()

Here, it creates a PlayerResource, linked with a players table in DB (if any)

Note the ‘s’ concatenated with the Resource name. Its the real Resource.name of a resource

For the same name without the ‘s’, there is a Resource.lname property.

/!\ Never forget to call Init() /!\

It’s needed in order to prepare the Resource. All the Nodulator‘s magic is inside this call.

If you forget it :
- The Resource will NOT be linked to Route (if any)
- It will NOT prepare Account system (if any)
- It will NOT prepare inheritance system so you won’t be able to inherit from it
- It will NOT be linked to a corresponding table in DB
- Nothing will work or happend. Ever.

/!\ Please read this section again /!\ (beware of infinite loops :p)

You can pass several params to Nodulator.Resource :

Nodulator.Resource name [, Route] [, config]

You can attach a Route and/or a config object to a Resource.

Class methods

Each Resource provides some ‘Class methods’ to manage the specific model in db :

PlayerResource.Fetch(id, done)
PlayerResource.FetchBy(constraints, done)
PlayerResource.List(id, done)
PlayerResource.ListBy(constraints, done)
PlayerResource.Deserialize(blob, done)
PlayerResource.Create(blob, done)

The Fetch method take an id and return a PlayerResource intance to done callback :

PlayerResource.Fetch 1, (err, player) ->
  return console.error err if err?

  [...] # Do something with player instance

You can also call FetchBy method to give a specific field to retrive.
It can be unique, or the first occurence in DB will return (depends on DB implementations)

You can list every models from this Resource thanks to List call :

PlayerResource.List (err, players) ->
  return console.error err if err?

  [...] # players is an array of PlayerResource instance

Like FetchBy, you can ListBy a specific field.

The Deserialize method allow to get an instance of a given Resource.

Never use new operator directly on a Resource, else you might bypass the relationning system.

Deserialize method is used to make pre-processing work (like fetching related models) before instantiation.

Create method is an alias to Deserialize followed by a Save.

Instance methods

A player instance has some methods :

player.Save(done)
    Save the model in DB. The callback take 2 arguments : (err, instance) ->

player.Delete(done)
    Delete the model from the DB. The callback take 1 argument : (err) ->

player.Serialize()
    Get every object properties, and return it in a new object.
    Generaly used to get what to be saved in DB.

player.ToJSON()
    By default, it calls Serialize().
    Generaly used to get what to send to client.

Schema and Validation

By default, every Resource is schema less. It means that you can put almost anything into your Resource.

It can obviously be schema less only for DB systems that allows it. When using MySQL for exemple, you’ll have
to define a schema and validation rules if you don’t want your server to answer raw SQL errors for non existant fields

To make a Resource to respect a given schema, you just have to define a schema field into Resource configuration

config =
  schema:
    foo:
      type: 'int'
    bar:
      type: 'string'
      optional: true

Differents types are
- bool
- int
- string
- date
- email

By default, each fields is required, but you can make one field optional with the optional field. It will never complain if this field is not present, but if it is,
it will check for its validity:
- For every post routes (if any) it will check for every schema fields validity (each one in the model definition, and returns an error if any is missing or invalid)
- For every put routes (if any) it will check for each request fields validity (each one in the client request, and returns an error if any is invalid)

Model association

You can make associations between Resource. For making a Resource to be automaticaly fetched when querying another, you can add it to its schema :

config =
  schema:
    foo:
      type: 'int'
    barId:
      type: 'int'
    bar:
      type: BarResource
      localKey: 'barId'

class TestResource extends Nodulator.Resource 'test', config

TestResource.Init()

# Fetch TestResource with id == 1
TestResource.Fetch 1, (err, test) ->
  # Will print for exemple : {id: 1, foo: 12, barId: 1, bar: {id:1, barProperty: 'test'}}
  console.log test

Overriding and Inheritance

You can inherit from a Resource to override or enhance its default behaviour, or to make a complex class inheritance system built on Resource

Override default behaviour

In CoffeeScript its pretty easy:

class UnitResource extends Nodulator.Resource 'unit'

  # We create a new instance method
  LevelUp: (done) ->
    @level++
    @Save done

  # We override default 'List' method
  @List: (done) ->
    @ListBy {life: 10}, (err, units) ->
      return done err if err?

      done null, units

  UnitResource.Init()

Abstract class

You can define an abstract class, that won’t be attached to any model in DB or any Route

class UnitResource extends Nodulator.Resource 'unit', {abstract: true}
  [...]

UnitResource.Init();

Of course, abstract classes are only designed to be inherited. (Please note that they can’t have a Route attached)

Complex inheritance system

Given the last exemple, here is a class that inherits from UnitResource

# Note the call to 'Extend()' method
class PlayerResource extends UnitResource.Extend 'player'

  # Give PlayerResource a new beheviour
  NewBehaviour: (args, done) ->
    [...]

  # Overriding existing UnitResource LevelUp()
  LevelUp: (done) ->
    [...]

PlayerResource.Init();

You can call the Extend() method either from a full Resource or from an abstract one.

Please note that if both parent and child are full Resource, both will have corresponding model available from ORM (here units and players)

So be carefull when creating extended Resource, and think about abstract !


Route

Route Object

Nodulator provides a Route object, to be attached to a Resource object in order to describe routing process.

class UnitResource extends Nodulator.Resource 'unit', Nodulator.Route

There is no need of Init() here. Every Route is initiated and configured when its attached Resource is.

Default Nodulator.Route do nothing. You have to inherit from it to describe routes :

class UnitRoute extends Nodulator.Route

  # Override the Config() method
  Config: ->

    # And never forget to call the super()
    super()

    # Here we define: GET => /api/1/{resource_name}/:id
    @Get '/:id', (req, res) =>

      # The @resource field points to attached Resource
      @resource.Fetch req.params.id, (err, unit) ->
        return res.status(500).send err if err?

        res.status(200).send unit.ToJSON()

    # Here we define: POST => /api/1/{resource_name}
    @Post (req, res) ->
      res.status(200).end()

This Route, attached to a Resource (here UnitResource) add 2 endpoints :

GET  => /api/1/units/:id
POST => /api/1/units

Each Route have to implement a Config() method, calling super() and defining routes thanks to ‘verbs’ route calls (@Get(), @Post(), @Put(), @Delete(), @All()).

Here are all ‘verb’ route calls definition :

Nodulator.Route.All     [endPoint = '/'], [middleware, [middleware, ...]], callback
Nodulator.Route.Get     [endPoint = '/'], [middleware, [middleware, ...]], callback
Nodulator.Route.Post    [endPoint = '/'], [middleware, [middleware, ...]], callback
Nodulator.Route.Put     [endPoint = '/'], [middleware, [middleware, ...]], callback
Nodulator.Route.Delete  [endPoint = '/'], [middleware, [middleware, ...]], callback

Default Route Object

Nodulator provides also a standard route system for lazy : Nodulator.Route.DefaultRoute.
It setups 5 routes (exemple when attached to a PlayerResource) :

GET     => /api/1/players       => List
GET     => /api/1/players/:id   => Get One
POST    => /api/1/players       => Create
PUT     => /api/1/players/:id   => Update
DELETE  => /api/1/players/:id   => Delete

Route Inheritance

You can inherit from any route object :

class Test1Route extends Nodulator.Route
class Test2Route extends Nodulator.Route.DefaultRoute
class Test3Route extends Test2Route
class Test4Route extends Test3Route

And you can override existing route by providing same association verb + url. Exemple :

class TestRoute extends Nodulator.Route.DefaultRoute
  Config: ->
    super()

    # Here we override the default GET => /api/1/{resource_name}/:id
    @Get '/:id', (req, res) =>
      [...]

Db Systems

Abstraction

We defined a driver interface for some DB implementations.

It’s based on SQL Table concept. (see lib/connectors/sql/index.coffee)

Table.Find(id, done)
Table.FindWhere(fields, where, done)
Table.Select(fields, where, options, done)
Table.Save(blob, done)
Table.Insert(blob, done)
Table.Update(blob, where, done)
Table.Delete(id, done)

Every Resource have an associated Table instance that links to the good table/document in the good DB driver system

Mysql

Built-in MySQL implementation (node-mysql) for Nodulator

Check lib/connectors/sql/Mysql.coffee

MongoDB

Built-in MongoDB implementation (mongous) for Nodulator

Check lib/connectors/sql/Mongo.coffee

SqlMem

Special DB driver, built on RAM.

It provides same options as others systems do, but nothing is stored. When you stop the server, everything is deleted.

Check lib/connectors/sql/SqlMem.coffee


Other stuff

Bus

There is a Nodulator.bus object that is basicaly an EventEmitter. Every objects in Nodulator use this bus.

Here are the emitted events:

Exemple

PlayerResource = Nodulator.Resource 'player'

Nodulator.on 'new_player', (player) ->
  [...] # Do something with this brand new player

You can override default Bus by setting new class to Nodulator.Bus :

Nodulator = require 'nodulator'
NewBus = require './NewBus'

Nodulator.Bus = NewBus

Always set new Bus before any new Resource call or any added Module


Modules

Usage

To inject a module into Nodulator, preceed this way :

Nodulator = require 'nodulator'
ModuleName = require 'nodulator-ModuleName'

Nodulator.Use ModuleName

Replace ModuleName with the module’s name you want to load

Module Hacking

If you want to create a new module for Nodulator, you have to export a single function, taking Nodulator as parameter :

module.exports = (Nodulator) ->
  [...] # Your module here

You can extend anything you want, as the whole Nodulator object is passed to your function.

Be carefull to server/loadOrder.json.

Watch how other modules are made !


Project Generation

You can get global Nodulator :

$> npm install -g nodulator
$> Nodulator
Usage: Nodulator (init) | ((install |  install-dev | remove) moduleName1 [, moduleName2 [, ...]])

Nodulator provides a way of installing Nodulator, modules and dependencies easely

# If no arguments, install or remove Nodulator
$> Nodulator install
$> Nodulator install-dev
$> Nodulator remove

# Will install nodulator-angular and every dependencies (if any)
$> Nodulator install angular

# Will install nodulator-angular, nodulator-account, and all their dependencies (if any)
$> Nodulator install angular account

# Will create local link instead of a full install of nodulator-angular and every dependencies (if any)
# It's used to avoid reinstalling locally a Nodulator package under development
$> Nodulator install-dev angular

# Will remove nodulator-socket
$> Nodulator remove socket

Then you can launch the init process :

$> Nodulator init

It creates the following structure if non-existant :

main.coffee
package.json
settings/
server/
├── index.coffee
├── loadOrder.json
├── processors/
│   └── index.coffee
└── resources/
    └── index.coffee

And then find for every Nodulator modules installed, and call their respective init method.

It generate a main.coffee and a package.json with every modules pre-loaded.

The server folder is auto-loaded (check server/index.coffee and every index.coffee in subfolders).

Folders load order is defined in server/loadOrder.json, and is automaticaly managed by new modules installed (they care of the order)

You can immediately start to write Resource in server/resources !


Developers

Never forget that I’m always available at champii.akronym@gmail.com for any questions


Contributors


DOC

Nodulator

  Properties :
    Nodulator.app       => the express app
    Nodulator.express   => the express module
    Nodulator.passport  => the passport module
    Nodulator.server    => the http server
    Nodulator.authApp   => if this app handle passport authentication
    Nodulator.appRoot   => the app root path
    Nodulator.bus       => official bus (EventEmitter)
    Nodulator.Route     => Route object

  Nodulator.Resource(resourceName, [Route], [config])

    Create the resource Class

  Nodulator.Config(config)

    Change config

  Nodulator.Use(module)

    Inject a module inside Nodulator

  Nodulator._ListEndpoints(done)

    DEBUG PURPOSE
    List every api endpoint added by application

Resource

(Uppercase for Class, lowercase for instance)

  Resource.Fetch(id, done)

    Take an id and return it from the DB in done callback: (err, resource) ->

  Resource.FetchBy(constraints, done)

    Take an object representing contraints, and return first row from the DB in done callback: (err, resource) ->

  Resource.List(done)

    Return every records in DB for this resource and give them to done: (err, resources) ->

  Resource.ListBy(constraints, done)

    Take an object representing constraints, and return every row from the DB in done callback: (err, resources) ->

  Resource.Deserialize(blob, done)

    Method that take the blob returned from DB to make a new instance

  Resource.Create(blob, done)

    Alias for Deserialize and Save

  resource.Save(done)

    Save the instance in DB

    If the resource doesn't exists, it create and give it an id
    It return to done the current instance


  resource.Delete(done)

    Delete the record in DB, and return affected rows in done

  resource.Serialize()

    Return every properties defined in the schema,
    else if no schema is defined it will take every properties not stating with '_' (the private ones)
    This method is used to get what must be saved in DB

  resource.ToJSON()

    This method is used to get what must be send to client
    Call @Serialize() by default, but can be overrided

Route

  route.Get     [url = ''], [middleware, [middleware, [...]]], done)
  route.All     [url = ''], [middleware, [middleware, [...]]], done)
  route.Post    [url = ''], [middleware, [middleware, [...]]], done)
  route.Put     [url = ''], [middleware, [middleware, [...]]], done)
  route.Delete  [url = ''], [middleware, [middleware, [...]]], done)

    Create a route.

    'url' will be concatenated with '/api/{VERSION}/{RESOURCE_NAME}'. Optional
    'middleware' are optionals
    'done' is the express app callback: (req, res, next) ->

  route.Config()

    Called when a Route is associated with a Resource.
    This call prepare every routes, and must be inherited.


ToDo

By order of priority


ChangeLog

XX/XX/XX: current (not released yet)
- Nothing

14/04/15: v0.0.15
- Minor changes in Route to fit Nodulator-Account new release

10/04/15: v0.0.14
- Resource ‘user’ is no longer a reserved word
- Resources with name finishing with ‘y’ are now correctly put in plurial form in route name

09/04/15: v0.0.13
- Better model association and validation
- Pre-fetched resources in Route.All() are now put in @instance instead of req[@resource.lname]
- Updated README
- Updated Mongo driver

20/01/15: v0.0.12
- Fixed bug on FetchBy

20/01/15: v0.0.11
- Fixed tests
- Added travis support for tests
- Added model associations
- Added schema and model validation
- Changed FetchBy and ListBy prototype. Now take an object instead of a key/value pair.
- Added Create() method into Resource
- Added limit and offset to both Mysql and SqlMem

07/01/15: v0.0.10
- Added Philosophy section
- Added multiple package name support in package generation
- Fixed some bugs with modules

03/01/15: v0.0.9
- Separated AccountResource into a new module Nodulator-Account
- Changed README

02/01/15: v0.0.8
- Fixed Route middleware issue

02/01/15: v0.0.7
- Separated Socket into a new module Nodulator-Socket
- Added new methods for @Get(), @Post(), @Delete(), @Put(), @All() in Route
- Replace old method @All() into @_All(). Is now a private call.
- Improved README (added Modules section)
- Global Nodulator now manage dependencies installation