Real-time Chat Application
Part of the Full Stack Developer Roadmap
In short
Chat feels simple until you try it: messages must appear instantly for everyone in the room, survive refresh, and arrive in order. It's the classic real-time full stack project.
A complete chat app with rooms and persistence — bringing together WebSockets, auth, and database-backed message history into one polished full-stack product.

Think of it like this
The cricket commentary box
The commentator speaks once and every radio tuned to that match hears it live. The scorer writes every ball in the scorebook, so someone tuning in late can catch up. Live broadcast plus a written record: that's chat.
Watch it happen
One message, saved and broadcast
connect (auth token)
1/6The browser opens a WebSocket and sends its token. The server checks it before allowing anything.
The big ideas
What you need to know
One open connection
WebSockets keep a two-way pipe open, so the server can push new messages without the browser asking.
Save, then broadcast
Store the message before sending it out. Anyone who reconnects can load what they missed.
Rooms limit who hears
Each chat is a room. Check the user is a member before letting them join it.
Plan for drops
Phones lose signal. On reconnect, fetch messages after the last id you have.
See it in code
Walk through the code
1import { Server } from "socket.io";2const io = new Server(httpServer, { cors: { origin: process.env.WEB_URL } });3io.use(async (socket, next) => {4 socket.data.user = await verifyToken(socket.handshake.auth.token);5 next(socket.data.user ? undefined : new Error("unauthorised"));6});7io.on("connection", (socket) => {8 socket.on("join", async (roomId: string, ack: (msgs: unknown[]) => void) => {9 socket.join(roomId);10 ack(await db.message.findMany({ where: { roomId }, orderBy: { id: "desc" }, take: 50 }));11 });12 socket.on("send", async ({ roomId, text }: { roomId: string; text: string }) => {13 const msg = await db.message.create({ data: { roomId, text, userId: socket.data.user.id } });14 io.to(roomId).emit("message", msg);15 });16});1/3Connection middleware: no valid token, no socket. This runs once per connection.
A Socket.IO server sitting next to your API. It checks the token once on connect, sends history on join, and saves every message before broadcasting it.
Save yourself the pain
Common mistakes
Oops
Letting any connected user join any room id they send.
Do this instead
Check membership in the database inside the join handler before calling socket.join.
Oops
Keeping messages only in server memory.
Do this instead
Persist every message. Memory is lost on restart and isn't shared when you run two servers.
Your mission
First milestone: two tabs talking
Quick check
Why should the server save a chat message to the database before broadcasting it?
Go deeper
- 1Support multiple chat rooms or one-to-one conversations.
- 2Persist messages so history survives a refresh or reconnect.
- 3Add typing indicators and online/offline presence.
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.