[React + Node.js]: Implement Web Socket with Socket.io

React

Node.js

03/20/2020


Websocket

Typical approach of Node.js application requires client to request data, and the server sends the response back--this is HTTP protocol, but what if you want to notify a client when there's a change on the server? Imagine a messenger application. Your friend needs to be able to receive your message in real-time without having to request something to the server. This is where websocket comes into play. Built over HTTP, websockets enable two-way communication where the server can push data to the client without receiving request. Socket.io is a popular library that you can use to establish websocket.

Overview

Demo

As an example, I'll be using this MERN stack app to implement websocket. As user adds/deletes an entry, the updated data will be updated in real-time on other browsers as well. The complete project is available here.

Socket.io on the server

Install socket.io on the server side

BASH
npm i socket.io

Create a utility to initialize & get io

socket.js

JS
let io
module.exports = {
init: httpServer => {
io = require("socket.io")(httpServer)
return io
},
getIO: () => {
if (!io) {
throw new Error("Socket.io is not initialized")
}
return io
},
}

Creating socket io connection

app.js

JS
// ...
// DB connection
mongoose
.connect(MONGODB_URI)
.then(result => {
const port = process.env.PORT || 8080
const server = app.listen(port, () => {
console.log(`Listening on port ${port}...`)
})
// Create socket io connection
const io = require("./util/socket").init(server)
io.on("connection", socket => {
console.log("Client connected")
})
})
.catch(err => {
// Handle error
})

Check the import directory of socket util

Sending message to client on adding user

controllers/user.js

JS
const User = require("../models/user")
const io = require("../socket")
// ...
exports.postUser = async (req, res, next) => {
try {
let occupation = req.body.occupation
if (!occupation) occupation = "unemployed"
const user = new User({
name: req.body.name,
age: req.body.age,
occupation: occupation,
})
await user.save()
// Sends message to all connected users
io.getIO().emit("user event", {
action: "add",
user: { ...user._doc },
})
res.status(201).json({
message: "User created",
user: user,
})
} catch (err) {
if (!err.statusCode) {
err.statusCode = 500
}
next(err)
}
}

Sending message to client on deleting user

controllers/user.js

JS
exports.deleteUser = async (req, res, next) => {
const userId = req.params.userId
try {
const user = await User.findById(userId)
if (!user) {
const error = new Error("Couldn't find the user")
error.statusCode = 404
throw error
}
await User.findByIdAndRemove(userId)
// Sends message to all connected users
io.getIO().emit("user event", {
action: "delete",
userId: userId,
})
res.status(200).json({ message: "User removed" })
} catch (err) {
if (!err.statusCode) {
err.statusCode = 500
}
next(err)
}
}

Client side

Install socket.io-client on the client side

BASH
npm i socket.io-client

Create socket connection & Update state with received data

front-end/App.js

JSX
import React, { useState, useEffect } from "react";
import openSocket from "socket.io-client";
import User from "./user";
import UserAddForm from "./user-add-form";
import "./App.css";
const App = () => {
const [addLoading, setAddLoading] = useState(false);
const [deleteLoading, setDeleteLoading] = useState(false);
const [users, setUsers] = useState([]);
useEffect(() => {
fetchUsers();
createSocketConnection();
}, []);
const createSocketConnection = () => {
const socket = openSocket(process.env.REACT_APP_FETCH_URL);
socket.on("user event", data => {
if (data.action === "add") {
setUsers(prevUsers => [data.user, ...prevUsers]);
} else if (data.action === "delete") {
setUsers(prevUsers =>
prevUsers.filter(user => user._id !== data.userId)
);
}
});
};
const fetchUsers = async () => { ... };
const handleAdd = async formData => { ... };
const handleDelete = async userId => { ... };
return ( ... );
};

user event listens to the event that was sent from the server


WRITTEN BY

Keeping a record