Stop 22 · Build Backend Portfolio ProjectsProjects · 6/14

Real-time Chat Backend (WebSocket)

Part of the Backend Developer Roadmap

In short

A chat backend that works on one server is a tutorial. One that keeps rooms, history and online status working across many servers is a portfolio project.

A more complete real-time backend than the earlier practice project — supporting multiple rooms, message history, presence, and horizontal scaling via Redis pub/sub.

The purple blob talks into a walkie-talkie while friends in other rooms hear it.

Think of it like this

Walkie-talkies in the hostel

Once everyone's walkie-talkie is on the same channel, anyone can talk and everyone hears it instantly, no dialling each time. A WebSocket is that always-open channel between each phone and your server.

Watch it happen

Riya messages Kabir, who's on another server

Riya
Server A
Redis pub/sub
Server B

message → room cse

1/6Riya's phone sends the message over its open WebSocket to Server A.

The big ideas

What you need to know

Rooms and history

Users join rooms. Messages are saved, and older history loads with cursor pagination as you scroll up.

Presence

Track who's online with heartbeats and short Redis TTLs, so a dropped phone goes offline on its own.

Scale with pub/sub

Sockets stick to one server. A Redis pub/sub adapter lets a message reach users on every server.

Delivery and acks

Acknowledge saved messages and let clients retry with a client-generated ID, so retries don't duplicate.

See it in code

Walk through the code

ts
1import { Server } from "socket.io";2import { createAdapter } from "@socket.io/redis-adapter";3import { createClient } from "redis";4 5const pub = createClient({ url: process.env.REDIS_URL });6const sub = pub.duplicate();7await Promise.all([pub.connect(), sub.connect()]);8const io = new Server(3000, { adapter: createAdapter(pub, sub) });9io.on("connection", (socket) => {10  socket.on("join", (room: string) => socket.join(room));11  socket.on("message", async ({ room, text }, ack) => {12    const saved = await saveMessage(room, socket.data.userId, text); // DB first13    io.to(room).emit("message", saved); // reaches sockets on every server14    ack({ id: saved.id });15  });16});

1/3Two Redis connections, one to publish and one to subscribe, power the adapter.

Check that the user is allowed in a room before socket.join, and authenticate the socket during the handshake.

Save yourself the pain

Common mistakes

Oops

Running two servers without an adapter and wondering why some messages vanish.

Do this instead

Each server only knows its own sockets. Add the Redis adapter, or users on different servers can't hear each other.

Oops

Trusting the userId the client sends inside each message.

Do this instead

Authenticate once at connection time, store the user on socket.data, and use that.

Your mission

Milestone 1: two servers, one chat

~45 min

Quick check

Riya is connected to Server A, Kabir to Server B. Without a pub/sub adapter, what happens when Riya messages their room?

Go deeper

  1. 1Scale it across multiple server instances using a Redis pub/sub adapter.
  2. 2Add message delivery guarantees — persist messages before broadcasting.
  3. 3Support both one-to-one and group conversations.

More projects at this stop

Stuck on this topic? Ask an Insider

Get 1:1 guidance from people who've walked this exact path — on the InsideEdge app.

Download
InsideEdge