Build a Real-Time Chat Application with Node.js
Lesson 3 of 10 30% of course

Building the Server — Express & Socket.IO

15 · 5 min · 6/12/2026

Learn Building the Server — Express & Socket.IO in our free Build a Real-Time Chat Application with Node.js series. Step-by-step explanations, examples, and interview tips on Toolliyo Academy.

Sign in to track progress and bookmarks.

Building the Server — Express & Socket.IO
Lesson 3 of 10 · Part 2 — Build the App · Build a Real-Time Chat Application with Node.js
Course: Build a Real-Time Chat Application with Node.js · Lesson: 3/10 · Read time: ~25 min · Level: Beginner

Building the Server — Express & Socket.IO

The server is the heart of our chat app. It listens for browser connections, places users into rooms, and broadcasts messages to everyone in that room. Create a file named server.js in the project root (same level as package.json).

Why we need three modules

ModulePurpose
expressServes static files (public/index.html, CSS, JS)
httpBuilt into Node — creates the HTTP server Express sits on
socket.ioUpgrades connections to real-time two-way messaging
💡 Tip: Socket.IO cannot attach directly to app.listen(). You must create http.createServer(app) first, then pass that server to new Server(server).

Full server.js — copy and read every comment

Save this complete file. Each section is explained below the code block.

// server.js — Real-time chat backend
// Step 1: Import required modules
const express = require('express');       // Web framework — serves HTML/CSS/JS files
const http = require('http');             // Creates the underlying HTTP server
const { Server } = require('socket.io'); // Socket.IO — real-time two-way communication

// Step 2: Create Express app and HTTP server
const app = express();
const server = http.createServer(app);    // Socket.IO needs an HTTP server, not just Express

// Step 3: Attach Socket.IO to the HTTP server
const io = new Server(server);

// Step 4: Serve static files from the "public" folder
// When browser requests /index.html, Express sends public/index.html
app.use(express.static('public'));

// Track online users per room (used in bonus lesson)
const rooms = {};

// Step 5: Handle new WebSocket connections
io.on('connection', (socket) => {
  // "socket" = one connected browser tab
  console.log('A user connected:', socket.id);

  // Event: user joins a named room (e.g. "general", "nodejs-help")
  socket.on('join-room', ({ username, room }) => {
    socket.username = username;  // Save on this socket for later messages
    socket.room = room;
    socket.join(room);           // Socket.IO rooms — like WhatsApp group chats

    // Track online users in this room
    if (!rooms[room]) rooms[room] = [];
    if (!rooms[room].includes(username)) rooms[room].push(username);

    // Tell everyone IN the room (not the joiner only) that someone joined
    io.to(room).emit('receive-message', {
      type: 'system',
      message: username + ' joined the room',
      timestamp: new Date().toISOString()
    });

    // Send updated online user list to everyone in the room
    io.to(room).emit('online-users', rooms[room]);
  });

  // Event: user sends a chat message
  socket.on('send-message', (message) => {
    io.to(socket.room).emit('receive-message', {
      type: 'chat',
      username: socket.username,
      message: message,
      timestamp: new Date().toISOString()
    });
  });

  // Event: user is typing (bonus feature)
  socket.on('typing', () => {
    socket.to(socket.room).emit('user-typing', {
      username: socket.username
    });
  });

  // Event: user closes tab or loses connection
  socket.on('disconnect', () => {
    console.log('User disconnected:', socket.id);
    if (socket.room && socket.username) {
      // Remove from online list
      if (rooms[socket.room]) {
        rooms[socket.room] = rooms[socket.room].filter(function (u) {
          return u !== socket.username;
        });
        if (rooms[socket.room].length === 0) delete rooms[socket.room];
      }

      io.to(socket.room).emit('receive-message', {
        type: 'system',
        message: socket.username + ' left the room',
        timestamp: new Date().toISOString()
      });

      io.to(socket.room).emit('online-users', rooms[socket.room] || []);
    }
  });
});

// Step 6: Start the server (Railway uses process.env.PORT)
const PORT = process.env.PORT || 3000;
server.listen(PORT, function () {
  console.log('Chat server running on http://localhost:' + PORT);
});

Line-by-line walkthrough

Imports (lines 1–4)

require() loads modules. http is built into Node — no npm install needed. Server from Socket.IO wraps our HTTP server with WebSocket support.

Express + HTTP + Socket.IO setup (lines 6–12)

express.static('public') means: when the browser requests /style.css, send public/style.css. When it requests /, send public/index.html if configured.

⚠️ Common Mistake: Using app.listen(3000) instead of server.listen(3000). If you listen on app only, Socket.IO may not receive connections correctly.

The connection event

io.on('connection', ...) fires every time a browser opens your site and Socket.IO connects. Each connection gets a unique socket.id — like a temporary ID card for that tab.

The join-room event

The client will emit('join-room', { username, room }). We:

  1. Save username and room on the socket object
  2. Call socket.join(room) — Socket.IO’s built-in room feature
  3. Broadcast a system message: “Alex joined the room”
  4. Send the online users list (bonus feature — already wired here)

The send-message event

When someone sends text, we use io.to(socket.room).emit(...) to push the message to everyone in that room, including the sender. The payload includes username, message text, and ISO timestamp.

The disconnect event

Fires when the user closes the tab or loses internet. We remove them from the online list and notify the room.

Starting the server

process.env.PORT || 3000 uses port 3000 locally, but Railway (Lesson 10) injects its own PORT environment variable.

👨‍🏫 Teaching note: Live-code this file slowly. After each event handler, console.log the event name so students see output when they test in Lesson 7.

Quick test (optional now, required in Lesson 7)

npm run dev

You should see: Chat server running on http://localhost:3000. The page will be blank or error until we add HTML — that is expected.

Summary

  • Express serves static frontend files from public/
  • Socket.IO listens for custom events: join-room, send-message, typing, disconnect
  • io.to(room).emit sends data to everyone in a room

Next: build the HTML the user actually sees.

Continue learning

Previous: Project Setup — Folder, npm & Dependencies

Next: Frontend HTML — Login Screen & Chat Layout

Course home: All 10 lessons

Test your knowledge

Quizzes linked to this course—pass to earn certificates.

Browse all quizzes
Build a Real-Time Chat Application with Node.js

On this page

Why we need three modules Full server.js — copy and read every comment Line-by-line walkthrough Imports (lines 1–4) Express + HTTP + Socket.IO setup (lines 6–12) The connection event The join-room event The send-message event The disconnect event Starting the server Quick test (optional now, required in Lesson 7) Summary Continue learning
Part 1 — Getting Started
Introduction — What We Are Building Project Setup — Folder, npm & Dependencies
Part 2 — Build the App
Building the Server — Express & Socket.IO Frontend HTML — Login Screen & Chat Layout Frontend CSS — Modern Chat UI Design Frontend JavaScript — Connect & Send Messages
Part 3 — Run, Learn & Extend
Running the App & Testing in Two Tabs How Socket.IO Works — Deep Dive
Part 4 — Ship It
Bonus Features — Typing, Online Users & Timestamps Deploy to Railway, Troubleshooting & Summary
Toolliyo Assistant
Ask about tutorials, ebooks, training, pricing, mentor services, and support. I use public site content only—not admin or internal tools.

care@toolliyo.com

Need callback? Share your details