This walkthrough covers React Real-Time Notifications with SSE. An authenticated same-origin React and Node application. This lesson uses server-sent events for one-way notifications and a durable notification table for recovery.
The video is an illustrated code walkthrough with AI narration. Code blocks are integration excerpts, not a complete tested application. Helper names such as stores, middleware and clients describe responsibilities you must implement in your existing project. No live server, payment provider or device execution is claimed.
Before you begin
Use a separate development environment, fictional data and test credentials. Record dependency versions and follow the official references below for your installed versions. Keep a working baseline and add one workflow at a time.
1. Choose the transport for the job
For one-way notification updates, server-sent events can provide a straightforward persistent channel. Ordinary requests still handle user actions. Keep the notification records in durable storage so a disconnected browser can recover. The live stream is a delivery mechanism, not the only copy of the message. If you need bidirectional low-latency interaction, evaluate a different transport against those requirements.
Server -> browser: server-sent events
Browser -> server: ordinary authenticated requests
Durable state: notifications table
Live connection: delivery hint, not the database2. Authenticate before opening the stream
Apply authentication before sending stream headers. Bind the connection to the verified user rather than a user identifier in the URL. Scope every delivered event to that user or their authorized audience. Native EventSource supports same-origin cookies, but not arbitrary authorization headers. Choose an authentication design that fits the client and avoid putting reusable access tokens into query strings.
app.get("/api/events", requireSession, (req,res) => {
res.set({
"Content-Type": "text/event-stream",
"Cache-Control": "no-cache",
"X-Accel-Buffering": "no"
});
res.flushHeaders();
// Register this connection only for req.user.id.
});3. Send a well-formed event
Each event has a stable identifier, an optional type, and a data payload followed by a blank line. Serialize the payload consistently. Keep events small and avoid including sensitive fields the interface doesn't need. Handle slow clients and backpressure instead of allowing an unbounded queue per connection. The durable notification record lets a client reload detail through an ordinary authorized request.
res.write("id: " + event.id + "\n");
res.write("event: notification\n");
res.write("data: " + JSON.stringify(event.payload) + "\n\n");4. Subscribe and clean up in React
Create the connection in an effect and close it when the component unmounts or the account changes. Validate received data and deduplicate by notification identifier before updating state. Show connection status without treating a temporary disconnect as data loss. Keep the notification list accessible, and avoid repeatedly interrupting assistive technology with every minor background event.
useEffect(() => {
const source = new EventSource("/api/events");
source.addEventListener("notification", event => {
receiveNotification(JSON.parse(event.data));
});
return () => source.close();
}, []);5. Replay missed events safely
Reconnects can include the last event identifier. Validate it and replay only the current user's authorized events after that point. Bound the replay window and size. If the cursor is too old, tell the client to fetch a fresh snapshot. Avoid a race between replay and subscribing to new events by using a coordinated durable feed or a documented reconciliation strategy.
const cursor = req.get("Last-Event-ID");
// Validate cursor and scope replay to req.user.id.
// SELECT ... WHERE user_id=? AND id>? ORDER BY id
// Cap replay size; offer a fresh snapshot if too old.6. Release disconnected clients
Clean up heartbeat timers and subscriptions when the connection closes. Configure proxy timeouts and buffering for streaming, and test through the real proxy rather than only localhost. Long-lived streams also need an authentication expiry policy. Close or revalidate connections when sessions expire or permissions change. Multiple Node processes need shared event distribution; an in-memory subscriber map reaches only one process.
const heartbeat = setInterval(() => res.write(": keepalive\n\n"), 20000);
req.on("close", () => {
clearInterval(heartbeat);
unsubscribeCurrentConnection();
});7. Keep read state on the server
Marking a notification as read is a normal protected mutation. Persist it so several tabs and devices can converge on the same state. Derive counts from an authoritative source or reconcile optimistic updates after failures. Don't assume every event is delivered exactly once. A reliable notification feature combines durable records, permission checks, deduplication, and recovery rather than relying only on a continuously open connection.
PATCH /api/notifications/:id
Body: {read: true}
Server: verify owner, persist read_at
Client: reconcile list and unread countIntegration checks
Test disconnect/reconnect, duplicate events, another user’s cursor, expired sessions, slow clients, proxy buffering and multiple application instances.
These are verification instructions, not reported test results. The excerpts have not been executed as a complete integrated application. Review authentication, database schema, dependency versions and operational settings before deployment.
Official references
Explore CodeKits development services.

