27.10. Real-Time Applications
Table of Contents
Understanding Real-Time Applications
Real-time applications are systems where users see updates almost immediately after something happens, without needing to refresh the page or send a new request manually.
In a classic request-response model, the client asks for data, the server answers, and that is it. In a real-time application, the server can also push new data to the client as soon as it is available.
Common examples:
- Chat applications (Slack, WhatsApp Web)
- Live dashboards and analytics (stock prices, server metrics)
- Collaborative tools (Google Docs, Figma)
- Online games and multiplayer boards
- Notifications and activity feeds
- Live location tracking (delivery apps, maps)
Real-time does not mean “literally instant.” It normally means “fast enough that users perceive it as immediate,” usually within a few hundred milliseconds.
Key idea: A real-time app is one where the server can deliver new data to the client as soon as it exists, without the client asking for it each time.
Patterns for Real-Time Communication
There are several ways to build real-time features on top of HTTP and related technologies.
1. Polling
Polling is the simplest strategy. The client asks the server for new data every few seconds.
Example:
- Every 2 seconds, the browser sends:
GET /notifications - The server returns any new notifications, or an empty list
Pseudocode in JavaScript:
async function pollNotifications() {
const response = await fetch('/notifications');
const data = await response.json();
renderNotifications(data);
setTimeout(pollNotifications, 2000); // repeat after 2 seconds
}
pollNotifications();Pros:
- Very easy to implement
- Works with any backend that supports HTTP
Cons:
- More requests than necessary
- There can be up to the polling interval as a delay
- Wastes resources when nothing changes
Polling is acceptable when:
- Updates are not very frequent
- A delay of a few seconds is fine
- You want a simple solution with no special protocols
2. Long Polling
Long polling keeps the same idea as polling, but reduces needless requests.
Flow:
- Client sends
GET /events. - Server:
- If there is an event, returns it immediately.
- If there is no event yet, keeps the request open for some time (for example 30 seconds).
- If a new event appears while the request is open, the server returns it at once.
- If no event appears in 30 seconds, the server returns an empty response or timeout.
- Client immediately sends another
GET /events.
This way, during quiet periods, there is at most one open request instead of many idle requests.
Pros:
- Works over normal HTTP
- Near real-time updates
- Supported even by very old browsers
Cons:
- Still uses many requests over time
- Harder to scale, many hanging connections
- More complex server logic than simple polling
3. Server-Sent Events (SSE)
SSE uses a single long-lived HTTP connection from client to server, where the server streams events as text.
Characteristics:
- Client connects with
GETto an endpoint like/events. - Server responds with headers:
Content-Type: text/event-stream
Cache-Control: no-cache
Connection: keep-alive- Then writes events in a special text format:
event: message
data: {"text": "Hello"}
event: count
data: 42- The client receives each event as it arrives.
Client example (browser):
const source = new EventSource('/events');
source.onmessage = (event) => {
const data = JSON.parse(event.data);
console.log('New message:', data);
};
source.addEventListener('count', (event) => {
console.log('Count event', event.data);
});Pros:
- Very simple in the browser (built-in
EventSource) - HTTP based, easy to integrate
- Works well for server to client streams (one way)
Cons:
- Browser to server is still normal HTTP requests, not a live channel
- Only supports text streaming, not binary
- Some environments do not support SSE well (for example some proxies, older browsers)
SSE is great for:
- Live dashboards and metrics
- Notifications
- Streaming logs
- Market or sports scores where the server broadcasts data
4. WebSockets
WebSockets provide a bidirectional connection between client and server.
- Connection starts from an HTTP request with an upgrade header.
- If the server accepts, both sides switch to the WebSocket protocol.
- After that, both client and server can send messages at any time.
This enables:
- Two-way chat
- Multiplayer games
- Collaborative editing
- Real-time control panels
The difference from SSE:
| Feature | SSE | WebSockets |
|---|---|---|
| Direction | Server to client only | Both client and server |
| Transport | HTTP text stream | WebSocket protocol on top of TCP |
| Browser API | EventSource | WebSocket |
| Complexity | Simple | More complex |
| Good for | Notifications, feeds | Chat, games, collaborative apps |
Real-Time vs REST APIs
REST APIs:
- Stateless HTTP requests.
- Each request is independent.
- Client decides when to ask for data.
- Easy to cache and scale.
Real-time channels (WebSockets or SSE):
- Long-lived connections.
- Server can push updates at any time.
- Both sides can keep context in memory during the connection.
- Harder to cache and distribute, but more interactive.
In real applications you often use both:
- REST for “normal” operations: sign up, login, CRUD for data.
- Real-time channels for live updates: “someone changed this document,” “new chat messages,” “new orders arrived.”
Example flow in a chat app:
- User sends a message:
POST /messages(REST). - Server saves it to the database.
- Server publishes “new message” event to a message broker.
- A WebSocket server receives the event.
- WebSocket server sends the new message to all connected clients in that chat room.
This keeps your API simple and still gives real-time UX.
Core Design Challenges in Real-Time Apps
Real-time apps bring new challenges that are less visible in plain REST APIs.
Connection Management
You must handle:
- Many open connections per server.
- Detect when clients disconnect.
- Reconnect clients after network failures.
Typical patterns:
- Use ping or heartbeat messages every few seconds.
- On missed heartbeats, close the connection.
- Client retries with exponential backoff.
Example heartbeat flow:
- Every 20 seconds, server sends:
{ "type": "ping" }. - Client responds with
{ "type": "pong" }. - If server does not receive
pongafter some time, it closes the connection.
Identifying Users and Sessions
You still need authentication and authorization, but now the connection lives a long time.
Common approaches:
- Client includes an access token in the connection request.
- Server validates token and attaches user id to the connection context.
- For each message, you can know which user sent it.
Example WebSocket URL with token in query (simplified):
wss://api.example.com/ws/chat?token=abc123
Better options are usually:
- Send token in a header during the upgrade or handshake.
- Or send an authenticate message as the first message.
Never trust data inside the message alone. Validate tokens during connection and, for long-lived connections, consider revalidating if tokens expire.
Rooms, Topics, and Subscriptions
Real-time apps often have the idea of “who should receive which messages.”
Common patterns:
- Rooms or channels in chat:
room:123,room:general - Topics in pub/sub systems:
orders.created,notifications.user.42 - Subscriptions: client subscribes to a subset of data
Examples:
- Client subscribes to live updates for
order_id = 10. - Admin dashboard subscribes to all new orders.
- User subscribes only to their notifications.
To support this, your server keeps an in-memory mapping:
- Room name → list of connections
- Topic name → list of connections
- User id → list of connections
When a new event arrives, you look up the correct group and send messages only to those connections.
Ordering and Idempotency
Messages may arrive:
- Out of order
- Duplicated
- Missing (for example if connection drops)
To handle this safely:
- Each message should have a unique id or sequence number.
- The client can ignore duplicates.
- The client can detect missing sequence numbers and request history from a REST API.
Example:
- Events for document edits have
versionnumbers: 1, 2, 3, ... - Client sees version 1, then receives version 3.
- It knows version 2 is missing, so it can request a full document from the REST API.
Scaling Real-Time Connections
One server process can usually handle many open connections, especially with asynchronous frameworks. But for large systems you need multiple servers.
Problem:
- User A is connected to Server 1.
- User B is connected to Server 2.
- User A sends a message to B.
- How does Server 1 tell Server 2?
Solution:
- Use a message broker or pub/sub system such as Redis, Kafka, or a managed service.
- Each server subscribes to relevant channels (for example
user.42.notifications). - When Server 1 receives a message, it publishes to the broker.
- The broker sends it to Server 2, which pushes to user B.
This is a classic pattern for real-time systems.
Example Architectures for Real-Time Features
Example: Chat Feature
Let us design a simple chat backend at a high level.
Components:
- REST API (for example FastAPI):
POST /roomsto create a room.GET /rooms/{id}/messagesto load the last 50 messages.POST /rooms/{id}/messagesto send a message.- WebSocket server endpoint:
/ws/rooms/{id}for live updates. - Database, for example PostgreSQL, for message storage.
- Redis pub/sub for distribution.
Flow for sending a message:
- Client sends
POST /rooms/5/messageswith JSON{ "text": "Hello" }. - API:
- Checks permissions.
- Saves message to
messagestable withroom_id = 5. - Publishes a message to Redis channel
room.5with message data. - Each WebSocket server is subscribed to
room.5in Redis. - WebSocket server receives the event and sends it to all clients connected to
/ws/rooms/5.
Flow for receiving messages:
- User connects:
ws://.../ws/rooms/5. - Server authenticates and registers connection in room 5.
- When Redis publishes
room.5events, server forwards them to all room 5 connections.
This setup allows many servers to share the same chat rooms.
Example: Live Metrics Dashboard with SSE
Goal:
- Show CPU usage of servers updated every second.
Components:
- Metrics collector that gathers CPU data and stores it in memory or Redis.
- SSE endpoint
/metrics/stream.
Flow:
- Browser uses
new EventSource('/metrics/stream'). - Server sets
Content-Type: text/event-streamand flushes data every second:
event: cpu
data: {"server":"api-1","usage":57}
event: cpu
data: {"server":"api-2","usage":44}- Client JavaScript updates dashboard graphs on each event.
This is one-way streaming from server to client, using a simple HTTP connection.
Handling Failures and Reconnection
Real-time connections are more fragile than simple HTTP requests. Networks drop, Wi-Fi jumps between routers, laptops sleep and wake.
You need strategies for:
- Reconnecting
- Resynchronizing state
- Avoiding message loss or duplication
Reconnection Strategies
Client logic should:
- Try to reconnect automatically if the connection is lost.
- Use increasing delays between attempts to avoid flooding the server.
Example simple strategy:
- 1st retry after 1 second
- 2nd retry after 2 seconds
- 3rd retry after 4 seconds
- Up to a maximum delay, for example 30 seconds
This is called exponential backoff.
Resynchronizing State
When reconnection happens, client may have missed events.
Patterns:
- Use a “last seen message id”:
- Client stores the id of the last message.
- After reconnect, client calls a REST endpoint:
GET /rooms/5/messages?after_id=123.- Server returns all messages after 123.
- Or use version numbers:
- For each resource, keep a
versionnumber. - On reconnect, client asks for the latest version and resets local state.
The key idea is that real-time channels are best-effort, and the source of truth is usually a database behind a REST API.
Choosing the Right Technology
Use this table as a guide:
| Requirement | Technique to consider |
|---|---|
| Rare updates, delay of seconds is fine | Simple polling |
| Frequent updates, still simple setup | Long polling |
| Server-only push (notifications, dashboards) | SSE |
| Two-way communication (chat, games) | WebSockets |
| Many servers, high scale | WebSockets + broker (Redis, Kafka) |
| Streaming text or logs | SSE or WebSockets |
Often you will mix them:
- Use REST for core CRUD.
- Use SSE for broadcasting metrics.
- Use WebSockets for interactive features like chat.
Data Formats and Message Design
In real-time messages you still need:
- Clear structure
- Versioning
- Validation
Common practice:
- Use JSON for WebSocket and SSE messages.
- Include a
typefield and apayloadfield.
Example WebSocket message:
{
"type": "chat.message",
"payload": {
"id": 123,
"room_id": 5,
"text": "Hello",
"author": "alice",
"created_at": "2026-08-28T10:00:00Z"
}
}On the server:
- Parse JSON.
- Use a dispatcher that calls different handlers based on
type.
On the client:
- Switch on
typeto update the correct part of the UI.
Versioning:
- Add a
versionfield for the protocol:
{
"version": 1,
"type": "chat.message",
"payload": { ... }
}When you change the message format later, you can handle old and new versions in parallel.
Security Considerations in Real-Time Apps
Security rules from standard APIs still apply, but there are extra considerations.
Important real-time security rules:
- Always authenticate connections before accepting messages.
- Never trust messages from clients without validation.
- Treat every message like an API request with its own authorization checks.
Key points:
- Use the same authentication tokens used by your HTTP APIs.
- Validate the token at connection time.
- Authorize each action:
- In chat, check that user is in the room before accepting message.
- In live dashboard, check that user has permission to view metrics.
- Limit message rates per connection to avoid abuse.
- Consider maximum message sizes to prevent memory issues.
Putting It All Together
Real-time applications extend your backend beyond simple request-response. They let you:
- Push updates as they happen.
- Keep users in sync with the latest data.
- Build more interactive and collaborative tools.
The core patterns are:
- Choose the right technique (polling, long polling, SSE, WebSockets).
- Combine real-time connections with REST APIs and databases.
- Use message brokers for scale and distribution.
- Handle disconnections, reconnections, and missing messages.
- Use authentication, authorization, and validation on every live connection.
As a backend developer, you do not need to implement everything from scratch. Frameworks, libraries, and managed services can handle much of the low-level work. Your focus is on designing clear protocols, secure flows, and robust data handling so that your real-time features stay correct and reliable as your system grows.
Views: 9
KAHIBARO