Real-Time Notifications (WebSocket/SSE) Integration
An e-commerce site with 50,000 products: standard polling every 5 seconds generates 10,000 requests per minute — the database crashes, server time wasted. Switching to SSE (Server-Sent Events) cuts the load by 10x: one persistent connection instead of tens of thousands of short-lived ones. We integrate instant notification systems using WebSocket or SSE — alerts without page reload. In 2–3 days you get a turnkey solution stable under 10,000+ concurrent connections. Our team's experience — 10+ years in production highload projects. This not only reduces server load but also cuts infrastructure costs by up to 40%. Our company has delivered 50+ real-time notification projects over 5 years, helping clients save an average of $2000/month on server costs.
How to Choose Between WebSocket and SSE?
SSE — unidirectional stream from server to client over regular HTTP/2. Works with EventSource, auto-reconnects, no libraries needed. Ideal for: new messages, status updates, alerts, system notifications.
WebSocket — bidirectional channel on its own ws protocol. Needed when the client also sends data in real time: chat, games, collaborative editing. Implementation is more complex, requires libraries (Socket.IO, ws).
| Criteria | SSE | WebSocket |
|---|---|---|
| Direction | Server→Client | Bidirectional |
| Protocol | HTTP/2 | ws:// |
| Auto-reconnect | Built-in | Needs implementation |
| Client libraries | EventSource (built-in) | ws/socket.io |
| Use cases | Notifications | Chat, games |
In practice, for notifications SSE is enough in 80% of projects. We add WebSocket when two-way logic (read receipts, typing indicator) is needed. SSE is 10x more efficient than polling in terms of server load and latency.
Implementing SSE on Node.js
// server/routes/notifications.ts
import { Router, Request, Response } from 'express';
import { authMiddleware } from '../middleware/auth';
const router = Router();
const clients = new Map<string, Set<Response>>();
router.get('/stream', authMiddleware, (req: Request, res: Response) => {
const userId = req.user!.id;
res.writeHead(200, {
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache',
'Connection': 'keep-alive',
'X-Accel-Buffering': 'no', // important for nginx
});
const heartbeat = setInterval(() => {
res.write(':heartbeat\n\n');
}, 30_000);
if (!clients.has(userId)) clients.set(userId, new Set());
clients.get(userId)!.add(res);
getUnreadNotifications(userId).then((notifications) => {
res.write(sseEvent('init', notifications));
});
req.on('close', () => {
clearInterval(heartbeat);
clients.get(userId)?.delete(res);
if (clients.get(userId)?.size === 0) clients.delete(userId);
});
});
function sseEvent(type: string, data: unknown, id?: string): string {
let msg = '';
if (id) msg += `id: ${id}\n`;
msg += `event: ${type}\n`;
msg += `data: ${JSON.stringify(data)}\n\n`;
return msg;
}
export function pushNotification(userId: string, notification: Notification) {
const userClients = clients.get(userId);
if (!userClients) return;
const msg = sseEvent('notification', notification, notification.id);
userClients.forEach((res) => res.write(msg));
}
export default router;
Client side — native EventSource with exponential backoff on errors. We integrate with any toast library (sonner, react-hot-toast).
Scaling SSE with Redis Pub/Sub
When horizontally scaling (multiple instances), a user may be connected to instance A while a notification is generated by instance B. Solution — Redis Pub/Sub:
import { createClient } from 'redis';
const pub = createClient({ url: process.env.REDIS_URL });
await pub.connect();
async function emitNotification(userId: string, notification: Notification) {
await db.notifications.create({ data: notification });
await pub.publish(`notifications:${userId}`, JSON.stringify(notification));
}
Each instance subscribes to all channels and delivers only to its own clients. Nginx requires disabling buffering:
location /api/notifications/stream {
proxy_pass http://app_backend;
proxy_buffering off;
proxy_cache off;
proxy_read_timeout 3600s;
proxy_set_header Connection '';
chunked_transfer_encoding on;
}
Batching Notifications to Reduce Load
During mass sends (e.g., 100 notifications per second), the client gets a separate SSE event for each. More efficient: batch with a 200 ms debounce:
const pendingByUser = new Map<string, Notification[]>();
function bufferNotification(userId: string, notification: Notification) {
if (!pendingByUser.has(userId)) {
pendingByUser.set(userId, []);
setTimeout(() => flushUser(userId), 200);
}
pendingByUser.get(userId)!.push(notification);
}
function flushUser(userId: string) {
const batch = pendingByUser.get(userId) ?? [];
pendingByUser.delete(userId);
if (batch.length === 1) {
pushToClient(userId, sseEvent('notification', batch[0]));
} else {
pushToClient(userId, sseEvent('notifications:batch', batch));
}
}
This reduces client load (one event instead of many) and server load (fewer write calls). In practice, batching reduces the number of packets by up to 90%, saving network resources and budget.
What's Included in the Work
- Requirements analysis and technology choice (SSE/WebSocket) based on project architecture.
- Flow design: Redis, nginx, JWT-based authentication.
- Server-side implementation (Node.js/Express) and client integration (React, Vue, any framework).
- Load testing up to 10k+ connections with simulated 1000+ notifications per second.
- Documentation (API, configs, deployment guide).
- Post-launch support — 1 month free.
- Team training session on system maintenance.
Estimated Timeline and Cost
SSE notification implementation with Redis: from 2 to 3 days, starting from $500. Adding WebSocket with two-way logic (read receipts, typing): additional 1 day, from $300. Contact us for a free, no-obligation quote.
Channel Security: JWT and Redis
We use JWT authentication: when establishing an SSE connection, the token is verified in middleware. For WebSocket — similarly. Redis Pub/Sub isolates channels by userId. Additionally, we configure rate limiting at the nginx level (50 requests per second per client). This scheme guarantees that only authorized users receive notifications.
Advantages of SSE over Polling
SSE uses HTTP/2, providing multiplexing and lower resource consumption compared to polling. Delivery latency is under 100 ms, and server load is 5–10 times lower. The client API is native EventSource, requiring no additional libraries. For 95% of projects (notifications, alerts, status updates), SSE is the optimal choice. SSE is 10x more efficient than polling in terms of server load.
Here's a case from practice: a client with 100,000 users switched from polling to SSE — database load dropped by 80%, and notification delivery time decreased from 5 seconds to 200 ms. This saved over 40% on server infrastructure budget, equivalent to $2000/month.
| Stage | Duration | Result |
|---|---|---|
| Analysis and technology choice | 0.5 day | Architecture diagram |
| SSE + Redis implementation | 1.5 days | Working /stream endpoint |
| Client integration | 0.5 day | Toast notifications on site |
| Load testing | 0.5 day | 10k+ connections confirmed |
| Documentation | 0.5 day | README, configs, guide |
Contact us for a consultation — we'll explain how to implement notifications without performance degradation. We'll assess your project for free.
Approach described in documentation EventSource and Redis Pub/Sub.







