An isomorphic web framework built on top of AngularJS, express and PostgreSQL.

An isomorphic web framework built on top of Node.js, AngularJS, PostgreSQL, Express, Knex.js and more.

Dependency injection is also available on the back-end side, which makes creating Express routes super easy.'/api/users', function(UserModelrequest) {
    return UserModel.create(request.body)
        .then(function(user) {
            return user;

All your services are available on both the front- and back-end. This makes it easy to create isomorphic services. The below TextService is available on the front- and back-end.

app.service(function TextService() {
    this.slugify = function(text) {
        return text.toString().toLowerCase()
        .replace(/\s+/g, '-')
        .replace(/[àáâãäå]+/g, 'a')
        .replace(/[èéêë]+/g, 'e')
        .replace(/[ìíîï]+/g, 'i')
        .replace(/[òóôõö]+/g, 'o')
        .replace(/[ùúûü]+/g, 'u')
        .replace(/\\s+/g, "")
        .replace(/æ+/g, "ae")
        .replace(/ç+/g, "c")
        .replace(/ñ"+/g, 'n')
        .replace(/œ+/g, "oe")
        .replace(/[ýÿ]+/g, 'y')
        .replace(/\\W+/g, '')
        .replace(/[^\w\-]+/g, '')
        .replace(/\-\-+/g, '-')
        .replace(/^-+/, '')
        .replace(/-+$/, '');

It's easy to declare your data model and all associations (one-to-one, one-to-many and many-to-many).

var fire = require('fire');
var app ='todomvc');
app.model(function TodoItem(TodoListModel) {
    this.list = [this.BelongsTo(TodoListModel), this.Required]; = [this.String, this.Required];
    this.completed = [this.Boolean, this.Required, this.Default(false)];
    this.createdAt = [this.DateTime, this.Default('CURRENT_TIMESTAMP')];
app.model(function TodoList(TodoItemModel) {
    this.items = [this.HasMany(TodoItemModel), this.AutoFetch];
    this.createdAt = [this.DateTime, this.Default('CURRENT_TIMESTAMP')];

It's trivial to create A/B tests. Tests work with your existing analytics service e.g. Mixpanel.

app.controller('/', function StartController(TextOfButtonTest$scope) {
    if(TextOfButtonTest.getVariant() == 'A') {
        $scope.buttonText = 'Register for FREE';
    else {
        $scope.buttonText = 'Register now';

All changes to your data model are applied in migrations. Migrations are automatically generated based on the changes of your models.

exports = module.exports = Migration;
function Migration() {
Migration.prototype.up = function() {
    this.models.createModel('TodoItem', {
        id: [this.UUID, this.CanUpdate(false)],
        list: [this.BelongsTo(this.models.TodoList), this.Required],
        name: [this.String, this.Required],
        createdAt: [this.DateTime, this.Default('CURRENT_TIMESTAMP')]
    this.models.createModel('TodoList', {
        id: [this.UUID, this.CanUpdate(false)],
        items: [this.HasMany(this.models.TodoItem)],
        createdAt: [this.DateTime, this.Default('CURRENT_TIMESTAMP')]
Migration.prototype.down = function() {

All your config is stored in the .env file, but you can use the fire command line interface to manage the config. You can e.g. set NODE_ENV to production by invoking fire config:set NODE_ENV=production or view your config by invoking fire config.

$ fire config
NODE_ENV:     development
SESSION_KEYS: XI4frrvs+z1JU9auFEmOIAtM...FL3di8Eysw==
DATABASE_URL: postgres://martijndeh@

Easily create workers which execute background tasks.

function MailWorker() {
MailWorker.prototype.sendResetPasswordMail = function(userresetPassword) {
    var defer = Q.defer();
    mandrill('/messages/send', {
        message: {
            to: [{
    }, defer.makeNodeResolver());
    return defer.promise;

It's easy to queue a background task which the worker executes in a worker process.'/api/forgot-password', function(requestMailWorkerUserModel) {
    return UserModel.getMe(request).then(function(user) {
        return MailWorker.sendResetPassword(user, user.resetPassword);

Install Node on Fire globally:

$ npm install -g fire

Create your first app:

$ fire apps:create helloworld

Run your app:

$ cd helloworld/
$ fire run

For additional reading and documentation please visit

Open an issue over at GitHub or send a tweet to @nodeonfire.