Mateusz KowalczykUpdated: 07/01/2020
Some frameworks take a more opinionated approach giving us the architecture and tools that suit particular needs than the most popular Express. One of them is FeathersJS.
Feathers is a simple and lightweight framework that allows you to quickly build APIs (both RESTful and realtime) as well as connect to them using frontend JS libraries. The most important point in its philosophy is DRY (Don’t repeat yourself) approach. The goal of Feathers is to handle all the repetitive operations, letting you implement only the things that are specific to your app. It achieves this goal by:
– providing CLI for generating project structure, services, model files, hooks (more on hooks later)… – so you don’t have to create all the standard files/folders or write boilerplate code yourself
– using the uniform service interface (similar to REST) – it allows Feathers to implement things like communication protocol, error handling, database storage and others in a standard way
The last thing to mention before we create our first API is that Feathers doesn’t implement everything from scratch. It uses popular and battle-tested solutions under the hood, including Express, Yeoman, Socket.io, passport, mongoose or sequelize. This makes Feathers stable and compatible with many JS libraries.
Before we create (generate) the project structure let’s briefly talk about the app itself. In this tutorial, we’ll implement the API that can be used for tasks management. We’ll be able to add, edit, view or delete our tasks. To keep it simple, we won’t assign tasks to the users (even though it’s really quick to add users and authentication in Feathers).
OK, so to the point – let’s start by opening our terminal/console and installing Feathers CLI (assuming you already have npm installed).
npm install -g @feathersjs/cli
We can generate the basic project structure using CLI now.
feathers generate app
Feathers will ask us about some project details then. Let’s set the name to todomanager and leave the defaults for others.
? Project name todomanager ? Description ? What folder should the source files live in? src ? Which package manager are you using (has to be installed globally)? npm ? What type of API are you making? REST, Realtime via Socket.io
After automatically installing required packages we have the basic project ready to go. Let’s check it by running
npm start and opening
http://localhost:3030 in a web browser. Feathers welcome screen should appear.
testscripts that allow us to (surprise) start and run test for our app respectively
To create the service we will use another generator like this:
feathers generate service
HINT: To see the full list of generators just run
Now we are to choose how our service is about to store data. Most popular choices would be Mongoose (for MongoDB) or Sequelize for SQL databases, but for this tutorial, we’ll use NeDB that is really simple and convenient (and its API is a subset of MongoDB’s). It stores data in .db files (inside data folder) as JSON documents – one for each row. It’s simple to edit and doesn’t require former db server installation.
After choosing NeDB we can set the name of the service to tasks and leave the default path value as well as db connection string.
? What kind of service is it? NeDB ? What is the name of the service? tasks ? Which path should the service be registered on? /tasks ? What is the database connection string? nedb://../data
Our service file is generated along with others: task model, hooks and tests. You can take a look at tasks.service.js file, but it’s mostly boilerplate code that you won’t need to edit in most cases (as you will keep most of your app logic in hooks). However, it’s good to know that you can override service default behaviour if necessary.
One last thing I’d like to mention here is that it is possible to generate a custom service that doesn’t use data storage (you may have noticed such an option while generating tasks service). It is useful for creating services that aggregate data from other ones or doing some stuff not related to data storage at all.
Now after creating our service, we can play with it for a while using a tool like Postman for making RESTful API calls.
There are a few methods available in each service in Feathers (with corresponding HTTP actions and paths in parentheses):
– find (GET /tasks) – finds all the documents matching the query (optional), also supports sorting and pagination
– get (GET /tasks/:id) – finds a single document by id
– create (POST /tasks) – creates a new document
– update (PUT /tasks/:id) – updates the whole document by overwriting it with provided one (missing fields will be cleared)
– patch (PATCH /tasks/:id) – updates only the fields provided (missing fields will remain untouched)
– remove (DELETE /tasks/:id) – removes the document
Let’s start with adding a few tasks with just name like below:
Feathers will respond with the newly created document including id.
HINT: Notice your tasks are being added to NeDB file (data/tasks.db).
Feel free to play around with other actions like editing or deleting tasks. However, we’re going to take a closer look at find method that gives us a few handy features.
First, let’s call
http://localhost:3030/tasks using the GET method (you can use either Postman or your browser). As a response you’ll receive JSON document similar to this one:
We have all the documents here as well as some additional information: total number of documents, the limit of documents returned at once and number of skipped documents. The last two are useful for paging – default page limit can be set in the configuration (config/default.json) or in the url.
So for example calling:
http://localhost:3030/tasks?$limit=2&$skip=1 will result in:
It’s also possible to find documents by its field value:
or to sort the results:
More on available queries here
As you can see we are already able to perform a lot of common operations and we haven’t written a single line of code yet 🙂 But now it’s time to dig deeper into Feathers. Let’s talk about…
Hooks are the way to add custom logic to generic auto-generated services. And they do it in a way that doesn’t compromise DRY rule. So what are hooks? They are functions called before or after our service method or in case of error. For example, we can use a hook to authenticate the user before executing the service method, to validate incoming data, to modify data returned by our service method, to log errors and so on.
Let’s say we’d like to set updatedAt field of our task to current date and time every time it’s updated. To do that we will use hooks. We could implement our own hook (and we’ll do that in a moment for some other case), but now we can simply use one of the hooks provided in the feathers-hooks-common package. First, let’s install it:
npm install --save feathers-hooks-common
Next, we need to edit the file defining hooks for our tasks service (services/tasks/tasks.hooks.js). This is how it looks like right now:
We have objects for hooks called before and after actions as well as in case of error. Each object contains arrays of hooks assigned to particular service actions (like find or create) as well as a field called all that includes an array of hooks executed for each action.
The hook that we’re going to use is called setNow and it takes the name (or names) of fields that should be set to current date and time as a parameter.
First, we need to import it from the feathers-hooks-common package. Then we can add it in every place that modifies our task. Our code will look like this after those changes:
If you play around with adding and modifying tasks you will notice the new field automatically added to all documents:
HINT: Remember to restart your development server after making changes to the code.
There are many more standard hooks that you can find here, including ones that can be used for modifying data, caching, validation, conditional hooks execution and more.
Finally, we’re going to create our own hook. It will make sure that the name of the task will not be empty. To add the hook file and its basic structure we’ll use the generator again. We’ll call it validateName and select I will add it myself, because we’ll add it in a few custom places later.
feathers g hook (
g here is short for
? What is the name of the hook? validateName ? What kind of hook should it be? I will add it myself
The validate-name.js file in the hooks folder will be generated:
As you can see it exports a function. This function is called when defining a hook (as we did with common setNow hook before) and it returns another function that will be called once the hook is executed (i.e. before/after action or in case of error). Right now this generated function only returns context wrapped in promise and we’re going to replace it with our own code now.
We need three steps:
– get name value somehow
– check if it’s missing or empty
– throw an error if so
First, we have to find name value that is sent in the request to the API. It’s available – along with many other useful things – inside context variable or to be more precise in its data field (more on hook context here).
npm install --save feathers-errors
We removed default return statement, because it’s ok for hook function to return undefined too (more info here)
Finally, we need to assign our custom hook to appropriate actions:
Adding the new task without a name will result in an error now.
We only scratched the surface here, but having a basic understanding of Feathers philosophy and conventions, it’s pretty easy to develop your app further (excellent documentation is a big help too). Feathers itself provides ready to use solutions for authentication (including JWT and OAUTH), support for SQL and NoSQL databases, validation as well as the library that helps to connect with Feathers from a frontend app. There is also a lot of external libraries created for Feathers making easy integration with tools and frameworks like: Angular/RxJS, React/Redux, VueJS, Mailgun/Sendgrid. And last but not least, as Feathers is based on Express, you can use any library or middleware made for it too. All of that makes Feathers quick and straightforward in typical use cases, but also really flexible when necessary.