Open In App

How to manage users in socket.io in Node.js ?

Improve
Improve
Like Article
Like
Save
Share
Report

Socket.IO is a library that enables real-time, bidirectional, and event-based communication between the browser and the server. Managing users in Socket.io and Node.js typically involves handling user connections, disconnections, and broadcasting messages to specific users or groups of users

Prerequisites

Approach to managing users in socket.io in Node.js

First, it is important to note that when a new socket is created, it is assigned a unique Id which is retrieved by calling socket.id. This id can be stored within a user object and we can assign an identifier such as a username. For the front end, we will use React, and for the back end node.js and express. Create two folders in your main directory name server(backend) and client(frontend). Socket.on will be an event that will be called whenever needed, and the event is called by the socket.emit, where we call the event and pass the parameter if needed.

Steps to Create Application

Backend Setup

Step 1: Initialize the project using the command

npm init

Step 2 : Install the requires dependencies:

npm i cors express nodemon socket.io http

Project Structure for Backend:

The updated dependencies in package.json file of backend will look like:

{
    "dependencies": {
        "cors": "^2.8.5",
        "express": "^4.18.2",
        "http": "^0.0.1-security",
        "nodemon": "^3.0.1",
        "socket.io": "^4.7.2"
    }
}

Example: Socket.on is used for join whenever a user will join frontend will emit join event and backend will emit message event and send the message that user has joined. Define connection, messaging and other user functions like add, remove and get users.

Javascript




// Filename - index.js
 
const express = require('express');
const socketio = require('socket.io');
const http = require('http');
const cors = require('cors');
const { addUser, removeUser, getUser,
    getUsersInRoom } = require("./users");
 
const app = express();
const server = http.createServer(app);
const io = socketio(server);
app.use(cors())
 
io.on("connection", (socket) => {
    socket.on('join', ({ name, room }, callback) => {
 
        const { error, user } = addUser(
            { id: socket.id, name, room });
 
        if (error) return callback(error);
 
        // Emit will send message to the user
        // who had joined
        socket.emit('message', {
            user: 'admin', text:
                `${user.name},
            welcome to room ${user.room}.`
        });
 
        // Broadcast will send message to everyone
        // in the room except the joined user
        socket.broadcast.to(user.room)
            .emit('message', {
                user: "admin",
                text: `${user.name}, has joined`
            });
 
        socket.join(user.room);
 
        io.to(user.room).emit('roomData', {
            room: user.room,
            users: getUsersInRoom(user.room)
        });
        callback();
    })
 
    socket.on('sendMessage', (message, callback) => {
 
        const user = getUser(socket.id);
        io.to(user.room).emit('message',
            { user: user.name, text: message });
 
        io.to(user.room).emit('roomData', {
            room: user.room,
            users: getUsersInRoom(user.room)
        });
        callback();
    })
 
    socket.on('disconnect', () => {
        const user = removeUser(socket.id);
        if (user) {
            io.to(user.room).emit('message',
                {
                    user: 'admin', text:
                        `${user.name} had left`
                });
        }
    })
 
})
 
server.listen(process.env.PORT || 5000,
    () => console.log(`Server has started.`));


Javascript




// Filename - User.js
 
const users = [];
 
const addUser = ({ id, name, room }) => {
    name = name.trim().toLowerCase();
    room = room.trim().toLowerCase();
 
    const existingUser = users.find((user) => {
        user.room === room && user.name === name
    });
 
    if (existingUser) {
        return { error: "Username is taken" };
    }
    const user = { id, name, room };
 
    users.push(user);
    return { user };
 
}
 
const removeUser = (id) => {
    const index = users.findIndex((user) => {
        user.id === id
    });
 
    if (index !== -1) {
        return users.splice(index, 1)[0];
    }
}
 
const getUser = (id) => users
    .find((user) => user.id === id);
 
const getUsersInRoom = (room) => users
    .filter((user) => user.room === room);
 
module.exports = {
    addUser, removeUser,
    getUser, getUsersInRoom
};


Frontend Setup:

Step 1: Install react for frontend using this command in terminal

npx create react-app client

Step 2: After react installed, install dependencies for Project inside client folder.

cd client
npm i query-string react-emoji react-router socket.io-client

Project Structure for frontend:

