Stop 14 · Real-time CommunicationProjects · 2/2

Live Notification System

Part of the Backend Developer Roadmap

In short

"Your order is out for delivery" popping up the second it happens feels magical. Behind it is an event, a saved notification and a push down an open connection.

A service that pushes real-time notifications to connected clients when events happen elsewhere in your system — practice for pub/sub and connecting real-time infra to your broader backend.

The blob as a station announcer rings a big brass bell while parcels light up on a shelf.

Think of it like this

The railway platform announcement

The station doesn't phone each passenger. When a train is late, the announcer rings the bell and speaks once, and everyone on that platform hears it. Passengers who arrive later check the display board. Live push for those online, saved list for the rest.

Watch it happen

From order shipped to a red badge

Order service
Notifier
Database
User's app

order.shipped #42

1/5The order service emits an event. It doesn't care how the user gets told.

The big ideas

What you need to know

Store, then push

The database is the truth; the live push is a bonus. Users who were offline still see everything later.

SSE is often enough

Notifications flow one way, server to user. Server-Sent Events are simpler than WebSockets for this.

Read state

Track read_at per notification, and add a mark-all-read endpoint. Badges must match what's actually unread.

Respect preferences

Let users mute types of notifications. Check preferences in the notifier, not in every service that emits events.

See it in code

Walk through the code

ts
1import type { Response } from "express";2const streams = new Map<string, Set<Response>>();3 4app.get("/notifications/stream", requireLogin, (req, res) => {5  res.set({ "Content-Type": "text/event-stream", "Cache-Control": "no-cache" });6  res.flushHeaders();7  const set = streams.get(req.user.id) ?? new Set<Response>();8  set.add(res);9  streams.set(req.user.id, set);10  req.on("close", () => set.delete(res));11});12 13export async function notify(userId: string, note: { title: string }) {14  const saved = await db.notification.create({ data: { userId, ...note } });15  for (const res of streams.get(userId) ?? []) res.write(`data: ${JSON.stringify(saved)}\n\n`);16}

1/3A logged-in user opens an event stream that stays open.

One user can have several tabs or devices open, so each user id maps to a set of streams. With more than one server, fan out through Redis Pub/Sub.

Save yourself the pain

Common mistakes

Oops

Only pushing live, never saving.

Do this instead

Offline users would miss everything. Save every notification and push as an extra.

Oops

Every service writing directly to user sockets.

Do this instead

Services emit events. One notifier decides who gets what, how, and respects mute settings.

Your mission

Milestone 1: a working bell icon

~40 min

Quick check

A user was offline when their order shipped. How should they see the notification when they come back?

Go deeper

  1. 1Trigger a notification via pub/sub when an event occurs, e.g. a new comment.
  2. 2Handle users who are offline — queue or store notifications for when they reconnect.
  3. 3Support multiple notification types with different payloads on one connection.

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