What is a WebSocket?
WebSocket is a communication protocol that enables two-way communication between a server and a client over a long-lived connection. A protocol is a set of rules and conventions that dictate how data is being transmitted between two or more computer devices.
Basically websockets are a set of ideas about one particular way where we work with data. Unlike traditional APIs and HTTP connections, websockets allow for real-time communication making it ideal for chart applications and sports betting sites.
The major difference between HTTP and WebSockets is how the client and the server communicate. HTTP requests send a request to the server then the server sends a response to the client and then the connection ends.
On the other hand, WebSockets sends a handshake request to the server and if the server is capable of supporting the websocket protocol it will send back a confirmation. Once the connection is established both parties can send and receive information without waiting for the request-response cycle.
Even though web sockets are long-lived they include mechanisms for detecting and handling dropped connections and inactivity. For example, you could specify that if neither the server nor the client has sent a message in the last ten minutes the connection should be stopped.
Picture this in an online chatting app. Two users are able to chat in real time with the application showing whether the other party is online, typing or has deleted a message. That is the real-world application of WebSockets.
Building a CRUD Operation using socket.io
We are going to build a CRUD application where you can create, read, update, and delete text using socket.io.
Setting up the environment
Prerequisites
Make sure node.js has been installed in your environment. If not, run the following command in your terminal globally:
sudo apt install -y nodejs
After installation, run the following command to check the version of node js installed:
node -v
Setting up the backend
In your code editor, create a folder named server
. After creating the folder, run the following command in your terminal, to install the necessary node modules, run this command:
npm install
To install the socket.io package in your backend folder, run this command:
npm install socket.io
Setting up the frontend
In your code editor, create a folder named client
. After creating the client folder, run npm install
to install the necessary node modules. After installing the node modules, it's time to install the socket.io package in your frontend folder using the following command:
npm install socket.io
Implementing the frontend
In the client folder you had created earlier, create a React app using vite by running the following command:
npm create vite@latest
After creating the React app we are going to write our code in the app.jsx. Here is the code:
import { useEffect, useState } from "react";
import { io } from "socket.io-client";
import "./App.css";
import { v4 as uuidv4 } from "uuid";
function App() {
const [formInputs, setFormInputs] = useState({});
const [crudData, setCrudData] = useState([]); // Initialize as an empty array
const [isEdit, setIsEdit] = useState(false);
const socket = io("http://localhost:3000");
const handleInput = (event) => {
const { name, value } = event.target;
setFormInputs((prev) => ({ ...prev, [name]: value }));
};
const handleSubmit = () => {
if (!formInputs.name || !formInputs.age || !formInputs.number) {
console.error("All fields must be filled before submitting.");
return;
}
const newData = {
...formInputs,
id: uuidv4(), // Generate a unique ID for the new data
};
socket.emit("data", newData); // Send the new data to the server
setFormInputs({}); // Clear the input fields after submission
};
const handleEdit = () => {
if (!formInputs.id) {
console.error("No item selected for editing.");
return;
}
console.log("Submitting edited data:", formInputs);
socket.emit("editData", formInputs); // Emit the edited data to the server
setIsEdit(false); // Reset edit mode
setFormInputs({}); // Clear the form inputs
};
const handleDelete = (id) => {
console.log("Deleting item with ID:", id);
socket.emit("deleteData", id); // Emit the delete request to the server
};
useEffect(() => {
// Listen for updated CRUD data from the server
socket.on("crudData", (response) => {
console.log("Updated data received from server:", response);
if (Array.isArray(response)) {
setCrudData(response); // Update the UI with the latest data
} else {
console.error("Unexpected response format:", response);
}
});
return () => {
socket.off("crudData"); // Clean up listener
};
}, []);
const getEditData = (data) => {
setFormInputs(data);
setIsEdit(true);
};
return (
<>
<h1>CRUD Operations</h1>
<div className="form-fields">
<input
onChange={handleInput}
className="input-field"
name="name"
placeholder="Enter your name"
value={formInputs.name || ""}
/>
<input
onChange={handleInput}
className="input-field"
name="age"
placeholder="Enter your age"
value={formInputs.age || ""}
/>
<input
onChange={handleInput}
className="input-field"
name="number"
placeholder="Enter your phone number"
value={formInputs.number || ""}
/>
</div>
<button onClick={isEdit ? handleEdit : handleSubmit}>
{isEdit ? "Edit" : "Add"} Data
</button>
<table>
<thead>
<tr>
<th>Name</th>
<th>Age</th>
<th>Phone Number</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
{Array.isArray(crudData) && crudData.length > 0 ? (
crudData.map((data, index) => (
<tr key={index}>
<td>{data.name}</td>
<td>{data.age}</td>
<td>{data.number}</td>
<td>
<button onClick={() => getEditData(data)}>Edit</button>
<button onClick={() => handleDelete(data.id)}>Delete</button>
</td>
</tr>
))
) : (
<tr>
<td colSpan="4">No data available</td>
</tr>
)}
</tbody>
</table>
</>
);
}
export default App;
Let's discuss this frontend code in detail. First, we start with the imports in lines 1 to 4:
-
useState and useEffect
: These are React hooks for managing state and side effects. -
io
: This is simply a socket.io package used for real-time communication to the server. -
uuidv4
: This is used to create a unique ID for each input in the application.
Now let's move to the initialization from lines 7 to 10:
-
formInput
: This is for storing the form inputs from the user of the app that is name, age, and phone number. -
crudData
: This is used to store data sent to the server or received from the server. -
isEdit
: It's used to check if the button is in the submit or edit mode.
In line 10 we are connecting the frontend to the server which is running in local host port 3000.
The handleInput
function in line 12 is used to update the formInputs state as the user types in the values.
The handleSubmit
function is used to submit the new data to the server. It works by checking if all the fields have been entered, if they are, it will create a new ID for the data and send it to the server. Finally, it clears the input field after the data has been submitted.
After that, we move to the handleEdit
function from line 32. The function works by checking if an input field has been selected for editing. If so, It will emit the edited data to the server. Then it resets the edit mode and clears the input field.
handleDelete
function is used to delete data from the UI and also submit the delete request to the server using socket.io for real-time communication.
The useEffect
hook is used to check for any updates to the crudData from the server then it updates it in our UI. By ui, I mean the frontend or what you see in the browser.
getEditData
function enables us to edit our data by setting the isEdit state to true.
The return method from lines 70 to 130 is used to render the UI. It displays the input fields for name, age, and phone number and a button that switches between edit and add. It also displays all the data we have and buttons to edit and delete each entry.
Implementing the Backend
Now let's move to the backend and discuss the nodejs code.
In the server folder create a file and name it server.js
.
const { createServer } = require("http");
const { Server } = require("socket.io");
// Create the HTTP server
const httpServer = createServer();
const io = new Server(httpServer, {
cors: {
origin: "http://localhost:5173", // Allow requests from the frontend
},
});
let crudData = []; // Array to store all CRUD data
io.on("connection", (socket) => {
console.log("A client connected");
// Send the current data to the newly connected client
socket.emit("crudData", crudData);
// Handle receiving new data from the client
socket.on("data", (data) => {
console.log("Received new data:", data);
// Add the new data to the array
crudData.push(data);
// Broadcast the updated data to all connected clients
io.emit("crudData", crudData);
});
// Handle editing data
socket.on("editData", (response) => {
console.log("Edit data received:", response);
// Find the index of the item to be edited
const currentIndex = crudData.findIndex((data) => data.id === response.id);
if (currentIndex !== -1) {
// Update the item
crudData[currentIndex] = { ...crudData[currentIndex], ...response };
// Broadcast the updated data to all clients
io.emit("crudData", crudData);
console.log("Updated data broadcasted:", crudData);
} else {
console.error("Item not found for editing");
}
});
// Handle deleting data
socket.on("deleteData", (id) => {
console.log("Delete request received for ID:", id);
// Filter out the item to delete
crudData = crudData.filter((data) => data.id !== id);
// Broadcast the updated data to all clients
io.emit("crudData", crudData);
console.log("Updated data after deletion:", crudData);
});
// Handle client disconnect
socket.on("disconnect", () => {
console.log("A client disconnected");
});
});
// Start the server
httpServer.listen(3000, () => {
console.log("Server is connected and listening on http://localhost:3000");
});
In lines 1 and 2, we are declaring an HTTP server and initializing socket.io for real-time communication.
Lines 4 to 10 create an HTTP server associated with socket.io and then connect to the front end which is running in port 5173 using the CORS policy which prevents it from blogging the connection. In line 12 we are declaring an empty array to store all the inputs from the frontend.
The io.on event in line 14 is an event that connects to the client and emits all the data in the server to the newly connected client. Socket.on the event in line 21 handles new data from the client and publishes the data in the crudData array using the push array method. It then broadcast the newly added data to the client using io.emmit.
In line 32 we have the socket.io editaData
event which handles data editing in the server. When the user clicks edit in the client, the server searches for the data using the findIndex method. If it exists it updates the data in the crudData array then it broadcasts the edited data to the client.
The socket.io deleteData
event in line 52 handles the deleting of data in the server. When the client initiates a delete function, the server finds the data using its ID and deletes it from crudData array using the filter method. The event then broadcasts the updated data to the client.
Testing
I am going to show you how real-time communication works with web sockets by opening two tabs and also show the server side.
As you can see from the video, initially, I had two entries in my applications then I added the third one and it immediately updated. Then I switched to another tab and there were also three entries same as the previous tab. I also deleted one entry and it was updated in both tabs.
When I switch to the server you can see the updated data after deletion. I then edited the name of the first entry from kiprop rono
to kiprop kiptoo
and it updated both in the server and the ui.
Conclusion
Socket.io is a very powerful tool that is used by various chat applications and betting apps for their real-time communication.
In this article, we have discussed what are WebSockets, WebSockets vs HTTP protocol, and its common application. We have also built a real-time CRUD application using react, socket.io, and node Js.
For more information, check out my GitHub link here.
Top comments (0)