feathers

Shared REST and real-time APIs with Express.

Feathers - Let your applications fly!

Shared REST and real-time APIs with Express.

Feathers extends Express 4, one of the most popular web frameworks for NodeJS. It makes it easy to create shared RESTful web services and real-time applications using SocketIO and several other NodeJS websocket libraries.

If you are not familiar with Express head over to the Express Guides to get an idea. Feathers works the exact same way except that var app = require('express')(); is replaced with var app = require('feathers')(). This means that you can literally drop Feathers into your existing Express 4 application and start adding new services right away. The following guide will walk through creating a basic Todo REST and websocket API with Feathers and MongoDB and also explain how to add authentication and authorization. For additional information also make sure to read through the API documentation later.

To get started with this guide, lets create a new folder and in it

npm install feathers

The most important concept Feathers adds to Express is that of services. Services can be used just like an Express middleware function but instead are JavaScript objects that provide at least one of the following methods:

var myService = {
  findfunction(paramscallback) {},
  getfunction(idparamscallback) {},
  createfunction(dataparamscallback) {},
  updatefunction(iddataparamscallback) {},
  patchfunction(iddataparamscallback) {},
  removefunction(idparamscallback) {},
  setupfunction(apppath) {}
}

This object can be registered like app.use('/my-service', myService) which - if configured - makes it available as a REST endpoint at /my-service and also through websockets. As usual in NodeJS, callback has to be called with the error (if any) first and the data as the second parameter.

With those methods available we can implement a very basic Todo service that returns a single Todo using the id passed to the get method:

// app.js 
var feathers = require('feathers');
var app = feathers();
var todoService = {
  getfunction(idparamscallback) {
    // Call back with no error and the Todo object 
    callback(null, {
      id: id,
      text: 'You have to do ' + id + '!'
    });
  }
};
 
app.configure(feathers.rest())
  .use('/todos', todoService)
  .listen(3000);

After running

node app.js

You can go to localhost:3000/todos/dishes and should see the following JSON response:

{
  "id": "dishes",
  "text": "You have to do dishes!"
}

As you might have noticed, service methods mainly reflect basic CRUD functionality. Following up is a longer example with comments for implementing a complete Todo service that manages all Todos in memory:

// todos.js 
module.exports = {
  // The current id counter 
  id: 0,
  // An array with all todos 
  todos: [],
 
  // Tries to get a single Todo by its id. 
  // Throws an error if none can be found. 
  getTodofunction(id) {
    var todos = this.todos;
 
    for(var i = 0; i < todos.length; i++) {
      if(todos[i].id === parseInt(id, 10)) {
        return todos[i];
      }
    }
 
    // If we didn't return yet we can throw an error 
    throw new Error('Could not find Todo');
  },
 
  // Return all Todos 
  findfunction(paramscallback) {
    callback(null, this.todos);
  },
 
  // Returns a single Todo by id 
  getfunction(idparamscallback) {
    try {
      callback(null, this.getTodo(id));
    } catch(error) {
      callback(error);
    }
  },
 
  // Create a new Todo 
  createfunction(dataparamscallback) {
    // Increment the global ID counter and 
    // use it as the Todos id 
    data.id = this.id++;
    this.todos.push(data);
    callback(null, data);
  },
 
  // Update (replace) an existing Todo with new data 
  updatefunction(iddataparamscallback) {
    try {
      var todo = this.getTodo(id);
      var index = this.todos.indexOf(todo);
 
      data.id = todo.id;
      // Replace all the data 
      this.todos[index] = data;
      callback(null, data);
    } catch(error) {
      callback(error);
    }
  },
 
  // Extend the data of an existing Todo 
  patchfunction(iddataparamscallback) {
    try {
      var todo = this.getTodo(id);
 
      // Extend the existing Todo with the new data 
      Object.keys(data).forEach(function(key) {
        if(key !== 'id') {
          todo[key] = data[key];
        }
      });
 
      callback(null, todo);
    } catch(error) {
      callback(error);
    }
  },
 
  // Remove an existing Todo by id 
  removefunction(idparamscallback) {
    try {
      var todo = this.getTodo(id);
      var index = this.todos.indexOf(todo);
 
      // Splice it out of our Todo list 
      this.todos.splice(index, 1);
      callback(null, todo);
    } catch(error) {
      callback(error);
    }
  }
}

