Odo, whatever you want it to be

Odo - whatever you want it to be

A Nodejs framework for creating awesome things.

Install via npm

npm install --save odo

Status and current work on Trello

  1. Easy to install, use, modify, adapt and refactor
  2. Modular and lightweight components that are useful independently
  3. Strong cohesion where things change at the same rate, loose coupling where things change at a different rate
  4. Few concepts needed in any one area of the code to keep mental effort of development low

Odo hopefully follows these goals and also makes it easy to write applications that follow these goals.

Get started

Fork odo example (out of date at the moment - contact me to update)



There are two types of code in Odo: infrastructure and plugins.

Tools, frameworks and techniques make up the odo infrastructure. Using existing 3rd party frameworks and libraries has been prioritised over custom development. Where existing frameworks are not available small independent utilities have been written.

The goal of any infrastructure code is to accomplish one task, do it well and have few touch points with any other code.

Infrastructure is included as and when you need it by code you write. Components like the hub 'odo/hub' and humanize 'odo/humanize' are examples of infrastructure.

Plugins are independent features of the application loosely coupled to other plugins to make up the whole application. Usually plugins communicate through a combination of dependency injection and events.

Plugins are added to the systems array in config.cson. Express web authentication modules 'odo/auth', 'odo/auth/local', 'odo/auth/facebook' and public folder 'odo/public' are examples of plugins.

Plugins can run in four contexts: web, api, domain and projection. This technique allows the web code, database logic, and validation rules for a particular piece of information to exist in the same codebase but run in four different contexts. Having all aspects in the same codebase increases speed of development and still provides good decoupling between concepts.

Plugins are loaded on startup and either expose themselves as a class or as a plain object. Specific methods are checked for and will be called depending on the context the plugin is running in. These methods are 'web', 'api', 'domain' and 'projection'.

class ExamplePlugin
    web: =>
        console.log "I'm running in web context"
        # I can register express routes here 

Frontend plugins are registered by backend code in the 'web' context. They have the ability to bind themselves to several hooks, most importantly as single page application routes through durandal.

Backend infrastructure


All of odo uses requirejs to pull together plugins and components. In the backend node.js's require function is passed into require to include npm modules.

requirejs = require 'requirejs'
    nodeRequire: require
        odo: './node_modules/odo'
        local: './'
requirejs ['odo/bootstrap']

In the frontend requirejs is used more conventionally - included by a html file and configured by a javascript file.


For sending emails.

