Processing data =============== Now that we can :doc:`./authentication`, 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 :doc:`../basics/hooks` come into play. In this specific case, a *before* hook. To create a new hook we can run: .. code-block:: sh 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 :doc:`../../api/services`. For this specific hook, only select ``create``. After confirming the last prompt you should see something like this: .. figure:: ./assets/process-message.png :alt: The process-message hook prompts 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: .. code:: js // Use this hook to manipulate incoming or outgoing data. // For more information on hooks see: http://docs.feathersjs.com/api/hooks.html module.exports = function (options = {}) { // eslint-disable-line no-unused-vars return async context => { const { data } = context; // Throw an error if we didn't get a text if(!data.text) { throw new Error('A message must have a text'); } // The authenticated user const user = context.params.user; // The actual message text const text = context.data.text // Messages can't be longer than 400 characters .substring(0, 400); // Override the original data (so that people can't submit additional stuff) context.data = { text, // Set the user id userId: user._id, // Add the current date createdAt: new Date().getTime() }; // Best practice: hooks should always return the context return context; }; }; 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: .. code-block:: sh 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 .. figure:: ./assets/gravatar.png :alt: The gravatar hook prompts The gravatar hook prompts Then we update ``src/hooks/gravatar.js`` with the following code: .. code:: js // Use this hook to manipulate incoming or outgoing data. // For more information on hooks, see: http://docs.feathersjs.com/api/hooks.html // We need this to create the MD5 hash const crypto = require('crypto'); // The Gravatar image service const gravatarUrl = 'https://s.gravatar.com/avatar'; // The size query. Our chat needs 60px images const query = 's=60'; module.exports = function (options = {}) { // eslint-disable-line no-unused-vars return async context => { // The user email const { email } = context.data; // Gravatar uses MD5 hashes from an email address (all lowercase) to get the image const hash = crypto.createHash('md5').update(email.toLowerCase()).digest('hex'); context.data.avatar = `${gravatarUrl}/${hash}?${query}`; // Best practice: hooks should always return the context return context; }; }; 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 .. figure:: ./assets/populate-user.png :alt: The populate-user hook The populate-user hook Once created, update ``src/hooks/populate-user.js`` to: .. code:: js // Use this hook to manipulate incoming or outgoing data. // For more information on hooks, see: http://docs.feathersjs.com/api/hooks.html module.exports = function (options = {}) { // eslint-disable-line no-unused-vars return async context => { // Get `app`, `method`, `params` and `result` from the hook context const { app, method, result, params } = context; // Make sure that we always have a list of messages either by wrapping // a single message into an array or by getting the `data` from the `find` method's result const messages = method === 'find' ? result.data : [ result ]; // Asynchronously get user object from each message's `userId` // and add it to the message await Promise.all(messages.map(async message => { // Also pass the original `params` to the service call // so that it has the same information available (e.g. who is requesting it) message.user = await app.service('users').get(message.userId, params); })); // Best practice: hooks should always return the context return context; }; }; .. .. 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 :doc:`./frontend`. See the :doc:`../frameworks/readme` 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 :doc:`../api/readme` for details on using Feathers and its database adaptors.