How to Build a Real-Time Chat App with Socket.io and Node.js

How to Build a Real-Time Chat App with Socket.io and Node.js

by | Jun 1, 2026 | Uncategorized | 0 comments

Why Build a Real-Time Chat App with Socket.io and Node.js?

Real-time communication is at the heart of modern web applications. Whether it is a customer support widget, a team collaboration tool, or a social messaging platform, users expect messages to appear instantly. Building a real-time chat app with Socket.io and Node.js is one of the best ways to learn how bidirectional, event-based communication works on the web.

In this tutorial, we will walk through every step: from understanding WebSocket fundamentals to writing server-side and client-side code, managing chat events, and finally deploying your finished application. By the end, you will have a fully functional chat app you can extend and customize.

What You Will Learn

  • How WebSockets differ from traditional HTTP requests
  • How to set up a Node.js server with Express
  • How to integrate Socket.io for real-time, bidirectional communication
  • How to handle events like connecting, disconnecting, and sending messages
  • How to add features such as usernames, typing indicators, and chat rooms
  • How to deploy your real-time chat app to production
chat application code editor

Prerequisites

Before we start, make sure you have the following installed and ready:

  • Node.js (v18 or later recommended)
  • npm (comes bundled with Node.js)
  • A code editor such as VS Code
  • Basic knowledge of JavaScript and HTML

Understanding WebSocket Fundamentals

Traditional HTTP communication follows a request-response model. The client sends a request, the server responds, and the connection closes. This works well for loading web pages, but it falls short for real-time applications where the server needs to push data to the client without being asked.

How WebSockets Work

WebSockets provide a persistent, full-duplex communication channel over a single TCP connection. Once the initial handshake is complete, both the client and server can send data to each other at any time without the overhead of opening new connections.

Feature HTTP WebSocket
Connection Opens and closes per request Persistent and stays open
Direction Client to server (request-response) Bidirectional (full-duplex)
Latency Higher (new connection each time) Lower (reuses connection)
Best For Static pages, REST APIs Chat apps, live feeds, gaming

Where Socket.io Fits In

Socket.io is a JavaScript library that abstracts WebSockets and adds powerful features on top, including automatic reconnection, fallback to HTTP long-polling, room-based broadcasting, and event-driven architecture. It consists of two parts:

  1. A server-side library that runs on Node.js
  2. A client-side library that runs in the browser

This makes Socket.io the go-to choice for building a real-time chat app with Node.js.

Step 1: Initialize the Project

Open your terminal and create a new project directory:

mkdir realtime-chat-app
cd realtime-chat-app
npm init -y

This creates a package.json file with default settings.

chat application code editor

Step 2: Install Dependencies

We need two packages to get started:

npm install express socket.io
  • express: A minimal web framework for Node.js to serve our HTML files and handle HTTP routes.
  • socket.io: The real-time engine that handles WebSocket connections and events.

Step 3: Set Up the Node.js Server

Create a file named server.js in the root of your project and add the following code:

const express = require('express');
const http = require('http');
const { Server } = require('socket.io');

const app = express();
const server = http.createServer(app);
const io = new Server(server);

app.use(express.static('public'));

app.get('/', (req, res) => {
  res.sendFile(__dirname + '/public/index.html');
});

const PORT = process.env.PORT || 3000;
server.listen(PORT, () => {
  console.log(`Server running on port ${PORT}`);
});

What Is Happening Here?

  1. We create an Express app and wrap it in a native Node.js HTTP server.
  2. We pass that HTTP server to Socket.io so it can listen for WebSocket connections on the same port.
  3. We serve static files from a public folder.
  4. The server listens on port 3000 by default.

Step 4: Create the Client-Side HTML

