FeathersJS Deep Dive

An Examination of the FeathersJS Framework and its Realtime Functionality

Well-known for its simplicity and adaptability, new users of FeathersJS sometimes feel they have stumbled across the holy grail of a perfect framework. Its main advantage is easy integration with client-side frameworks, as well as data agnosticity. It’s highly customizable, with interface options with technologies like React, Angular and so on, and its structure allows for easy building of service-oriented apps. In this article we put the framework through its paces. By the end, you should have an outline of the advantages and disadvantages of FeathersJS, depending on your use case. Specifically, we focus on the appropriateness of FeathersHS when it comes to implementing different realtime functionalities.

What is FeathersJS?

FeathersJS describes itself as an “open source REST and realtime API layer for modern applications.” The framework comes with tools that quickly get you from prototypes to production-ready apps. FeathersJS provides realtime, RESTful and ORM support out of the box. The framework has a NodeJS server and a Javascript client that can be used in web or mobile applications. When it comes to understanding, maintaining, and scaling your applications FeathersJS sets out to be easier than a typical MVC architecture. According to the creators, this is primarily due to MVC frameworks being overly complex with a tendency to distract from the core concern of data. As FeathersJS describe on their website, when using the MVC architecture, “setting up routes, doing CRUD, and dealing with the request-response cycle feels tedious”. This is a niche FeathersJS intends to fill.

Where does it fit in?

What makes FeathersJS different from all the other JavaScript frameworks out there? When FeathersJS came into existence as an idea in 2010, its creators, David Luecke and Eric Kryski, wanted a simple framework for creating web and mobile applications. They sought to create a framework that was not in any way prescriptive, i.e. avoiding lock-in, capable of adapting as the scope of their applications grew. They also sought a framework that would scale with applications, that was simple to set up, while also being powerful enough to build modern apps. Since it was launched on GitHub in 2013, the framework has matured quite a lot – however, so have the ‘modern applications’ the framework provides for. The next parts of this article evaluate FeathersJS by use case.

How does it work?

At the heart of FeathersJS are services. Services are simply JavaScript objects or ES6 classes that represent an API endpoint. Each one implements a specific set of functions to perform CRUD operations on the data. To add on to services, Feathers offers hooks which act as middleware. These can be made to execute code before or after a service function is called, or when an error occurs within a service function. FeathersJS also sends out events, if set up, when CRUD operations are executed by services. When used with Socket.IO, WebSockets can be used to emit and receive these events allowing FeathersJS to communicate between client and server creating realtime functionality. Again, every part of Feathers is made with the goal of being easy to understand, maintain, apply – and above all adapt and add on to.

More details about the framework and how it functions can be found in Feathers JS’ documentation.

Setting Up FeathersJS

Installing FeathersJS and creating a new app can be done with the following commands:

npm install -g @feathersjs/cli
mkdir my-app
cd my-app
feathers generate app

To make things easier and more streamlined, it’s recommended to use the Feathers generator. The Feathers generator will ask a few questions such as the name of the project, the datastore to use, and others. Once the app is generated, it can be started by running `npm start`. Just five commands and you’re up and running with a barebones Feathers application. This simple process is a primary feathers in its cap.

The most popular databases are also supported by FeathersJS. An additional plus is that you can use a database that is not supported by using a custom adapter. The FeathersJS community created quite a few custom database adapters for databases not-covered by the core framework.

FeathersJS in action: REST and realtime

As said before, FeathersJS is used for REST APIs and realtime, data-driven applications. Their website brings in chat applications as an evaluative use case.

REST

Just like other frameworks, Feathers provides a way to create a REST API capable of handling a lot of data that needs to be retrieved, updated and removed. If we are to use the common use case example of a chat app, FeathersJS will be good for ensuring its smooth-functioning. Another basic functionality of a chat application is the effective storage and handling of messages. Again, this is easily provided by Feathers, which lets you create separate API endpoints, also known as ‘services’ in Feathers, for users and messages.

Feathers can also handle authentication allowing users can create an account and login. For a chat application, a user should be able to log in, as well as send and receive messages. Feathers takes away the need to rewrite the same logic over and over again – eg the authentication logic – so you can focus on making the unique parts of your application.

Realtime

Realtime functionality requires both serverside and clientside logic. On the server side, realtime functionality begins by services sending `created`, `updated`, `patched`, and `removed` events whenever the corresponding service method returns. In order to fully take advantage of realtime functionality, the client side also has to play a role in this process by properly reacting to these events sent by the server. This can be done by using bi-directional communication through the use of Socket.io or Primus. Both of these are supported by Feathers right out of the box.

