TrueSpec

WebSocket Chat with Socket.io

Rooms, namespaces, auth middleware, reconnection, and scaling

What You’ll Build

After following this guide, you will have a working implementation of websocket chat with socket.io in your project. Build a real-time chat application with Socket.io. Covers rooms (separate chat channels), authentication middleware (verify JWT before connection), typing indicators, online presence, and graceful reconnection. Socket.io handles WebSocket fallbacks and auto-reconnection.

Use Cases & Problems Solved

  • Build reliable server endpoints that clients can consume consistently
  • Handle common backend patterns like routing, middleware, and error handling
  • Provide a clean interface between your frontend and data layer

Prerequisites

  • Node.js 18+
  • Express.js

Step-by-Step Implementation

Install Socket.io

The following snippet shows how to install socket.io. Copy this into your project and adjust the values for your environment.

npm install socket.io socket.io-client

Server setup with auth and rooms

The following snippet shows how to server setup with auth and rooms. Copy this into your project and adjust the values for your environment.

const { Server } = require('socket.io');
const jwt = require('jsonwebtoken');

const io = new Server(httpServer, { cors: { origin: '*' } });

// Auth middleware — verify JWT before allowing connection
io.use((socket, next) => {
  const token = socket.handshake.auth.token;
  try {
    socket.user = jwt.verify(token, process.env.JWT_SECRET);
    next();
  } catch (err) {
    next(new Error('Authentication failed'));
  }
});

io.on('connection', (socket) => {
  console.log(\`\${socket.user.name} connected\`);

  socket.on('join-room', (roomId) => {
    socket.join(roomId);
    socket.to(roomId).emit('user-joined', { user: socket.user.name });
  });

  socket.on('message', ({ roomId, text }) => {
    io.to(roomId).emit('message', {
      user: socket.user.name,
      text,
      timestamp: new Date().toISOString(),
    });
  });

  socket.on('typing', ({ roomId }) => {
    socket.to(roomId).emit('typing', { user: socket.user.name });
  });

  socket.on('disconnect', () => {
    console.log(\`\${socket.user.name} disconnected\`);
  });
});

Client connection with auto-reconnect

The following snippet shows how to client connection with auto-reconnect. Copy this into your project and adjust the values for your environment.

import { io } from 'socket.io-client';

const socket = io('http://localhost:3000', {
  auth: { token: localStorage.getItem('jwt') },
  reconnection: true,
  reconnectionAttempts: 10,
  reconnectionDelay: 1000,
});

socket.emit('join-room', 'general');
socket.on('message', (msg) => appendMessage(msg));
socket.on('typing', ({ user }) => showTypingIndicator(user));

// Send message
function sendMessage(text) {
  socket.emit('message', { roomId: 'general', text });
}

⚠️ Don’t Do This

❌ Broadcasting to ALL connected users instead of a specific room

// Every user gets every message from every room!
io.emit('message', { room: roomId, text }); // BAD: io.emit is global!

✅ Use io.to(room) to send messages only to room members

// Only users in 'room-123' receive this message
io.to('room-123').emit('message', { text });

// To everyone in room EXCEPT sender:
socket.to('room-123').emit('message', { text });

Testing

Add these tests to verify your API endpoints work correctly:

// __tests__/api.test.ts
import { describe, it, expect } from 'vitest';

describe('WebSocket Chat with Socket.io', () => {
  it('should return 200 for valid requests', async () => {
    const res = await fetch('/api/endpoint', { method: 'GET' });
    expect(res.status).toBe(200);
    const data = await res.json();
    expect(data).toBeDefined();
  });

  it('should return 400 for invalid input', async () => {
    const res = await fetch('/api/endpoint', {
      method: 'POST',
      body: JSON.stringify({}),
    });
    expect(res.status).toBe(400);
  });

  it('should handle errors gracefully', async () => {
    const res = await fetch('/api/endpoint/nonexistent');
    expect(res.status).toBe(404);
  });
});

Verification

# Open two browser tabs to your chat app
# Join the same room in both tabs
# Send a message in one tab — should appear in both
# Type in one tab — typing indicator should appear in the other

Related Specs

Intermediate

Real-Time Updates with SSE

EventSource API, reconnection, broadcasting, and Express/Fastify SSE

API & Backend
Intermediate

Production-Ready REST API (Express)

Error handling, Zod validation, rate limiting, CORS, and structured logging

API & Backend
Intermediate

Background Jobs with BullMQ

Queue setup, workers, retries, job scheduling, and monitoring dashboard

API & Backend