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 and Angular, 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 FeathersJS 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. The FeathersJS website states that 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, that is 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 that 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’s 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 feather 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.
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 to 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 – authentication, for example – 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 its 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 middleware in Express. To see the order of execution, you can simply throw in a console.log('Running')
before the return
statement of the service's 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 because 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, a WebSockets issue may also arise. 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 says. 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 is a 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 that holy grail status starts to fade, and think about what's next.
FeathersJS and Ably
This deep dive has shown that while FeathersJS might be a good option for some use cases, the framework does not have a sufficiently wide palette of functionality to enable you to easily build or scale complex realtime data pipelines. In such scenarios, you’re better off using a more mature, feature-rich solution that can remove operational complexity from your shoulders.
Ably is an enterprise-ready pub/sub messaging platform. We make it easy to efficiently design, quickly ship, and seamlessly scale critical realtime functionality delivered directly to end-users. Everyday we deliver billions of realtime messages to millions of users for thousands of companies.
We power the apps people, organizations, and enterprises depend on everyday like Lightspeed System’s realtime device management platform for over seven million school-owned devices, Vitac’s live captioning for 100s of millions of multilingual viewers for events like the Olympic Games, and Split’s realtime feature flagging for one trillion feature flags per month.
We’re the only pub/sub platform with a suite of baked-in services to build complete realtime functionality such as showing a driver’s live GPS location on a home-delivery app, instantly loading the most recent score when opening a sports app, and automatically handling reconnection when swapping networks.
Our platform is mathematically modelled around Four Pillars of Dependability so we’re able to ensure messages don’t get lost while still being delivered at low latency over a secure, reliable, and highly available global edge network.
Developers from startups to industrial giants choose to build on Ably because they simplify engineering, minimize DevOps overhead, and increase development velocity.
References and further reading
Ably Engineering blog, which covers a wide spectrum of engineering challenges related to building a globally-distributed serverless realtime infrastructure at scale
Recommended Articles
Guide to Django Channels: What it is, pros and cons and use cases
Discover how Django Channels can be used to power realtime experiences like chat and multiplayer collaboration, its pros and cons, and suitable alternatives.
gRPC
Learn about gRPC, an open source remote procedure call framework for building microservices, supporting both an asynchronous and synchronous communication model
SignalR
SignalR is an open-source ASP.NET library that enables servers to send async messages to client-side web applications.