Real-Time Magic: Harnessing the Power of Socket.IO with Node.js for Seamless Web Communication
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
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
add localhost:5000/socket
& hit connect
you should be able to see the results below
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
Now we have connected to Socket & we shall see the results in the Console like below…
in the Backgroud if you used the Redis as recommended you shall see the the recored is successfully added to the Redis like below…
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…
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 craetetokens
for users in login & use it like i did in the socket connection & the setup is not hard using a famousorm
likeprisma
orsequlize
to handle users data in register & login is easy withmysql
ormongodb
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