define ['odo/mandrill'](Mandrill) ->
    options =
            text: 'An email sent with Mandrill'
            subject: 'Email from Odo'
            from_email: 'odo@odojs.com'
            from_name: 'Odo'
            to: [
                email: 'john.smith@example.com'
                name: 'John Smith'
                type: 'to'
    new Mandrill()
        .then(-> console.log 'Email away!')
        .catch((err) -> console.log err)

Require 'odo/config' into your code to access all configuration.

Configuration is merged from five sources:

  1. A local config.cson file
  2. A cson formatted environment variable 'ODO_CONFIG'
  3. Individual environment variables like 'EXPRESS_PORT'
  4. A cson formatted environment variable 'ODO_EXAMPLE_ODO_CONFIG'
  5. Individual environment variables like 'ODO_EXAMPLE_EXPRESS_PORT'

Use the local config.cson file to add plugins to your project, add configuration that won't change per environment and add events and commands you want published and sent at the start of the application.

Use the environment variable named 'ODO_CONFIG' for database details and other values that change between development and production environments and are shared across all applications running on the same computer.

Using the domain configuration set in config.cson an additional environment variable is also loaded. For example if odo: domain: 'odo-example' is present in the config.cson file then 'ODO_EXAMPLE_ODO_CONFIG' is also parsed. Use this for configuration specific to a project.

Direct environment variables are checked, see config.coffee for a template. For example both 'EXPRESS_PORT' and 'ODO_EXAMPLE_EXPRESS_PORT' will be checked to get the port express should run on, along with any values set in 'ODO_CONFIG' and 'ODO_EXAMPLE_ODO_CONFIG'.

Eventstore provides tooling to help implement Event Sourcing. The infrastructure includes an extend method to add methods and properties to an aggregate object to support event sourcing and the CQRS pattern. It uses the eventstore library and is backed by redis.

See user.coffee for a web, domain and projection eventstore example.

The hub used for cross plugin communication. It follows the CQRS pattern separating commands from events. The hub is using redis publish and subscribe with specific channels dedicated to each application. Event listers can be bound through the receive method and command handlers can be bound through the handle method. Send and publish methods are used to send commands and publish events.

define ['odo/hub'](hub) ->
    hub.receive 'userHasDisplayName'(event, cb) ->
        console.log "A new display name! #{event.payload.displayName}"
        command: 'assignDisplayNameToUser'
            id: 34
            displayName: 'John Smith'
        event: 'subspotActivityHasIncreased'
            amount: '100%'

Plugin is a component to help load other plugins. It provides web, api, domain and projection methods that call the same named method on an array of plugins passed to it's constructor.

define [
](Plugins, plugins...) ->
    new Plugins plugins

Recorder and sequencer are classes used internally, you're welcome to use them too although they might change.

Backend plugins


Express is a web application framework. It provides structure for url handling, request parsing, templating and more.

The web context is based around express. Plugins exposed in the web context are given an opportunity to register against different parts of express to define routes and extend the express system.

define ['odo/express'](express) ->
    web: ->
        express.get '/test'(req, res) ->
            res.send 'Hello World'

'odo/express' needs to be included in the systems array after any plugins wanting web context.


Restify is a framework to build REST web services. It's very similar to express and provides accept header parsing, throttling, jsonp and more.

The api context is based around restify. Plugins exposed in the api context are given the opportunity to register against different parts of restify to define routes and extend the restify system.

define ['odo/restify'](restify) ->
    web: ->
        restify.get '/test'(req, res) ->
            res.send 'Hello World'

'odo/restify' needs to be included in the systems array after any plugins wanting api context.


Bower is a package manager for the web. Installed components will be available in a local bower_components directory.

The odo bower plugin hosts the /bower_components directory so anything you've installed with bower is available to the web.

E.g. bower install --save jquery will result in http://localhost:1234/bower_components/jquery/dist/jquery.min.js being available, depending on your express port.


Durandal is a single page application library. It uses requirejs, knockoutjs and jQuery to stitch together views and widgets that respond to anchor tags as urls.

The durandal plugin allows other plugins to register components to be used in the Front End.

define [
](module, express, durandal) ->
    web: ->
        express.route '/views'express.modulepath(module.uri) + '/public'
        durandal.register 'views/welcome'

And in a public folder is welcome.coffee and welcome.html:

define ['knockout''plugins/router'](ko, router) ->
        route: ''
        moduleId: 'views/welcome'
    class Welcome
        title: 'Welcome'
        constructor: ->
            @displayName = ko.observable 'John Smith'
<div class="test">
    <h1>Test Page</h1>
    <p>Welcome <span data-bind="text: displayName"></span><p>

See the durandal front end folder for more information.


Handlebars is a templating engine available in Nodejs and in the browser. In odo we're using it in Nodejs for rendering static files, if needed. When writing a single page app this is often not needed.

Handlebars is a plugin in the web context to register handlebars as the view engine for express and add additional functionality for handlebars including a custom render method.

define ['odo/express'](module, express) ->
    web: =>
        express.get '/test'(req, res) ->
                view: 'templates/layout'
                    title: req.user.displayName
                    displayName: 'John Smith'
                    content: 'test'

The code sample above will combine the layout template with the test template from a directory called 'templates' and pass in some information to be used when rendering.


<!DOCTYPE html>
<html lang="en">
        <meta charset="utf-8">
        <meta http-equiv="X-UA-Compatible" content="IE=edge, chrome=1" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        {{hook 'content'}}


<div class="test">
    <h1>Test Page</h1>
    <p>Welcome {{displayName}}<p>

The public plugin hosts a public directory available in your application for static assets and durandal models and views.

Passport authentication - local, google, facebook, twitter and metocean

Passport is a framework and a collection of plugins to provide authentication for Nodejs applications. It has support for most common providers such as google, facebook, twitter, and linkedin.

The passport authentication plugins provide urls and methods to authenticate a user with passport and passport plugins. Custom local, twitter, facebook, google and metocean passport plugins have been provided.