The above example exports the service as a module from its own file, todos.js. This means that in app.js we can replace the previous todoService with loading that module. In order to parse JSON encoded HTTP bodies we additionally need to install and load the Express body-parser:

npm install body-parser

// app.js 
var feathers = require('feathers');
var bodyParser = require('body-parser');
 
var app = feathers();
var todoService = require('./todos');
 
app.configure(feathers.rest())
  .use(bodyParser.json())
  .use('/todos', todoService)
  .listen(3000);

Running app.js will now provide a fully functional REST API at http://localhost:3000/todos. You can test it, for example, using the Postman REST client plugin for Google chrome or via CURL:

curl 'http://localhost:3000/todos/' -H 'Content-Type: application/json' --data-binary '{ "text": "You have to do dishes!" }'

As previously mentioned, a Feathers service can also be exposed through websockets. You can either use SocketIO or Primus - an abstraction layer for differentNode websocket libraries. In the following examples we will use SocketIO.

SocketIO can be enabled by calling app.configure(feathers.socketio()). Once set up, it is possible to call service methods by emitting events like <servicepath>::<methodname> on the socket and also receive events by listening to <servicepath> <eventname> (eventname can be created, updated, patched or removed). To make it easier to test in a web page, lets also statically host the files in the current folder. app.js then looks like this:

// app.js 
var feathers = require('feathers');
var bodyParser = require('body-parser');
 
var app = feathers();
var todoService = require('./todos');
 
app.configure(feathers.rest())
  .configure(feathers.socketio())
  .use(bodyParser.json())
  .use('/todos', todoService)
  .use('/', feathers.static(__dirname))
  .listen(3000);

To test the connection, we can create an index.html file in the same folder. The example will connect to SocketIO, create a new Todo and also log when any Todo has been created, updated or patched:

<!DOCTYPE HTML>
<html>
<head>
  <title>Feathers SocketIO example</title>
</head>
<body>
  <h1>A Feathers SocketIO example</h1>
  <script src="http://localhost:3000/socket.io/socket.io.js"></script> 
  <script type="text/javascript">
    // Connect to SocketIO on the same host 
    var socket = io.connect();
 
    socket.on('todos created', function(todo) {
      console.log('Someone created a new Todo', todo);
    });
 
    socket.on('todos updated', function(todo) {
      console.log('Someone updated a Todo', todo);
    });
 
    socket.on('todos patched', function(todo) {
      console.log('Someone patched', todo);
    });
 
    socket.emit('todos::create', {
      description: 'You have to do something real-time!'
    }, {}, function(errortodo) {
      socket.emit('todos::find', {}, function(errortodos) {
        console.log('Todos from server:', todos);
      });
    });
  </script> 
</body>
</html>

After restarting, going directly to localhost:3000 with the console open will show what is happening on the HTML page. You can also see the newly created Todo at the REST endpoint localhost:3000/todos. With the page open, reating a new Todo via the REST API, for example

curl 'http://localhost:3000/todos/' -H 'Content-Type: application/json' --data-binary '{ "text": "Do something" }'

will also log Someone created a new Todo. This is how you can implement real-time functionality in any web page without a lot of magic using standardized websocket messages instead of having to re-invent your own.

Our CRUD Todo functionality implemented in the service is very common and doesn't have to be implemented form scratch every time. In fact, this is almost exactly what is being provided already in the feathers-memory module. Luckily we don't have to stop at storing everything in-memory. For the popular NoSQL database MongoDB , for example, there already is the feathers-mongodb module and if you need more ORM-like functionality through Mongoose you can also use feathers-mongoose.

npm install feathers-mongodb

With a MongoDB instance running locally, we can replace our todoService in app.js with a MongoDB storage on the feathers-demo database and the todos collection like this:

// app.js 
var feathers = require('feathers');
var mongodb = require('feathers-mongodb');
var bodyParser = require('body-parser');
 
var app = feathers();
var todoService = mongodb({
  db: 'feathers-demo',
  collection: 'todos'
});
 
app.configure(feathers.rest())
  .configure(feathers.socketio())
  .use(bodyParser.json())
  .use('/todos', todoService)
  .use('/', feathers.static(__dirname))
  .listen(3000);

And just like this we have a full REST and real-time Todo API that stores its data into MongoDB in just 16 lines of code!

To learn more about Feathers go to the feathersjs.com homepage and continue reading this guide.