My tips for using Socket.io

socket.io is the most used library to do real-time with a node server. When sockets carry a multitude of events, it becomes increasingly difficult to maintain them. Here are some tips that I personally use to avoid this problem as much as possible.

Before starting this article, it is better to know the basics of operation of socket.io.

Respect a naming standard

The first step before you start anything is to develop a standard for naming your socket events. For my part, I use the format domain:action.
For example, when you want to send a message on the chat, the name of the event will be message:send. If you want to refresh the list of messages, the event will be message:refresh. If we have the possibility of managing a list of friends, then we could have events which will have the name friends:add or even friends:remove.
This format allows initially to avoid duplicates and especially to have a better readability on the role of the event. Splitting events into Domaine also makes it possible to logically divide its functions into several files corresponding to their respective domains.

Use of handlers

The handlers are neither more nor less than files where we will implement the logic of events. The goal is to separate your code by respecting the same logic as for the naming (i.e., by creating a file by Domaine). This is how I separate the logic from my sockets:

- socket/ - index.js - message.js - friends.js

As explained in the previous chapter, your handlers will be grouped by Domaine. in the file index.js this is where you will have all your socket connections and handler implementations. Your handlers will only implement logic related to their domain.
In our example, all events related to message (Eg message:send, message:refresh) will be done in the file message.js.

Validate your events: Joi to the rescue

Being a big fan of opened, I was particularly frustrated that I couldn't use all of the validation aspect put in place by opened on the roads.
One of the strengths of these validations remains the use of Thursday, a library allowing to verify that an object respects a specific schema. These diagrams look like this:

Joi.object().keys({
  message: Joi.string().required().description("The content of the message"),
  to: Joi.string().optional().description("The name of the user to send the message")
});

This diagram above allows us to check the content of our event message:send. Here we can see that our event is expecting an object with the attribute message who is compulsory. An attribute to is optional and allows you to send a message to only one person.

Find out more: you can also externalize your diagrams in another file. so you can have an architecture that looks like this:

- socket/ - message/ - index.js - schemas.js - index.js

Add an extra layer: Helpers

To end on a high note, we are going to create helpers in another file (ideally located in a folder helpers followed by a file named socket.js) which will allow us to assemble everything we have seen previously. Indeed, we are not going to rewrite our whole piece of code each time we are going to create an event.
First of all, we are going to create a helper which will allow us to create an event and validate the payload of the event with a Joi schema. Here is an example of a helper that solves this problem:

// helpers/socket.js
/**
 * Create an event to be implemented into sockets
 * @param {String} name - The name of the event
 * @param {object} rules - Object containing Joi validation rules
 * @param {Function} fn - The function to be called on event
 * @returns {*} The event Object
 */
export const createEvent = (name, rules, fn) => {
  Hoek.assert(!!name, "helpers - socket.createEvent() must have a name");
  Hoek.assert(typeof fn === "function", "helpers - socket.createEvent() must have a function");
  return {
	  name,
	  fn,
	  validation: rules && Joi.object().keys(rules)
  };
};

Hoek is a utility used in the hapi universe. It is mainly used for the function Assert which makes it possible to avoid “if throws” which take up a lot of lines unnecessarily.
With this helper, we will now be able to create our event message:send:

// socket/message.js
export const sendMessage = createEvent("message:send", {
  message: Joi.string().required().description("The content of the message"),
  to: Joi.string().optional().description("The name of the user to send the message")
}, async (socket, { message, to }) => {
  // Insert your logic here
});

We just created our first event, but now we need to be able to implement it in the socket, for that we are going to create a second helper which will interpret the result of our function createEvent :

// helpers/socket.js
/**
 * Bind an event to a socket
 * @param {String} name - The name of the event
 * @param {any} validation - A Joi object validation
 * @param {Function} fn - The function to be called on event
 */
export const bindEvent = (socket, {name, validation, fn}) => {
  socket.on(name, (payload = {}) => {
    if (validation) {
      Joi.validate(payload, validation, (error) => {
        if (error) {
          return socket.emit(name, {error});
        }
        fn(socket, payload);
      });
    }
    return fn(socket, payload);
  });
};

bindEvent allows you to create the event on the socket, to check the payload (if a schema has been passed to it), to return an error if it is not valid, and finally to call the callback by adding the socket current in it.
In your main file, you will therefore only have to use our method created previously so that our events are now available in the socket

// socket/index.js
import Io from "socket.io";
import { bindEvent } from "./../helpers/socket";
import * as messageHandlers from "./message";
const handlers = Object.values({
  ...messageHandlers
});
export default (listener) => {
  const io = Io.listen(listener);
  io.on("connection", (socket) => {
    handlers.forEach((handler) => {
      bindEvent(socket, handler);
    });
  });
};

Conclusion

By following the tips, you should have a server architecture that looks like this:

- helpers/ - index.js - socket.js - socket/ - index.js - friends.js - message.js - index.js

These methods are not to be taken literally, they are intended to give you some advice to help you better organize the logic of your sockets.
Do not hesitate to write comments if you know other techniques or if you have a way to further improve the tips mentioned above.
Article written by Christopher Brochard