The updated dependencies in package.json file of frontend will look like:

    "dependencies": {
        "@testing-library/jest-dom": "^5.17.0",
        "@testing-library/react": "^13.4.0",
        "@testing-library/user-event": "^13.5.0",
        "query-string": "^8.1.0",
        "react": "^18.2.0",
        "react-dom": "^18.2.0",
        "react-emoji": "^0.5.0",
        "react-router": "^6.17.0",
        "react-scripts": "5.0.1",
        "socket.io-client": "^4.7.2",
        "web-vitals": "^2.1.4"
    },

Step 3: Inside App.js, create routes for the pages join page and chat page and import the components for both pages to display on that route.

Filename: App.js

Javascript




// Filename - App.js
import React from 'react';
 
import Chat from './components/Chat/Chat';
import Join from './components/Join/Join';
 
import { BrowserRouter as Router, Route }
    from "react-router-dom";
 
const App = () => {
    return (
        <Router>
            <Route path="/" exact component={Join} />
            <Route path="/chat" component={Chat} />
        </Router>
    );
}
 
export default App;


Javascript




// Filename - Chat/Chat.js
 
import React, { useState, useEffect } from "react";
import queryString from "query-string";
import io from 'socket.io-client';
import TextContainer from '../TextContainer/TextContainer';
import Messages from '../Messages/Messages';
import InfoBar from '../InfoBar/InfoBar';
import Input from '../Input/Input';
 
import "./Chat.css";
 
let connectionOptions = {
    "force new connection": true,
    "reconnectionAttempts": "Infinity",
    "timeout": 10000,
    "transports": ["websocket"]
};
 
let socket = io.connect('https://localhost:5000', connectionOptions);
 
 
const Chat = ({ location }) => {
 
    const [name, setName] = useState('');
    const [room, setRoom] = useState("");
    const [users, setUsers] = useState('');
    const [message, setMessage] = useState('');
    const [messages, setMessages] = useState([]);
 
    const ENDPOINT = 'localhost:5000';
 
    useEffect(() => {
        const { name, room } = queryString.parse(location.search);
 
        setName(name);
        setRoom(room);
 
        socket.emit('join', { name, room }, (error) => {
            if (error) {
                alert(error);
            }
        })
        return () => {
            socket.emit('disconnect');
            socket.off();
        }
 
    }, [ENDPOINT, location.search]);
 
    useEffect(() => {
        socket.on('message', (message) => {
            setMessages([...messages, message]);
        })
 
        socket.on("roomData", ({ users }) => {
            setUsers(users);
        });
    }, [messages, users])
 
    //Function for Sending Message
    const sendMessage = (e) => {
        e.preventDefault();
        if (message) {
            socket.emit('sendMessage', message, () => setMessage(''))
        }
    }
 
    console.log(message, messages);
 
    return (
        <div className="outerContainer">
            <div className="container">
 
                <InfoBar room={room} />
                <Messages messages={messages} name={name} />
                <Input message={message} setMessage={setMessage}
                    sendMessage={sendMessage} />
            </div>
            <TextContainer users={users} />
        </div>
    )
};
 
export default Chat;


Javascript




// Filename - InfoBar/InfoBar.js
 
import React from 'react';
 
import './InfoBar.css';
 
const InfoBar = ({ room }) => (
    <div className="infoBar">
        <div className="leftInnerContainer">
            <h3>{room}</h3>
        </div>
        <div className="rightInnerContainer">
            <a href="/">Leave</a>
        </div>
    </div>
);
 
export default InfoBar;


Javascript




// Filename - Input/Input.js
 
import React from 'react';
 
import './Input.css';
 
const Input = ({ setMessage, sendMessage, message }) => (
    <form className="form">
        <input
            className="input"
            type="text"
            placeholder="Type a message..."
            value={message}
            onChange={({ target: { value } }) => setMessage(value)}
            onKeyPress={event => event.key === 'Enter'
                ? sendMessage(event) : null}
        />
        <button className="sendButton"
            onClick={e => sendMessage(e)}>Send</button>
    </form>
)
 
export default Input;


Javascript




// Filename - Join/Join.js
 
import React, { useState } from "react";
import { Link } from 'react-router-dom';
 
import './Join.css';
 
