Services

Services are the heart of every Feathers application and JavaScript objects or instances of a class that implement certain methods. Services provide a uniform, protocol independent interface for how to interact with any kind of data like:

  • Reading and/or writing from a database
  • Interacting with the file system
  • Call another API
  • Call other services like
    • Sending an email
    • Processing a payment
    • Returning the current weather for a location, etc.

Protocol independent means that to a Feathers service it does not matter if it has been called internally, through a REST API or websockets (both of which we will look at later) or any other way.

Service methods

Service methods are CRUD methods that a service object can implement. Feathers service methods are:

  • find - Find all data (potentially matching a query)
  • get - Get a single data entry by its unique identifier
  • create - Create new data
  • update - Update an existing data entry by completely replacing it
  • patch - Update one or more data entries by merging with the new data
  • remove - Remove one or more existing data entries

Below is an example of Feathers service interface as a normal object and a JavaScript class:

{% codetabs name=”Object”, type=”js” -%}
const myService = {
async find(params) {
return [];
},
async get(id, params) {},
async create(data, params) {},
async update(id, data, params) {},
async patch(id, data, params) {},
async remove(id, params) {}
}

app.use(‘/my-service’, myService);
{%- language name=”Class”, type=”js” -%}
class myService {
async find(params) {
return [];
}
async get(id, params) {}
async create(data, params) {}
async update(id, data, params) {}
async patch(id, data, params) {}
async remove(id, params) {}
}

app.use(‘/my-service’, new myService());
{%- endcodetabs %}

The parameters for service methods are:

  • id - The unique identifier for the data
  • data - The data sent by the user (for creating and updating)
  • params (optional) - Additional parameters, for example the authenticated user or the query

Note: A service does not have to implement all those methods but must have at least one.

Pro tip: For more information about service, service methods and parameters see the Service API documentation.

A messages service

Now that we know how service methods look like we can implement our own chat message service that allows us to find, create, remove and update messages in-memory. Here we will use a JavaScript class to work with our messages but as we’ve seen above it could also be a normal object.

Below is the complete updated app.js with comments:

  1. const feathers = require('@feathersjs/feathers');
  2. class Messages {
  3. constructor() {
  4. this.messages = [];
  5. this.currentId = 0;
  6. }
  7. async find(params) {
  8. // Return the list of all messages
  9. return this.messages;
  10. }
  11. async get(id, params) {
  12. // Find the message by id
  13. const message = this.messages.find(message => message.id === parseInt(id, 10));
  14. // Throw an error if it wasn't found
  15. if(!message) {
  16. throw new Error(`Message with id ${id} not found`);
  17. }
  18. // Otherwise return the message
  19. return message;
  20. }
  21. async create(data, params) {
  22. // Create a new object with the original data and an id
  23. // taken from the incrementing `currentId` counter
  24. const message = Object.assign({
  25. id: ++this.currentId
  26. }, data);
  27. this.messages.push(message);
  28. return message;
  29. }
  30. async patch(id, data, params) {
  31. // Get the existing message. Will throw an error if not found
  32. const message = await this.get(id);
  33. // Merge the existing message with the new data
  34. // and return the result
  35. return Object.assign(message, data);
  36. }
  37. async remove(id, params) {
  38. // Get the message by id (will throw an error if not found)
  39. const message = await this.get(id);
  40. // Find the index of the message in our message array
  41. const index = this.messages.indexOf(message);
  42. // Remove the found message from our array
  43. this.messages.splice(index, 1);
  44. // Return the removed message
  45. return message;
  46. }
  47. }
  48. const app = feathers();
  49. // Initialize the messages service by creating
  50. // a new instance of our class
  51. app.use('messages', new Messages());

Using services

A service object can be registered on a Feathers application by calling app.use(path, service). path will be the name of the service (and the URL if it is exposed as an API which we will learn later).

We can retrieve that service via app.service(path) and then call any of its service methods. Add the following to the end of app.js:

  1. async function processMessages() {
  2. await app.service('messages').create({
  3. text: 'First message'
  4. });
  5. await app.service('messages').create({
  6. text: 'Second message'
  7. });
  8. const messageList = await app.service('messages').find();
  9. console.log('Available messages', messageList);
  10. }
  11. processMessages();

And run it with

  1. node app.js

We should see this:

  1. Available messages [ { id: 1, text: 'First message' },
  2. { id: 2, text: 'Second message' } ]

Service events

When you register a service it will automatically become a NodeJS EventEmitter that sends events with the new data when a service method that modifies data (create, update, patch and remove) returns. Events can be listened to with app.service('messages').on('eventName', data => {}). Here is a list of the service methods and their corresponding events:

Service method Service event
service.create() service.on('created')
service.update() service.on('updated')
service.patch() service.on('patched')
service.remove() service.on('removed')

We will see later that this is the key to how Feathers enables real-time functionality. For now, let’s update the processMessages function in app.js as follows:

  1. async function processMessages() {
  2. app.service('messages').on('created', message => {
  3. console.log('Created a new message', message);
  4. });
  5. app.service('messages').on('removed', message => {
  6. console.log('Deleted message', message);
  7. });
  8. await app.service('messages').create({
  9. text: 'First message'
  10. });
  11. const lastMessage = await app.service('messages').create({
  12. text: 'Second message'
  13. });
  14. // Remove the message we just created
  15. await app.service('messages').remove(lastMessage.id);
  16. const messageList = await app.service('messages').find();
  17. console.log('Available messages', messageList);
  18. }
  19. processMessages();

If we now run the file via

  1. node app.js

We will see how the event handlers are logging the information of created and deleted message like this:

  1. Created a new message { id: 1, text: 'First message' }
  2. Created a new message { id: 2, text: 'Second message' }
  3. Deleted message { id: 2, text: 'Second message' }
  4. Available messages [ { id: 1, text: 'First message' } ]

What’s next?

In this chapter we learned about services as Feathers core concept for abstracting data operations. We also saw how a service sends events which we will use later to create real-time applications. First, we will look at Feathers Hooks which is the other key part of how Feathers works.