How to Manage Users in Socket.io in Node.js ?

Last Updated : 3 Jun, 2026

In a real-time application, the server needs a way to keep track of who is connected and how to communicate with them individually or in groups. Socket.io handles this by assigning each client a unique connection and allowing the server to manage these connections efficiently. User management in Socket.io typically involves the following steps:

  • Map users to socket IDs: Link socket.id with user data (e.g., username).
  • Track active users: Store connected users in an object or map.
  • Remove on disconnect: Delete user data when the disconnect event fires.
  • Send targeted messages: Use socket IDs to emit events to specific users.
  • Group with rooms: Use rooms to manage communication for multiple users.

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 listens for a join event emitted by the frontend; the backend then emits a message event to notify that the user joined. It also handles connection, messaging, and user management functions like add, remove, and get users.

Node
// 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.`));
Node
// 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

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;
    }
}
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;

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

Comment

Explore