const Join = () => {
 
    const [name, setName] = useState('');
    const [room, setRoom] = useState("");
 
    return (
        <div className="joinOuterContainer">
            <div className="joinInnerContainer">
                <h1 className="heading">Join</h1>
                <div>
                    <input placeholder="Name"
                        className="joinInput"
                        type="text"
                        onChange=
                        {(event) => setName(event.target.value)} />
                </div>
 
                <div>
                    <input placeholder="Room"
                        className="joinInput mt-20"
                        type="text" onChange=
                        {(event) => setRoom(event.target.value)} />
                </div>
 
                <Link onClick={e => (!name || !room) ?
                    e.preventDefault() : null}
                    to={`/chat?name=${name}&room=${room}`
                    }>
                    <button className={'button mt-20'}
                        type="submit">Sign In
                    </button>
                </Link>
            </div>
        </div>
    );
};
 
export default Join;


Javascript




// Filename - Messages/Message/Message.js
 
import React from 'react';
 
import './Message.css';
 
import ReactEmoji from 'react-emoji';
 
const Message = ({ message: { text, user }, name }) => {
    let isSentByCurrentUser = false;
 
    const trimmedName = name.trim().toLowerCase();
 
    if (user === trimmedName) {
        isSentByCurrentUser = true;
    }
 
    return (
        isSentByCurrentUser
            ? (
                <div className="messageContainer justifyEnd">
                    <p className="sentText pr-10">{trimmedName}</p>
 
 
                    <div className="messageBox backgroundBlue">
                        <p className="messageText colorWhite">
                            {ReactEmoji.emojify(text)}
                        </p>
 
 
                    </div>
                </div>
            )
            : (
                <div className="messageContainer justifyStart">
                    <div className="messageBox backgroundLight">
                        <p className="messageText colorDark">
                            {ReactEmoji.emojify(text)}
                        </p>
 
 
                    </div>
                    <p className="sentText pl-10 ">{user}</p>
 
 
                </div>
            )
    );
}
 
export default Message;


Javascript




// Filename - Messages/Messages.js
 
import React from 'react';
 
import Message from './Message/Message';
 
import './Messages.css';
 
const Messages = ({ messages, name }) => (
    <div>
        {messages.map((message, i) => <div key={i}>
            <Message message={message} name={name} />
        </div>)}
 
    </div>
);
 
export default Messages;


Javascript




// Filename TextContainer/TextContainer.js
 
import React from "react";
 
import onlineIcon from "../../icons/onlineIcon.png";
 
import "./TextContainer.css";
 
const TextContainer = ({ users }) => (
    <div className="textContainer">
        {users ? (
            <div>
                <h1>People currently chatting:</h1>
                <div className="activeContainer">
                    <h2>
                        {users.map(({ name }) => (
                            <div
                                key={name}
                                className="activeItem"
                            >
                                {name}
                                <img
                                    alt="Online Icon"
                                    src={onlineIcon}
                                />
                            </div>
                        ))}
                    </h2>
                </div>
            </div>
        ) : null}
    </div>
);
 
export default TextContainer;


CSS




/* Filename - Chat/Chat.css */
 
.outerContainer {
    display: flex;
    justify-content: center;
    align-items: center;
    height: 100vh;
    background-color: #1A1A1D;
}
 
.container {
    display: flex;
    flex-direction: column;
    justify-content: space-between;
    background: #FFFFFF;
    border-radius: 8px;
    height: 60%;
    width: 35%;
}
 
@media (min-width: 320px) and (max-width: 480px) {
    .outerContainer {
        height: 100%;
    }
 
    .container {
        width: 100%;
        height: 100%;
    }
}
 
@media (min-width: 480px) and (max-width: 1200px) {
    .container {
        width: 60%;
    }
}


CSS




/* Filename - InfoBar/InfoBar.css*/
 
.infoBar {
    display: flex;
    align-items: center;
    justify-content: space-between;
    background: #2979FF;
    border-radius: 4px 4px 0 0;
    height: 60px;
    width: 100%;
}
 
.leftInnerContainer {
    flex: 0.5;
    display: flex;
    align-items: center;
    margin-left: 5%;
    color: white;
}
 
.rightInnerContainer {
    display: flex;
    flex: 0.5;
    justify-content: flex-end;
    margin-right: 5%;
}
 
