Real-Time Magic: Harnessing the Power of Socket.IO with Node.js for Seamless Web Communication

Mahmoud Bebars
8 min readFeb 16, 2024

--

I have been Building web services for almost 2 years for now & I did not face a problem to look for Real-Time Communication, but Using Sockets is one of the best Features you can add-on your projects.

What is Real-Time Communication ? (Web Sockets)

Imagine a conversation, Traditional web communication (HTTP) is like sending letters through the mail. You write a letter (send a request), mail it to someone (send the request to the server), and then wait for a reply (receive the response). It’s a one-time, back-and-forth process.

on the other hand, Web Sockets are like having a direct phone line to the person you want to talk to. You can talk and listen at the same time, and the connection stays open. This means you can have a more interactive and immediate conversation.

What’s Socket.io ?

simply it acts like a translator. It allows you to communicate over WebSockets but in a way that’s simpler and more familiar. So, instead of learning a new language, you can speak in a language you already know like Javascript and Socket.IO takes care of translating it into the WebSocket language.

Setup Fastify Server

we are going to explain the Setup simply using NodeJS &FastifyJS

First Let’s setup a simple Fastify Server

after npm init -y we will create server.js file like Below…

// ./server.js
require("dotenv").config();

const path = require("path");

// CommonJs
const fastify = require("fastify")({
logger: true,
});

/**
* Configure CORS - which is one of best Paractice for using Sockets
*/
fastify.register(require("@fastify/cors"), {
origin: [process.env.APP_URL],
methods: "*",
allowedHeaders: ["Content-Type", "Authorization"],
});

/**
* Fastify Redis Register - Setup Redis to use it Later With Socket.io
*/
fastify.register(require("@fastify/redis"), {
host: process.env.REDIS_HOST,
port: process.env.REDIS_PORT,
});

/**
* Autoload Plugins
*/
fastify.register(require("@fastify/autoload"), {
dir: path.join(__dirname, "plugins"),
});

/**
* Run the server!
*/
const start = async () => {
try {
await fastify.listen({ port: process.env.PORT, host: process.env.HOST });
const redis = fastify.redis;
fastify.log.info(
`Redis listening at http://${redis.options.host}:${redis.options.port}`
);
} catch (err) {
fastify.log.error(err);
process.exit(1);
}
};
start();

Let’s Explain Every thing…

First we will install The Needed Packages for our Server

// Basic Dependencies
npm install fastify dotenv socket.io @fastify/autoload @fastify/cors @fastify/redis

npm install -D nodemon // For Hot Load Server Updates

npm install -g pino-pretty // usage to formate fastify logging

First we have have required dotenv to use the .env which i will leave a starter file for it later, then created the fastify instance with the needed options logger: true

configure the CORS & define the available origins, methods to use & headers.

now it’s the time for Redis which is not a required step for now but i will use it later to impower the socket usage, simply we denine it & give it the needed values REDIS_HOST, REDIS_PORT

adding the @fastify/autoload enables us to load all the plugins we need which is useful because we are going to create the Socket as a fastify Plugin to use it in all the controllers easily.

Lastly init the Server Start Function to Run it in the Defined PORT & HOST in the .env , with logging to ensure evert thing Runs will.

you should hit nodemon server.js | pino-pretty & see the Console like this

server start terminal example

Setup Socket.io

Now we are all set Let’s setup our Socket.io Plugin.

First create a new directory plugins to store socket setup in it

create socket.js file.

// ./plugins/socket.js

// here we define the fastify plugin
const fp = require("fastify-plugin");

// craeting socket module
module.exports = fp(async function (fastify, opts) {
// define the Server instance from socket.io
const { Server } = require("socket.io");

// here we call the redis from the fastify instance as a plugin
const { redis } = fastify;

// configuration the cors irigin for the Socket
const io = new Server(fastify.server, {
cors: { origin: "*" },
});

fastify.decorate("socket", io);

fastify.ready((err) => {
if (err) throw err;
// defineing a path route for the socket
const connection = io.of("/socket");

// authentication middleware - JWT assuming we have authentication system
mainSocket.use(async (socket, next) => {
const { token } = socket.handshake.query;

if (token) {
const { user_id, name, role } = fastify.jwt.verify(token);
socket.name = name;
socket.user_id = user_id;
socket.role = role;
// here we store the user_id as a key &socket id as a value in redis
await redis.setex(user_id, socket.id);
next();
}
});

mainSocket.on("connection", async (socket) => {
console.log(
`${socket.name} with id: ${socket.user_id} has role: ${socket.role.name} connected! with socket id: ${socket.id}`
);

// connect user to room - for testing
socket.join("chatting");
socket.on("disconnect", async () => {
// when user disconnect remove the user_id & socket_id from the redis
await redis.del(socket.user_id);
console.log(`${socket.name} disconnected!`);
});
});
});
});