Let’s take a look at a basic example of realtime functionality in FeathersJS using WebSockets. Here’s the structure of this project:

- public (directory)
-- client.js
-- index.html
app.js


In @app.js@:
const feathers = require('@feathersjs/feathers');
const socketio = require('@feathersjs/socketio');
const memory = require('feathers-memory');

// Create a Feathers application
const app = feathers();

// Configure the Socket.io transport
app.configure(socketio());

// Create a channel that will handle the transportation of all realtime events
app.on('connection', connection => app.channel('everybody').join(connection));

// Publish all realtime events to the `everybody` channel
app.publish(() => app.channel('everybody'));

// To keep things simple, create a messages service and just store the messages in memory instead of a database
app.use('messages', memory());

// Start the server on port 3030
app.listen(3030);

In index.html:

<!DOCTYPE html>
<html>
   <head>
      <meta charset="UTF-8">
      <title>Real Time Message Example</title>
   </head>
   <body>
      <form id="send">
         <input type="text" name="message">
         <button type="submit">Send</button>
      </form>

      <script type="text/javascript" src="//cdnjs.cloudflare.com/ajax/libs/socket.io/2.0.4/socket.io.js"></script>
      <script type="text/javascript" src="//unpkg.com/@feathersjs/client@^3.0.0/dist/feathers.js"></script>
      <script src="client.js"></script>
   </body>
</html>

Lastly, in client.js:

/* global io */

// Create a websocket connecting to our Feathers server created in `app.js`
const socket = io('http://localhost:3030');

// Listen to new messages being created and add them to the DOM
socket.on('messages created', message => {
   const messageElem = document.createElement('p');
   messageElem.innerText = message.text;
   document.body.appendChild(messageElem);
});

document.addEventListener('submit', ev => {
      if (ev.target.id == 'send') {
         const input = document.querySelector('[name="message"]');

      ev.preventDefault();

      // Create the new message
      socket.emit('create', 'messages', {
      text: input.value
   });

      input.value = '';
   }
});

To test this out, you’ll need to start up an http server (http-server public/) and run node app.js to start the backend server.

Despite being very simple looking, we just created a realtime application using FeathersJS and WebSockets. Now whenever a message is sent, the page will update in realtime for all connected clients.

Services, Hooks, and Events in Action

Let’s take a look at a simple example of a service in FeathersJS:

const feathers = require('@feathersjs/feathers');
const app = feathers();

app.use('messages', {
   async get(message) {
       return {
           message,
           text: `New Message: ${message}`
       };
   }
});

async function getMessage(message) {
   const service = app.service('messages');
   const result = await service.get(message);

   console.log(result);
}

getMessage('Hello World!');

It begins with an import of FeathersJS, and from there we create a new instance of a Feathers application by calling `feathers()`. Using `app` we can create a service, which we call `messages`, that will have a single function `get`. All this function does is take a message and print it out. That’s all that we need to make a simple, functioning Feathers service.

That service can now be used, which we’re doing within the getMessage function. The app.service('messages') line is simply grabbing the messages service we just created. Using that, we can call it’s get function and await the result of it since it is an async function. Once we successfully retrieve the result, it gets printed out displaying { message: 'Hello World!', text: 'New Message: Hello World!' }.

Let’s take a look at a sample of hooks. For this, we’ll just modify the getMessage function a bit:

async function getMessage(message) {
   const service = app.service('messages').hooks({
       before(context) {
           console.log('before hook');
       },
       after(context) {
           console.log('after hook');
       },
   });

   const result = await service.get(message);

   console.log(result);
}

When this is run, you’ll notice “before hook” and “after hook” are printed. As the names suggest, the “before” hook gets run before the services function is executed, and the “after” hook gets run after it’s executed. This is similar to middlewares in express. To see the order of execution, you can simply throw in a console.log('Running') before the return statement of the services get function. You’ll end up with an output like this:

before hook
Running
after hook

Finally, let’s take a look at events in FeathersJS:

const feathers = require('@feathersjs/feathers');
const app = feathers();

app.use('messages', {
   async create(message) {
       return message
   }
});

const service = app.service('messages')

service.on('created', (message, context) => {
   console.log('created', message);
});

service.create({
   message: 'This is a message'
})

When run, created { message: 'This is a message' } will be printed.

