Feathers is an API framework for Node.js, React Native and the browser that provides a combination of design patterns and tools to easily create and use REST and real-time APIs.
Before a deep dive into Feathers, I will provide an overview of the Node.js server side framework landscape. A lot of these libraries operate at a different level of abstraction and it’s important to try and understand how to compare them.
Node.js never had one prominent server side framework rise to popularity the way Ruby had Rails. The closest comparisons perhaps are Express and Hapi.
Express is by far the most widely used server framework in Node.js, but in true Node.js spirit, it’s pretty lightweight and batteries not included. You can build pretty much anything using it, and people have, but you have to architect a lot of the aspects of your application yourself. Express only gives you a couple key concepts such as routing and handling HTTP requests. Express is therefore more comparable to Sinatra than it is to Rails.
Hapi is very similar to Express in that regard. Hapi is richer in functionality and more structured compared to Express. But ultimately it doesn’t go far beyond helping you with routing and responding to HTTP requests. It has a few more bells and whistles such as validation and error handling included. Some people like this more holistic approach, and Hapi has been popular, but personally I find it to have too much API surface area to learn and be productive with.
To make this overview comprehensive, I also wanted to mention Koa and Fastify.
Koa is very similar to Express and it’s a bit unfortunate that it split the community effort. Half the people seem to be using Koa and half using Express. I think it would have been better if the community somehow stuck together and improved the existing efforts.
Fastify is the newest project of the bunch and it attempts to improve upon the status quo by focusing on maximum performance and extensibility via richer (Hapi like?) plugin API. One cool feature of Fastify that already sets it apart from Express/Koa for me is how the plugins or middleware can be scoped to sub-apps. In Express adding a middleware in-between route definitions applies it to all of the remaining routes. In Fastify you can create a larger application from multiple sets of routes each set with their own plugins applied – this can be quite useful.
All of these frameworks are solid and are being used in production by many teams around the world. But they’re not the only kind of server side frameworks available. The frameworks I listed above, Express, Koa, Hapi and Fastify, are all lighter and less opinionated compared to something like Rails or Django. They are lower level abstractions. Rails has APIs and clear guidelines on how to handle project structure, configuration, database layer, authentication, user permissions, sending emails, logging, error handling, serialisation and even job queues. All the things that are very common in a typical web application API.
And so it’s not surprising that many attempts have been made at recreating similarly rich frameworks in Node.js. We have the likes of Nest, Adonis, Loopback, Sails, Hoodie and others. Oh, and of course, Feathers. In fact, a lot of these higher level frameworks are built directly on top of Express, our beloved lower level framework. A lot of them are also pluggable, allowing you to swap Express for Koa or Fastify.
And here we are at Feathers. What makes it special? Feathers doesn’t copy the “classic” MVC style structure found in Rails one for one like a lot of the other frameworks do. Instead Feathers uses a service centric design – an approach that I haven’t seen used commonly elsewhere. The service layer in Feathers is a protocol independent interface to the application logic. We’ll have a closer look at what that means, but this service centric approach is at the heart of Feathers and leads to all of the nice properties and capabilities.
The service centric approach is what informs us how to handle the project structure, the database layer, user permissions, sending emails, doing error handling and serialisation. These are all the things that Rails was helping us do. Except Feathers pulls it off with a much smaller core! In fact, that’s one of my favorite things about Feathers. Just like React.js or Express – Feathers provides the developer with a small set of concepts that can be used to build and scale a wide variety of applications. The framework supports you, but doesn’t get in the way.
Below are some of my other favorite aspects of Feathers and insight into how Feathers applications are built.
Services
All your application’s business logic in Feathers is contained in services. A service is a JavaScript object that implements CRUD methods. Here is a basic structure of a service:
const recipes = {
find(params) {},
get(id, params) {},
create(data, params) {},
update(id, data, params) {},
patch(id, data, params) {},
remove(id, params) {}
}
app.use('/recipes', recipes)
You would typically create a service for each entity in your application (e.g. users
, organisations
, tweets
). But you would also use them for grouping logically related actions that are not necessarily represented in your database. For example, an accounts
service could be used for orchestrating the creation or deletion of organisation accounts. An emails
service for sending emails.
The kicker is that once you start following this service structure, Feathers is able to provide you with a wealth of out of the box features:
- automatic HTTP and/or Websocket bindings
- standardised community maintained database adapters
- a browser/node client library for interacting with the API
- realtime functionality (🔥)
- code reuse via reusable hooks
- good testability
Let’s look at some of these in more detail.
HTTP/Websocket Bindings
When you write your logic in Feather’s services, you don’t directly deal with HTTP requests or socket connections. You can if you need to, but typically it’s not necessary. Instead you work with id
, data
and params
that are extracted from the HTTP/Websocket request and passed to your service. This is really nifty – your business logic becomes more concise, reusable and easier to test.
Because of this abstraction, Feathers can map your services to an HTTP REST API and/or to a Websocket API automatically. For example, you can expose your application’s REST API to external users as your public API and use a websocket API to make your app realtime.
Services can also be called from other services to create complex workflows. Imagine a sign up flow where an accounts
service creates an organisation, a user and sends an email. All within the same database transaction. Easy with Feathers – call the services you need and pass the transaction as one of the params.
Database Adapters
Often you use services to represent and manipulate entities that are stored in a database, such as Postgres, Mongo or Redis. In this case you don’t even need to implement the service at all! You can use one of the community maintained database adapters that implement the standard service interface. Here is how you create a service using feathers-knex
:
const service = require('feathers-knex')
module.exports = function recipes(app) {
app.use(
'/recipes',
service({
Model: app.get('knex'),
name: 'recipes'
})
)
}
That’s all the code it takes for you to be able to manipulate your entity all the way from the browser:
const recipes = app.service('recipes')
recipes.create({
title: 'Sticky mango prawns',
category: 'five-ingredients'
})
recipes.find({
query: {
category: 'five-ingredients',
$limit: 5
}
})
All this without configuring multiple route handlers, manually making and handling HTTP requests or writing any database queries.
Creating such a service is a starting point when building Feather’s apps. What follows is implementing the business logic using hooks.
Hooks
Hooks is the bread and butter of Feathers applications. Hooks are like little middleware that allow you to run logic before and after each request. You would use hooks to:
- validate request payload against a schema
- check if the user is logged in
- check if the user has sufficient privileges
- scope the database queries to what the user is allowed to see
- modify payloads before they get stored in the database (e.g. updating
updatedAt
field) - modify the response to the user removing any unwanted fields
- trigger other actions such as sending emails
The database adapter takes care of mapping CRUD requests to your database in a standard way. Hooks is where you restrict and transform what the users are actually allowed to do.
Hooks compose well and help reuse code between your services. You can use community maintained common hooks library and combine that with your own custom hooks to arrive at an expressive, declarative description of your services:
const { iff, isProvider, disallow, keep } = require('feathers-hooks-common')
const { authenticate } = require('@feathersjs/authentication').hooks
const errors = require('@feathersjs/errors')
const validate = require('../../hooks/validate')
// create some custom, composed, reusable hooks
const auth = () => authenticate('jwt')
const ifExternal = (...hooks) => iff(isProvider('external'), ...hooks)
const notAdmin = () => context => context.params.user.role !== 'admin'
const adminOnly = role => ifExternal(iff(notAdmin(), forbidden()))
const forbidden = () => context => {
throw new errors.Forbidden()
}
// describe the hooks of your service
module.exports = {
before: {
all: [auth()],
create: [adminOnly(), validate({ title: { type: 'string' } })],
patch: [adminOnly(), validate({ title: { type: 'string' } })],
remove: [ifExternal(disallow())]
},
after: {
all: [ifExternal(keep('id', 'title', 'category'))]
},
error: {
all: []
}
}
Feathers Client
After creating the API using services and hooks, you will want to access it. Feathers shines again, since you don’t have to manually make HTTP calls. Instead, Feathers provides a transport agnostic client library.
This client is pluggable, you can choose to use axios
, fetch
or any other package when using HTTP and socket.io
or primus
when using Websockets.
What I like here is that you can make requests to Feathers services exactly the same way from a Browser, another API or from internal service to service business logic. It always looks like this:
// CRUD
app.service('recipes').find({ query: { category: '5-ingredients' } })
app.service('recipes').create({ title: 'Yum' })
app.service('recipes').get(40)
app.service('recipes').patch(40, { title: 'Yummy' })
app.service('recipes').remove(40)
// Trigger flows/actions
app.service('auth').create({ action: 'validateSignUpEmail' })
app.service('auth').create({ action: 'sendPasswordReset' })
Realtime
Realtime was what made me discover Feathers in the first place. You don’t have to use realtime capabilities of Feathers for it to be useful, but having realtime as an option is really cool.
I’ve been of an opinion for a while that that at this day and age realtime should be a baseline when building applications. Shouldn’t all the data you see in the app be up to date and all changes reflected immediately? Then why is it so hard to build such apps. It’s not often that you have time or a priority to add realtime functionality into an app (unless it’s core to the application, e.g. chat).
But in Feathers realtime just kind of falls out of it’s service centric design in a really elegant way. Essentially, every time a create
/ patch
/ remove
operation is performed on any entity by any user or any internal service, that event gets sent to all relevant socket connections. What this means is that, any data that gets created or updated in your application can be re-rendered in realtime. This is especially a great match for frameworks like React that simply re-render state whenever it updates.
You get to control who gets the events by configuring the channels. Channels can be created with any granularity needed by your application. You could create a single channel such as everyone
. You could create per role channels, e.g. org/a/admins
and org/b/users
. Or go granular with per entity/user channels repo/xyz/user/5
. You get to control what data gets sent through each channel if you need to remove sensitive data. You can also filter which data gets sent to whom by inspecting the payload and filtering on things like organisationId
or userId
.
This scales up to multi server deployments by broadcasting these events to all servers (with something like Redis). In multi server setup each server would hold some of the client Websocket connections. The broadcasting accounts for cases when server A performs an operation and emits an event relevant to a connection on server B.
Testing
I never had more fun testing APIs. Because all of the logic is in services, and it’s easy to invoke them, your tests become clear and concise. This lets you focus on testing your business logic in detail without worrying about the transport and without having to spin up the HTTP/Websocket server and connections. Want to make an authenticated call? Just pass a user
param. Want to test external vs internal behaviour? Use the provider
param. Running Feathers tests in parallel with test runners like ava
is a breeze.
Here’s an example test:
test('users can only edit their own information', async t => {
const users = app.service('users')
const { members } = await createOrganisation({ members: 2 })
const [user1, user2] = members
const params = { provider: 'socketio', user: user1, authenticated: true }
await t.throwsAsync(() => users.patch(user2.id, { name: 'hello' }, params)), { name: 'Forbidden' })
})
Authentication
Yes. Feathers provides an auth module. You get a customizable username/password and OAuth2 authentication out of the box. Note: authentication is one of the rougher areas of Feathers, but it is being rewritten and should be greatly improved in the upcoming Feathers v4.
GraphQL
GraphQL is not something I’ve done using Feathers, but I can see how it could be a good pairing. Feathers services should map well to GraphQL resolvers. There’s also work being done to make using GraphQL with Feathers easier.
Conclusion
So far I had a lot of fun using Feathers. It’s a well designed library and brings a lot to the table. Feathers:
- makes realtime APIs a new baseline
- provides a lot of flexibility with only a few concepts
- helps you focus on business logic instead of wiring
- brings clarity to project structure
- has great testing experience
I can highly recommend using Feathers. It’s a higher level abstraction compared to Express, Koa, Hapi and Fastify. Feathers stands out among the other similar API frameworks with its unique and powerful design. Finally it’s a more open and much more flexible alternative to proprietary services such as Firebase.
Read more about the fundamentals of Feathers architecture as written by David Luecke, one of the Feathers creators.
If you’re looking for the docs, I recommend the “in progress” updated Docs site - https://crow.docs.feathersjs.com/ instead of the current “official docs”.
Finally, check out the Feathers monorepo and join the Feathers Slack channel.