Processing data

Now that we can create and authenticate users, we are going to process data, sanitize the input we get from the client and add extra information. For this chapter we require an empty database which can be done by removing the data/ folder (rm -rf data/).

Sanitize new message

When creating a new message, we automatically sanitize our input, add the user that sent it and include the date the message has been created, before saving it in the database. This is where hooks come into play. In this specific case, a before hook. To create a new hook we can run:

  1. feathers generate hook

Let’s call this hook process-message. We want to pre-process client-provided data. Therefore, in the next prompt asking for what kind of hook, choose before and press Enter.

Next a list of all our services is displayed. For this hook, only choose the messages service. Navigate to the entry with the arrow keys and select it with the space key.

A hook can run before any number of service methods. For this specific hook, only select create. After confirming the last prompt you should see something like this:

The process-message hook prompts

A hook was generated and wired up to the selected service. Now it’s time to add some code. Update src/hooks/process-message.js to look like this:

  1. // Use this hook to manipulate incoming or outgoing data.
  2. // For more information on hooks see: http://docs.feathersjs.com/api/hooks.html
  3. module.exports = function (options = {}) { // eslint-disable-line no-unused-vars
  4. return async context => {
  5. const { data } = context;
  6. // Throw an error if we didn't get a text
  7. if(!data.text) {
  8. throw new Error('A message must have a text');
  9. }
  10. // The authenticated user
  11. const user = context.params.user;
  12. // The actual message text
  13. const text = context.data.text
  14. // Messages can't be longer than 400 characters
  15. .substring(0, 400);
  16. // Override the original data (so that people can't submit additional stuff)
  17. context.data = {
  18. text,
  19. // Set the user id
  20. userId: user._id,
  21. // Add the current date
  22. createdAt: new Date().getTime()
  23. };
  24. // Best practice: hooks should always return the context
  25. return context;
  26. };
  27. };

This validation code includes:

  1. Check if there is a text in the data and throw an error if not
  2. Truncate the message’s text property to 400 characters
  3. Update the data submitted to the database to contain:
    • The new truncated text
    • The currently authenticated user (so we always know who sent it)
    • The current (creation) date

Add a user avatar

Let’s generate another hook to add a link to the Gravatar image associated with the user’s email address, so we can display an avatar. Run:

  1. feathers generate hook

The selections are almost the same as our previous hook:

  • Call the hook gravatar
  • It’s a before hook
  • … on the users service
  • … for the create method

The gravatar hook prompts

Then we update src/hooks/gravatar.js with the following code:

  1. // Use this hook to manipulate incoming or outgoing data.
  2. // For more information on hooks, see: http://docs.feathersjs.com/api/hooks.html
  3. // We need this to create the MD5 hash
  4. const crypto = require('crypto');
  5. // The Gravatar image service
  6. const gravatarUrl = 'https://s.gravatar.com/avatar';
  7. // The size query. Our chat needs 60px images
  8. const query = 's=60';
  9. module.exports = function (options = {}) { // eslint-disable-line no-unused-vars
  10. return async context => {
  11. // The user email
  12. const { email } = context.data;
  13. // Gravatar uses MD5 hashes from an email address (all lowercase) to get the image
  14. const hash = crypto.createHash('md5').update(email.toLowerCase()).digest('hex');
  15. context.data.avatar = `${gravatarUrl}/${hash}?${query}`;
  16. // Best practice: hooks should always return the context
  17. return context;
  18. };
  19. };

Here we use Node’s crypto library to create an MD5 hash of the user’s email address. This is what Gravatar uses as the URL for the avatar associated with an email address. When a new user is created, this gravatar hook will set the avatar property to the avatar image link.

Populate the message sender

In the process-message hook we are currently just adding the user’s _id as the userId property in the message. We want to show more than the _id in the UI, so we’ll need to populate more data in the message response. To display a users’ details, we need to include extra information in our messages.

We therefore create another hook:

  • Call the hook populate-user
  • It’s an after hook
  • … on the messages service
  • … for all methods

The populate-user hook

Once created, update src/hooks/populate-user.js to:

  1. // Use this hook to manipulate incoming or outgoing data.
  2. // For more information on hooks, see: http://docs.feathersjs.com/api/hooks.html
  3. module.exports = function (options = {}) { // eslint-disable-line no-unused-vars
  4. return async context => {
  5. // Get `app`, `method`, `params` and `result` from the hook context
  6. const { app, method, result, params } = context;
  7. // Make sure that we always have a list of messages either by wrapping
  8. // a single message into an array or by getting the `data` from the `find` method's result
  9. const messages = method === 'find' ? result.data : [ result ];
  10. // Asynchronously get user object from each message's `userId`
  11. // and add it to the message
  12. await Promise.all(messages.map(async message => {
  13. // Also pass the original `params` to the service call
  14. // so that it has the same information available (e.g. who is requesting it)
  15. message.user = await app.service('users').get(message.userId, params);
  16. }));
  17. // Best practice: hooks should always return the context
  18. return context;
  19. };
  20. };

Note: Promise.all ensures that all the calls run in parallel, instead of sequentially.

What’s next?

In this section, we added three hooks to pre- and post-process our message and user data. We now have a complete API to send and retrieve messages, including authentication.

Now we are ready to build a frontend using modern plain JavaScript.

See the frameworks section for more resources on specific frameworks like React, React Native, Angular or VueJS. You’ll find guides for creating a complete chat front-end with signup, logging, user listing and messages. There are also links to complete chat applications built with some popular front-end frameworks.

You can also browse the API for details on using Feathers and its database adaptors.