All services automatically emit events for created, patched, removed, and updated on the successful execution of the respective service function. If needed, you can easily emit your own custom events using service.emit('eventName', data) and listen for that custom event using service.on('eventName', {}).

With the above example, whenever the message service’s create function is called, it emits a created event that is then handled by service.on('created', {}).

Limitations – when is Feathers too lightweight?

FeathersJS is praised as a minimalist framework, however, common complaints point to this also being the framework’s biggest flaw, as so many add-ons are required. Therefore you may well encounter some limitations with FeathersJS when usage of your app gets heavy.

One of these limitations is the use of PassportJS for the authentication system which does not provide SAML authentication out of the box. If SAML authentication is necessary for your application, a community made NPM package is available that adds support for it: https://www.npmjs.com/package/passport-saml.

In comparison to Meteor, the realtime implementation in FeathersJS is less robust. This is due to FeathersJS handling realtime data at the service level in comparison to the oplog tailing being done in Meteor.

When it comes to larger scale realtime apps in FeathersJS, an issue that may arise is with WebSockets. If a large scale app is using them, the configuration may end up being complex in order to ensure they work properly at a larger scale. This is perhaps symbolic of FeathersJS’s disadvantages more widely. While its key advantage is adaptability and easy integration, eventually, having to maintain multiple components or offshoots of the system comes at a cost in terms of time.

Final Thoughts – Feathers JS for realtime

With FeathersJS, setting up a functioning realtime app and API is easy. Best of all, since FeathersJS uses Javascript, you’re able to create applications for both web, desktop (using a tool such as Electron), and mobile (using React Native). All with a similar codebase.

The point of FeathersJS is to give JavaScript users near complete freedom. Besides this, FeathersJS doesn’t force a specific tech stack on you or your application. You have the choice of a multitude of different database options right out of the box, including MongoDB and Postgres. If you need a different one, you have the ability to make your own adapter for it or use one that’s already been created by the community. And if you want or need to use multiple data stores, you can do that as well. If you have a specific frontend framework you prefer using, such as VueJS or React, you can use it alongside FeathersJS with no problem at all. You have a whole lot of freedom when it comes to add-ons that suit your use case.

If you’re someone who dislikes rewriting the same authentication code over and over again, FeathersJS handles this for you and gives you multiple different options for authentication. You can choose from authentication methods such as local (username/password), JWT, and OAuth to name a few. If you need a different method of authentication such as SAML, the community has created a few NPM packages to add on to it. If you’re feeling adventurous, create your own custom authentication system. Once again, keeping with the idea of providing the user with as much freedom as possible by never forcing a specific tech stack.

Freedom – but at what cost?

FeathersJS is, and does, exactly what it’s advertised to do. If you’re looking for a framework to help you create realtime applications easily and quickly, with the ability to quickly add on additional functionality when you need it, FeathersJS is a good choice for the upward curve. However, while this key selling point, it might eventually become the key reason not to use FeathersJS. Manually adding these functionalities to a FeathersJS application takes time – if your app scales, it will take more of your time to sync/ repair these various functions. For example, in terms of large scale realtime applications, you might run into some problems when using WebSockets. A relatively easy solution to this is to use a load balancer in order to distribute WebSocket connections to multiple servers, and use something like Redis to implement a pub/sub service allowing all of these WebSocket servers to communicate.

The problem with adding load balancers and multiple servers is the associated complexity it brings. If you’ve added on functions enough to operate a pub/sub service, maintaining a resilient pub/sub service takes time. Think global redundancy, rate limiting, history and message replay, message ordering, guaranteed delivery, presence and occupancy events, virtual connection continuity – the list goes on. While FeathersJS makes it easy to get started with realtime, if your app starts to scale these additional layers to your stack will likely cause problems, with different aspects feeling somewhat disjointed when you start to encounter bursts in usage, users demanding idempotency, message ordering and so on.

Ably’s engineering blog describes problems and solutions associated with scaling realtime apps. When you find your (or your team’s) time taken up solving and/ or provisioning for these issues, that’s when – we believe – it’s worth looking for a cloud or better still serverless solution, leaving you free to concentrate on the core functionality of your app. That said, for small-scale applications or developers starting out with realtime – until realtime functionality scales and gets complicated, there is indeed a point at which FeathersJS could fit the bill of the holy grail of frameworks. Developers can judge for themselves when holy grail status starts to fade, and think about how to move on.

Ably is a global cloud network for streaming data and managing the full lifecycle of realtime APIs. To talk about scaling your app or service, get in touch with our dev-rel team.