Tutorials Build a Real-Time Chat Application with Node.js

Building the Server — Express & Socket.IO

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.

On this page
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

Interview prep for this lesson

Practice these questions aloud after reading—each links to a full structured answer.

Junior Detailed
Explain Design in the context of Build a Real-Time Chat Application with Node.js.
Short answer: Interviewers want a crisp definition, a practical example from your projects, and awareness of trade-offs—not textbook dumps. How to structure your answer (60–90 seconds) Define Design in plain language for…
Mid Detailed
What are common mistakes teams make with Implementation when using Build a Real-Time Chat Application with Node.js?
Short answer: Interviewers want a crisp definition, a practical example from your projects, and awareness of trade-offs—not textbook dumps. How to structure your answer (60–90 seconds) Define Implementation in plain lang…
Senior Detailed
How would you debug a production issue related to Trade-offs in a Build a Real-Time Chat Application with Node.js application?
Short answer: Interviewers want a crisp definition, a practical example from your projects, and awareness of trade-offs—not textbook dumps. How to structure your answer (60–90 seconds) Define Trade-offs in plain language…
Mid Detailed
Compare two approaches to Testing—when would you choose each?
Short answer: Interviewers want a crisp definition, a practical example from your projects, and awareness of trade-offs—not textbook dumps. How to structure your answer (60–90 seconds) Define Testing in plain language fo…
Junior Detailed
Describe a real-world scenario where Demo mattered in a Build a Real-Time Chat Application with Node.js project.
Short answer: Interviewers want a crisp definition, a practical example from your projects, and awareness of trade-offs—not textbook dumps. How to structure your answer (60–90 seconds) Define Demo in plain language for B…
Questions on this lesson 0

Sign in to ask a question or upvote helpful answers.

No questions yet — be the first to ask!

Build a Real-Time Chat Application with Node.js
Course syllabus
Part 1 — Getting Started
Part 2 — Build the App
Part 3 — Run, Learn & Extend
Part 4 — Ship It
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