Create a public folder, and inside it, create index.html:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Real-Time Chat App</title>
  <style>
    * { margin: 0; padding: 0; box-sizing: border-box; }
    body { font-family: Arial, sans-serif; display: flex; flex-direction: column; height: 100vh; }
    #messages { flex: 1; overflow-y: auto; padding: 16px; list-style: none; }
    #messages li { padding: 8px 12px; margin-bottom: 8px; background: #f1f1f1; border-radius: 6px; }
    #messages li.system { background: #e8f5e9; font-style: italic; }
    form { display: flex; padding: 12px; background: #fafafa; border-top: 1px solid #ddd; }
    input { flex: 1; padding: 10px; border: 1px solid #ccc; border-radius: 4px; font-size: 16px; }
    button { padding: 10px 20px; margin-left: 8px; background: #007bff; color: #fff; border: none; border-radius: 4px; cursor: pointer; font-size: 16px; }
    button:hover { background: #0056b3; }
  </style>
</head>
<body>
  <ul id="messages"></ul>
  <form id="chat-form">
    <input id="message-input" autocomplete="off" placeholder="Type a message..." />
    <button type="submit">Send</button>
  </form>

  <script src="/socket.io/socket.io.js"></script>
  <script>
    const socket = io();
    const form = document.getElementById('chat-form');
    const input = document.getElementById('message-input');
    const messages = document.getElementById('messages');

    // Prompt for username
    const username = prompt('Enter your username:') || 'Anonymous';
    socket.emit('user-joined', username);

    form.addEventListener('submit', (e) => {
      e.preventDefault();
      if (input.value.trim()) {
        socket.emit('chat-message', input.value);
        input.value = '';
      }
    });

    socket.on('chat-message', (data) => {
      const li = document.createElement('li');
      li.textContent = data.username + ': ' + data.message;
      messages.appendChild(li);
      messages.scrollTop = messages.scrollHeight;
    });

    socket.on('system-message', (msg) => {
      const li = document.createElement('li');
      li.classList.add('system');
      li.textContent = msg;
      messages.appendChild(li);
      messages.scrollTop = messages.scrollHeight;
    });
  </script>
</body>
</html>

Key Points About the Client Code

  • Socket.io automatically serves its client library at /socket.io/socket.io.js, so you do not need to install anything extra on the front end.
  • We prompt the user for a username and emit a user-joined event to the server.
  • When the form is submitted, we emit a chat-message event with the text.
  • We listen for incoming chat-message and system-message events to update the UI.

Step 5: Handle Socket.io Events on the Server

Go back to server.js and add the Socket.io event handlers just before the server.listen line:

io.on('connection', (socket) => {
  console.log('A user connected:', socket.id);

  socket.on('user-joined', (username) => {
    socket.username = username;
    socket.broadcast.emit('system-message', `${username} has joined the chat`);
  });

  socket.on('chat-message', (msg) => {
    io.emit('chat-message', {
      username: socket.username || 'Anonymous',
      message: msg
    });
  });

  socket.on('disconnect', () => {
    if (socket.username) {
      io.emit('system-message', `${socket.username} has left the chat`);
    }
  });
});

Breaking Down the Events

Event Trigger Action
connection A client connects to the server Logs the socket ID and sets up listeners
user-joined Client sends a username after connecting Stores the username and broadcasts a join message
chat-message Client sends a message Broadcasts the message to all connected clients
disconnect Client closes the browser or loses connection Notifies remaining clients that the user left
chat application code editor

Step 6: Run and Test the Chat App

Start your server:

node server.js

Open http://localhost:3000 in two or more browser tabs. Enter a different username in each tab, then start sending messages. You should see messages appear instantly in all open tabs.

Congratulations! You now have a working real-time chat app built with Socket.io and Node.js.

Step 7: Add a Typing Indicator

A typing indicator makes your chat feel more polished. Here is how to implement one.

Client-Side (add to the script block in index.html)

let typingTimeout;
input.addEventListener('input', () => {
  socket.emit('typing');
  clearTimeout(typingTimeout);
  typingTimeout = setTimeout(() => {
    socket.emit('stop-typing');
  }, 1000);
});

socket.on('typing', (user) => {
  document.title = user + ' is typing...';
});

socket.on('stop-typing', () => {
  document.title = 'Real-Time Chat App';
});

Server-Side (add inside the connection handler)

socket.on('typing', () => {
  socket.broadcast.emit('typing', socket.username);
});

socket.on('stop-typing', () => {
  socket.broadcast.emit('stop-typing');
});

Step 8: Implement Chat Rooms

Socket.io has built-in support for rooms, which let you segment users into different channels.

Updated Server-Side Code

socket.on('join-room', (room) => {
  socket.join(room);
  socket.room = room;
  io.to(room).emit('system-message', `${socket.username} joined room: ${room}`);
});

socket.on('chat-message', (msg) => {
  const room = socket.room || null;
  const data = { username: socket.username || 'Anonymous', message: msg };
  if (room) {
    io.to(room).emit('chat-message', data);
  } else {
    io.emit('chat-message', data);
  }
});

Client-Side Addition

const room = prompt('Enter a room name (or leave blank for global chat):');
if (room) {
  socket.emit('join-room', room);
}

Now users who enter the same room name will only see messages from that room.

Step 9: Persist Messages with a Database (Optional)

If you want messages to survive a server restart, you can store them in a database. Here is a quick approach using MongoDB and Mongoose:

  1. Install Mongoose: npm install mongoose
  2. Connect to your MongoDB instance in server.js:
    const mongoose = require('mongoose');
    mongoose.connect('mongodb://localhost:27017/chatapp');
  3. Create a message schema:
    const messageSchema = new mongoose.Schema({
      username: String,
      message: String,
      room: String,
      timestamp: { type: Date, default: Date.now }
    });
    const Message = mongoose.model('Message', messageSchema);
  4. Save each message in the chat-message handler:
    socket.on('chat-message', async (msg) => {
      const data = { username: socket.username, message: msg, room: socket.room };
      await new Message(data).save();
      if (socket.room) {
        io.to(socket.room).emit('chat-message', data);
      } else {
        io.emit('chat-message', data);
      }
    });
  5. Load previous messages when a user connects or joins a room.
chat application code editor

Step 10: Deploy Your Real-Time Chat App

Once your chat application is ready, you need to deploy it so others can use it. Here are some popular options in 2026:

Platform Free Tier WebSocket Support Notes
Railway Yes (limited) Yes Easy deploy from GitHub
Render Yes Yes Good free tier for web services
Fly.io Yes (limited) Yes Great for low-latency apps
DigitalOcean App Platform No (paid) Yes Reliable for production use
AWS / GCP / Azure Free tiers vary Yes Best for large-scale applications

Deployment Checklist

  1. Set the PORT environment variable (most platforms provide this automatically).
  2. Make sure your package.json has a start script: "start": "node server.js"
  3. If using MongoDB, use a cloud-hosted database like MongoDB Atlas.
  4. Enable CORS in Socket.io if your front end and back end are on different domains:
    const io = new Server(server, {
      cors: {
        origin: 'https://yourdomain.com',
        methods: ['GET', 'POST']
      }
    });
  5. Push your code to GitHub and connect it to your hosting platform.

Complete Project Structure

Here is the final file structure for reference:

realtime-chat-app/
  public/
    index.html
  server.js
  package.json

Best Practices and Tips

  • Sanitize user input before rendering it in the browser to prevent XSS attacks. Consider using a library like DOMPurify.
  • Add authentication to identify users securely. JWT tokens or session-based auth work well with Socket.io.
  • Use namespaces in Socket.io to separate concerns (for example, /chat and /notifications).
  • Scale horizontally with the @socket.io/redis-adapter if you run multiple server instances behind a load balancer.
  • Rate limit messages to prevent spam and abuse in production environments.
  • Monitor connections using Socket.io’s built-in admin UI or tools like PM2 for process management.

Frequently Asked Questions

Is Socket.io the same as WebSockets?

No. Socket.io is a library built on top of WebSockets. It adds features like automatic reconnection, room support, event-based communication, and fallback transport mechanisms. While it uses WebSockets as the primary transport, it is not a plain WebSocket implementation.

Can I use Socket.io with a React or Vue front end?

Absolutely. Socket.io works with any front-end framework. You simply install the socket.io-client package via npm and connect to your server. There are also dedicated hooks and wrappers available for React, Vue, Angular, and other frameworks.

How many concurrent users can Socket.io handle?

A single Node.js server with Socket.io can typically handle thousands of concurrent connections. The exact number depends on your hardware, message frequency, and server logic. For larger scale, use the Redis adapter to distribute connections across multiple Node.js processes or servers.

Is this approach suitable for a production chat application?

The architecture covered in this tutorial is a solid foundation. For production, you should add authentication, input validation, message persistence, error handling, rate limiting, and horizontal scaling. These additions will make your real-time chat app robust and secure.

Do I need to use Express with Socket.io?

No. Express is optional. You can attach Socket.io to a plain Node.js HTTP server. However, Express makes it easy to serve static files and add REST API endpoints alongside your WebSocket functionality.

Can I save chat messages to a database?

Yes. As shown in Step 9, you can use MongoDB, PostgreSQL, MySQL, or any other database. Simply save the message inside the chat-message event handler on the server and load previous messages when a user connects.

Wrapping Up

Building a real-time chat app with Socket.io and Node.js is a rewarding project that teaches you the fundamentals of WebSocket communication, event-driven architecture, and full-stack JavaScript development. The combination of Node.js on the server and Socket.io for real-time events gives you a lightweight yet powerful stack that can scale from a side project to a production application.

We hope this guide helped you go from zero to a deployed chat app. If you have questions or want to share what you have built, feel free to reach out to us at boxsoftware.net.