.onlineIcon {
    margin-right: 5%;
}


CSS




/* Filename - Input/Input.css */
 
.form {
    display: flex;
    border-top: 2px solid #D3D3D3;
}
 
.input {
    border: none;
    border-radius: 0;
    padding: 5%;
    width: 80%;
    font-size: 1.2em;
}
 
input:focus,
textarea:focus,
select:focus {
    outline: none;
}
 
.sendButton {
    color: #fff !important;
    text-transform: uppercase;
    text-decoration: none;
    background: #2979FF;
    padding: 20px;
    display: inline-block;
    border: none;
    width: 20%;
}


CSS




/* Filename - Join/Join.css*/
 
html,
body {
    font-family: 'Roboto', sans-serif;
    padding: 0;
    margin: 0;
}
 
#root {
    height: 100vh;
}
 
* {
    box-sizing: border-box;
}
 
.joinOuterContainer {
    display: flex;
    justify-content: center;
    text-align: center;
    height: 100vh;
    align-items: center;
    background-color: #1A1A1D;
}
 
.joinInnerContainer {
    width: 20%;
}
 
.joinInput {
    border-radius: 0;
    padding: 15px 20px;
    width: 100%;
}
 
.heading {
    color: white;
    font-size: 2.5em;
    padding-bottom: 10px;
    border-bottom: 2px solid white;
}
 
.button {
    color: #fff !important;
    text-transform: uppercase;
    text-decoration: none;
    background: #2979FF;
    padding: 20px;
    border-radius: 5px;
    display: inline-block;
    border: none;
    width: 100%;
}
 
.mt-20 {
    margin-top: 20px;
}
 
@media (min-width: 320px) and (max-width: 480px) {
    .joinOuterContainer {
        height: 100%;
    }
 
    .joinInnerContainer {
        width: 90%;
    }
}
 
button:focus {
    outline: 0;
}


CSS




/* Filename - Messages/Message/Message.css */
 
.messageBox {
    background: #F3F3F3;
    border-radius: 20px;
    padding: 5px 20px;
    color: white;
    display: inline-block;
    max-width: 80%;
}
 
.messageText {
    width: 100%;
    letter-spacing: 0;
    float: left;
    font-size: 1.1em;
    word-wrap: break-word;
}
 
.messageText img {
    vertical-align: middle;
}
 
.messageContainer {
    display: flex;
    justify-content: flex-end;
    padding: 0 5%;
    margin-top: 3px;
}
 
.sentText {
    display: flex;
    align-items: center;
    font-family: Helvetica;
    color: #828282;
    letter-spacing: 0.3px;
}
 
.pl-10 {
    padding-left: 10px;
}
 
.pr-10 {
    padding-right: 10px;
}
 
.justifyStart {
    justify-content: flex-start;
}
 
.justifyEnd {
    justify-content: flex-end;
}
 
.colorWhite {
    color: white;
}
 
.colorDark {
    color: #353535;
}
 
.backgroundBlue {
    background: #2979FF;
}
 
.backgroundLight {
    background: #F3F3F3;
}


CSS




/* Filename Messages/Messages.css */
 
.messages {
    padding: 5% 0;
    overflow: auto;
    flex: auto;
 }


CSS




/* Filename - TextContainer/TextContainer.css */
 
.textContainer {
    display: flex;
    flex-direction: column;
    margin-left: 100px;
    color: rgb(201, 25, 25);
    height: 60%;
    justify-content: space-between;
}
 
.activeContainer {
    display: flex;
    align-items: center;
    margin-bottom: 50%;
}
 
.activeItem {
    display: flex;
    align-items: center;
}
 
.activeContainer img {
    padding-left: 10px;
}
 
.textContainer h1 {
    margin-bottom: 0px;
}
 
@media (min-width: 320px) and (max-width: 1200px) {
    .textContainer {
        display: none;
    }
}


Step to run backend: Go inside the backend folder and open the terminal to write the following command.

npm start

Step to run frontend: Go inside the folder and open the terminal to write the following command.

npm start

Output: Open the browser and type localhost 3000 to see the application running.



Last Updated : 02 Nov, 2023
Like Article
Save Article
Previous
Next
Share your thoughts in the comments
Similar Reads