Let’s Explain everthing for the setup

First I assume we have an authentication System which at least store user data & generate JWT token for him storing some data for securely connect only the my users to the Socket.

if not remove the Authentication setup it will work fine, & i will Explain the Authorization System Later.

we use the redis to use it in store user_id & socket_id to recall them with any action needed.

make the user join a room to know where to send messages to him in any action we make

& lastly in the Disconnect we remove the redis record.

in Fastify we work with the Plugins style to enable us to recall the plugin anywhere in the app.

Test Socket

Now we setup the Socket Plugin we will test it using Postman

First open Postman

start new Socket connection

postman screen to start new connection

add localhost:5000/socket & hit connect you should be able to see the results below

Connection Page (Postman)

if you have setup your authorization system you shall use token as query parameter in the connection else just connect without token.

I recommend to use Access Token in Production Project to secure your connection specifically if you are going to talk to Mobile Application

so we have joined the chatting room now enable your connection to listen to it

listening to events in postman

Now we have connected to Socket & we shall see the results in the Console like below…

Connection console

in the Backgroud if you used the Redis as recommended you shall see the the recored is successfully added to the Redis like below…

the photo explain the recored user using redisinsight app

about the last photo this is Redisinsight which i recommend to see the redis database, easy to use & setup

Socket Usage

We are connected to Socket, I will add a simple usage

first create conntrollers & routes directory

we will create a Simple Controller to test the socket usage

// ./controllers/SocketController
module.exports.socketTest = async (request, reply) => {
try {
// define the redis & socket plugins from Fastify instance
const { socket, redis } = request.server;

// define Request Body
const { user_id, message } = request.body
// Retrive Socket_id from redis with user id
const socket_id = await redis.get(user_id);

/**
* Some Actions can be Done Here like storing the message in Database
*/

// send a message to the user Socket
socket
.of("/socket")
.to(socket_id)
.emit("chatting", message);

// returing Results to close the HTTP Request
return reply.code(200).send({
code: 200,
results: "Sent Emit Message to " + socket_id,
});
} catch (error) {
return reply.code(400).send(error);
}
};

Here it’s looks simple we have made a request Handler to test the Socket

the .of('/socket') specifies in which path we are going to broadcast our message & .to(socket_id) tells the socket to which user & this means that the message will only go to this user, lastly .emit("chatting",message) telling the socket to send to the room user is listening to & the conent we want to send to user connected.

Now Lets Make a Router file

// ./routes/Test.js
const SocketController = require("../controllers/SocketController");

/**
* Encapsulates the routes
* @param {FastifyInstance} fastify Encapsulated Fastify Instance
* @param {Object} options plugin options, refer to https://www.fastify.io/docs/latest/Reference/Plugins/#plugin-options
*/
async function routes(fastify, options) {
fastify.post("/test", StampController.stampCard);
}

module.exports = routes;

the best practice is to make index.js in ./routes to store all the routers in it

module.exports.routes = [
{ routeName: "Test", prefix: "/api/socket" },
];

in the ./server.js let’s define the Routes

before the start function add

/**
* Load Routes - From ./route/index (Array style)
*/
const { routes } = require("./routes/index");

// load the routes from the array in ./routes/index.js
routes.forEach(({ routeName, prefix }) => {
const route = path.join(__dirname, "routes", `${routeName}.js`);

fastify.register(require(route), { prefix });
});

/**
* Not Found Route
*/
fastify.setNotFoundHandler(function (request, reply) {
return reply.code(404).sendFile("404.html");
});

start your server again & connect to the Socket

now a normal HTTP — POST with with a body has user_id & message we will see the results in the socket connection like below…

received message

Now we have created the Socket & tested it.

Consolation

  • using the Socket is not Hard but we should take care of some things to ensure The security & Performance like using accesstoken to secure our users connections & enable us to ensure a defined types of user to connect such making socket for users & other channel for admins, this will be done using the role of users & well created authorization system.
  • using the socket in our requests easily & i recommend using fastify it enable us to make the work through the plugins as we saw so it will be easy using it in any place of the app.
  • lastly the redis is optional for use but using it empower the Socket by storing only the information we want & using it again easily rather then storing it in normal database & manage database connections which using socket connections & disconnect actions.

Recommended to use fasftify-jwt to craete tokens for users in login & use it like i did in the socket connection & the setup is not hard using a famous orm like prisma or sequlize to handle users data in register & login is easy with mysql or mongodb

lastly this .env file is contain all the needed variables in the project

& lastly i will leave a complete example for using socket.io & a simple authentication system with authorization process in this repository.

i will be happy for an discussions about this topic or any other topics, you can reach me throught instagram or linkedin

--

--

Mahmoud Bebars
Mahmoud Bebars

Written by Mahmoud Bebars

A Developer looking to create problems solve 🤝

Responses (1)