REST APIs

In the previous chapters we learned about Feathers services and hooks and created a messages service that works in NodeJS and the browser. We saw how Feathers automatically sends events but so far we didn’t really create a web API that other people can use.

This is what Feathers transports do. A transport is a plugin that turns a Feathers application into a server that exposes our services through different protocols for other clients to use. Since a transport involves running a server it won’t work in the browser but we will learn later that there are complementary plugins for connecting to a Feathers server in a browser Feathers application.

Currently Feathers officially has three transports:

  • HTTP REST via Express for exposing services through a JSON REST API
  • Socket.io for connecting to services through websockets and also receiving real-time service events
  • Primus an alternative to Socket.io supporting several websocket protocols which also supports real-time events

In this chapter we will look at the HTTP REST transport and Feathers Express framework integration.

REST and services

One of the goals of Feathers is to make building REST APIs easier since it is by far the most common protocol for web APIs. For example, we want to make a request like GET /messages/1 and get a JSON response like { "id": 1, "text": "The first message" }. You may already have noticed that the Feathers service methods and the HTTP methods like GET, POST, PATCH and DELETE are fairly complementary to each other:

Service method HTTP method Path
.find() GET /messages
.get() GET /messages/1
.create() POST /messages
.update() PUT /messages/1
.patch() PATCH /messages/1
.remove() DELETE /messages/1

What the Feathers REST transport essentially does is to automatically map our existing service methods to those endpoints.

Express integration

Express is probably the most popular Node framework for creating web applications and APIs. The Feathers Express integration allows us to turn a Feathers application into an application that is both a Feathers application and a fully compatible Express application. This means you can use Feathers functionality like services and also any existing Express middleware. As mentioned before, the Express framework integration only works on the server.

To add the integration we install @feathersjs/express:

  1. npm install @feathersjs/express --save

Then we can initialize a Feathers and Express application that exposes services as a REST API on port 3030 like this:

  1. const feathers = require('@feathersjs/feathers');
  2. const express = require('@feathersjs/express');
  3. // This creates an app that is both, an Express and Feathers app
  4. const app = express(feathers());
  5. // Turn on JSON body parsing for REST services
  6. app.use(express.json())
  7. // Turn on URL-encoded body parsing for REST services
  8. app.use(express.urlencoded({ extended: true }));
  9. // Set up REST transport using Express
  10. app.configure(express.rest());
  11. // Set up an error handler that gives us nicer errors
  12. app.use(express.errorHandler());
  13. // Start the server on port 3030
  14. app.listen(3030);

express.json, express.urlencoded and express.errorHandler are normal Express middlewares. We can still use app.use to register a Feathers service though.

Pro tip: You can find more information about the Express framework integration in the Express API chapter.

A messages REST API

The code above is really all we need to turn our messages service into a REST API. Here is the complete code for our app.js exposing the service from the services chapter through a REST API:

  1. const feathers = require('@feathersjs/feathers');
  2. const express = require('@feathersjs/express');
  3. class Messages {
  4. constructor() {
  5. this.messages = [];
  6. this.currentId = 0;
  7. }
  8. async find(params) {
  9. // Return the list of all messages
  10. return this.messages;
  11. }
  12. async get(id, params) {
  13. // Find the message by id
  14. const message = this.messages.find(message => message.id === parseInt(id, 10));
  15. // Throw an error if it wasn't found
  16. if(!message) {
  17. throw new Error(`Message with id ${id} not found`);
  18. }
  19. // Otherwise return the message
  20. return message;
  21. }
  22. async create(data, params) {
  23. // Create a new object with the original data and an id
  24. // taken from the incrementing `currentId` counter
  25. const message = Object.assign({
  26. id: ++this.currentId
  27. }, data);
  28. this.messages.push(message);
  29. return message;
  30. }
  31. async patch(id, data, params) {
  32. // Get the existing message. Will throw an error if not found
  33. const message = await this.get(id);
  34. // Merge the existing message with the new data
  35. // and return the result
  36. return Object.assign(message, data);
  37. }
  38. async remove(id, params) {
  39. // Get the message by id (will throw an error if not found)
  40. const message = await this.get(id);
  41. // Find the index of the message in our message array
  42. const index = this.messages.indexOf(message);
  43. // Remove the found message from our array
  44. this.messages.splice(index, 1);
  45. // Return the removed message
  46. return message;
  47. }
  48. }
  49. const app = express(feathers());
  50. // Turn on JSON body parsing for REST services
  51. app.use(express.json())
  52. // Turn on URL-encoded body parsing for REST services
  53. app.use(express.urlencoded({ extended: true }));
  54. // Set up REST transport using Express
  55. app.configure(express.rest());
  56. // Initialize the messages service by creating
  57. // a new instance of our class
  58. app.use('messages', new Messages());
  59. // Set up an error handler that gives us nicer errors
  60. app.use(express.errorHandler());
  61. // Start the server on port 3030
  62. const server = app.listen(3030);
  63. // Use the service to create a new message on the server
  64. app.service('messages').create({
  65. text: 'Hello from the server'
  66. });
  67. server.on('listening', () => console.log('Feathers REST API started at http://localhost:3030'));

You can start the server by running

  1. node app.js

Note: The server will stay running until you stop it by pressing Control + C in the terminal. Remember to stop and start the server every time app.js changes.

Important: In Express an error handler, here app.use(express.errorHandler());, always has to be the last line before starting the server.

Using the API

Once the server is running the first thing we can do is hit localhost:3030/messages in the browser. Since we already created a message on the server, the JSON repsonse will look like this:

  1. [{"id":1,"text":"Hello from the server"}]

We can also retrieve that specific message by going to localhost:3030/messages/1.

Pro Tip: A browser plugin like JSON viewer for Chrome makes it much nicer to view JSON responses.

New messages can now be created by sending a POST request with JSON data to the same URL by using cURL on the command line like this:

  1. curl 'http://localhost:3030/messages/' -H 'Content-Type: application/json' --data-binary '{ "text": "Hello from the command line!" }'

Note: You can also use tools like Postman to make HTTP requests.

If you now refresh localhost:3030/messages you will see the newly created message.

We can also remove a message by sending a DELETE to its URL:

  1. curl -X "DELETE" http://localhost:3030/messages/1

What’s next?

In this chapter we built a fully functional messages REST API. You can probably already imagine how our messages service could store its data in a database instead of the messages array. In the next chapter, let’s look at some services that implement different databases allowing us to create those APIs with even less code!