Skip to Content
Welcome to the official DBS Documentation! πŸ“š
AdvancedNotifications & SSE

Notifications & Real-Time (SSE)

DBS includes a full-featured real-time notification system that combines Server-Sent Events (SSE) for live push with SWR polling as a fallback. All state is managed through a global React Context.

Architecture

Browser (Client) └── EventSource('/api/stream/events') ← persistent SSE connection └── SWR polling /api/notifications ← 30s fallback Server (Next.js) └── /api/stream/events ← SSE endpoint └── eventBus (src/lib/events.ts) ← in-process EventEmitter └── fires 'system-event' when: β”œβ”€β”€ task-completed (background task done) └── broadcast (admin broadcast message)

Key Files

FilePurpose
src/lib/notification-package/NotificationContext.tsxReact Context + SSE connection lifecycle
src/lib/notification-package/useNotifications.tsSWR hook for notifications CRUD
src/lib/notification-package/useTasks.tsSWR hook for background task status
src/lib/events.tsIn-process Node.js EventEmitter (the event bus)
src/app/api/stream/events/route.tsSSE HTTP endpoint
src/app/api/notifications/route.tsREST API for notifications CRUD

Using the Notification System

Access Global State

Use useNotificationSystem() anywhere inside the dashboard:

'use client'; import { useNotificationSystem } from '@/lib/notification-package'; export function NotificationBell() { const { notifications, // Notification[] tasks, // Task[] unreadCount, // number markAsRead, // (id: string) => Promise<void> markAllAsRead, // () => Promise<void> refresh, // () => void β€” force re-fetch } = useNotificationSystem(); return ( <button> <Bell /> {unreadCount > 0 && <Badge>{unreadCount}</Badge>} </button> ); }

useNotificationSystem() must be called inside a component wrapped by NotificationProvider. In DBS, the provider is already mounted in src/app/dashboard/layout.tsx, so all dashboard pages have access automatically.

Emitting a Real-Time Event from the Server

Push a live event to connected clients from any API route using eventBus:

import { eventBus } from '@/lib/events'; // After a background task finishes eventBus.emit('system-event', { type: 'task-completed', userId: task.userId, // Only this user receives the event taskId: task.id, status: 'completed', });

SSE Endpoint Details

// src/app/api/stream/events/route.ts export async function GET(req: Request) { const guard = await apiGuard(); if (guard.error) return guard.error; const { session } = guard; const stream = new ReadableStream({ start(controller) { const encoder = new TextEncoder(); // Send heartbeat every 30 seconds const heartbeat = setInterval(() => { controller.enqueue(encoder.encode(': heartbeat\n\n')); }, 30_000); // Listen for events const handler = (data: SystemEvent) => { // Only send to the correct user (or broadcast to all) if (!data.userId || data.userId === session.user.id) { controller.enqueue( encoder.encode(`data: ${JSON.stringify(data)}\n\n`) ); } }; eventBus.on('system-event', handler); // Cleanup on disconnect req.signal.addEventListener('abort', () => { clearInterval(heartbeat); eventBus.off('system-event', handler); controller.close(); }); }, }); return new Response(stream, { headers: { 'Content-Type': 'text/event-stream', 'Cache-Control': 'no-cache', 'Connection': 'keep-alive', }, }); }
PropertyValue
EndpointGET /api/stream/events
Auth RequiredYes β€” returns 401 if unauthenticated
Heartbeat Interval30 seconds (: heartbeat SSE comment)
Auto-reconnectBrowser EventSource retries after ~3s by default

Notification REST API

MethodEndpointDescription
GET/api/notifications?page=1&limit=50Fetch paginated notifications
PATCH/api/notificationsMark one ({ id }) or all ({ all: true }) as read
DELETE/api/notificationsDelete one ({ id }) or all ({ all: true })
POST/api/notifications/broadcastCreate a notification for all users + emit SSE event

Notification Data Model

interface Notification { id: string; userId: string; title: string; message: string; type: 'info' | 'warning' | 'success' | 'error'; read: boolean; createdAt: string; }

The SSE pattern works well in a single-instance deployment (Vercel, Docker). If you run multiple instances (Kubernetes, horizontal scaling), the in-process eventBus will only fire to clients connected to the same instance. For multi-instance deployments, replace eventBus with a pub/sub system like Redis Pub/Sub or Upstash Redis.

Last updated on