Setting Up Socket.io with Express

Socket.io attaches to the same HTTP server that Express already uses โ€” they share port 5000 in development without any conflict. The Socket.io server wraps the HTTP server, intercepts WebSocket upgrade requests, and handles socket lifecycle events alongside Express’s normal route handling. In this lesson you will install Socket.io, attach it to the Express HTTP server, configure CORS for the socket connection, emit your first test events, and verify the real-time channel is working before adding any application-specific logic.

Installation

cd server
npm install socket.io

cd ../client
npm install socket.io-client

Attaching Socket.io to Express

// server/index.js โ€” modified to support Socket.io
require('dotenv').config();

const express    = require('express');
const http       = require('http');    // โ† needed to share server with Socket.io
const { Server } = require('socket.io');
const cors       = require('cors');
const connectDB  = require('./src/config/db');

const app    = express();
const server = http.createServer(app); // wrap Express app in HTTP server

// โ”€โ”€ Socket.io attached to the same HTTP server โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
const io = new Server(server, {
  cors: {
    origin:      process.env.CLIENT_URL || 'http://localhost:5173',
    methods:     ['GET', 'POST'],
    credentials: true,
  },
  pingTimeout:  60000, // disconnect after 60s without ping
  pingInterval: 25000, // ping every 25s to keep connection alive
});

// โ”€โ”€ Express middleware โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
app.use(cors({ origin: process.env.CLIENT_URL, credentials: true }));
app.use(express.json());

// โ”€โ”€ Express routes โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
app.use('/api/auth',  require('./src/routes/auth'));
app.use('/api/posts', require('./src/routes/posts'));

// โ”€โ”€ Socket.io connection handler โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
io.on('connection', (socket) => {
  console.log(`Socket connected: ${socket.id}`);

  socket.on('disconnect', (reason) => {
    console.log(`Socket disconnected: ${socket.id} โ€” ${reason}`);
  });

  socket.on('error', (err) => {
    console.error(`Socket error on ${socket.id}:`, err.message);
  });
});

// โ”€โ”€ Start server โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
const PORT = process.env.PORT || 5000;
const start = async () => {
  await connectDB();
  server.listen(PORT, () => console.log(`Server + Socket.io โ†’ http://localhost:${PORT}`));
};

start().catch(err => { console.error(err); process.exit(1); });

// โ”€โ”€ Export io for use in route handlers โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
module.exports = { app, io };
Note: The key change from a plain Express setup is wrapping app with http.createServer(app) and calling server.listen(PORT) instead of app.listen(PORT). Socket.io needs access to the raw HTTP server (not the Express app) to intercept the WebSocket upgrade request. Express and Socket.io share the same port with no conflict โ€” HTTP requests go to Express, WebSocket upgrade requests go to Socket.io.
Tip: Export the io instance from index.js so you can use it inside route handlers and controllers to emit events when data changes. When a user posts a comment via POST /api/posts/:id/comments, the comment controller can import io and emit a new-comment event to the relevant room immediately. This connects your REST API mutations to real-time notifications.
Warning: Socket.io’s CORS configuration is separate from Express’s CORS configuration. Even if Express allows your React origin, Socket.io will block the WebSocket upgrade if its own CORS is not configured. Always set the cors option in the new Server(server, { cors: {...} }) call, matching the same origin you set for Express.

Verifying the Connection with a Browser Test

// Quick browser console test โ€” paste in DevTools console
// (while Socket.io client script is loaded or from the React app)

const socket = io('http://localhost:5000');

socket.on('connect',    ()    => console.log('Connected! ID:', socket.id));
socket.on('disconnect', ()    => console.log('Disconnected'));
socket.on('error',      (err) => console.error('Socket error:', err));

// Emit a test event
socket.emit('ping', { message: 'hello from browser' });

// Server console should log: Socket connected: abc123xyz

Accessing io in Controllers

// server/src/config/socket.js โ€” central socket access
let io;

const initSocket = (serverIo) => {
  io = serverIo;
};

const getIO = () => {
  if (!io) throw new Error('Socket.io not initialised');
  return io;
};

module.exports = { initSocket, getIO };

// server/index.js
const { initSocket } = require('./src/config/socket');
const socketIo = new Server(server, { cors: { ... } });
initSocket(socketIo); // store reference

// server/src/controllers/commentController.js
const { getIO } = require('../config/socket');

const createComment = asyncHandler(async (req, res) => {
  const comment = await Comment.create({ ... });

  // Emit to all clients viewing this post
  getIO()
    .to(`post:${req.params.postId}`)
    .emit('new-comment', comment);

  res.status(201).json({ success: true, data: comment });
});

Socket.io Server-Side API โ€” Key Methods

Method Target Example
socket.emit(event, data) This socket only Respond to the sender
socket.broadcast.emit(event, data) All sockets except this one Notify others of a user joining
io.emit(event, data) All connected sockets Server-wide announcement
io.to(room).emit(event, data) All sockets in a room Post-specific event
socket.join(room) โ€” Subscribe this socket to a room
socket.leave(room) โ€” Unsubscribe from a room
socket.to(room).emit(event, data) Room except this socket Notify room without echo

Common Mistakes

Mistake 1 โ€” Calling app.listen() instead of server.listen()

โŒ Wrong โ€” Socket.io attached to the HTTP server but Express listens separately:

const server = http.createServer(app);
const io     = new Server(server, { ... });
app.listen(5000); // wrong! Socket.io is on server, not app

โœ… Correct โ€” listen on the HTTP server:

server.listen(5000); // โœ“ both Express and Socket.io share port 5000

Mistake 2 โ€” Missing Socket.io CORS configuration

โŒ Wrong โ€” Express CORS configured but Socket.io CORS not set:

app.use(cors({ origin: 'http://localhost:5173' })); // Express only
const io = new Server(server); // no Socket.io CORS โ†’ browser blocked!

โœ… Correct โ€” configure CORS on the Socket.io server too:

const io = new Server(server, {
  cors: { origin: 'http://localhost:5173', credentials: true },
}); // โœ“

Mistake 3 โ€” Using io.emit() for post-specific events

โŒ Wrong โ€” broadcasting a comment to ALL connected clients:

io.emit('new-comment', comment); // all 1000 connected users get this!

โœ… Correct โ€” emit only to the relevant room:

io.to(`post:${postId}`).emit('new-comment', comment); // โœ“ only post viewers

Quick Reference

Task Code
Wrap Express in HTTP server const server = http.createServer(app)
Create Socket.io server const io = new Server(server, { cors: {...} })
Listen for connections io.on('connection', socket => { ... })
Listen on same port server.listen(PORT)
Emit to room io.to('post:id').emit('event', data)
Emit to all io.emit('event', data)

🧠 Test Yourself

You set up Socket.io but React cannot connect โ€” the browser console shows a CORS error on the WebSocket upgrade request. Your Express CORS is configured correctly. What is the most likely cause?