Learn how Sente models real-time communication in Clojure web apps, including event-based message handling, WebSocket and fallback transport choices, and where real-time channels are stronger or weaker than simpler alternatives.
Sente: A Clojure library for channel-socket style real-time communication, typically using WebSockets when available and fallback transports when needed.
Real-time communication in web systems is not just about “opening a socket.” It is about choosing a message contract, handling reconnects, managing identity, and deciding whether the problem genuinely needs full-duplex communication. Sente is useful because it gives Clojure applications a practical event-oriented model instead of forcing each app to build that lifecycle from scratch.
Before choosing WebSockets, ask:
WebSockets are strongest when updates are frequent, low-latency, and two-way. If the client mostly just needs occasional server updates, lighter transports may be easier to operate.
That question is architectural, not cosmetic. A persistent socket changes connection lifecycle, auth refresh, reconnect behavior, and overload handling. It should earn its complexity.
A real-time channel is still an application boundary. That means the message protocol needs:
If the socket messages are informal, the system becomes hard to evolve even if the transport itself is stable.
It helps to classify messages early:
Once those categories are explicit, it becomes easier to decide which ones need durability, deduplication, or replay semantics.
Sente gives you:
That is more useful than thinking about the system as raw WebSocket frames alone.
It also keeps the real-time layer closer to ordinary application design. Instead of thinking in transport trivia, the team can think in message contracts, identity, subscriptions, and failure ownership.
A real-time protocol becomes easier to evolve when messages look like events with clear intent:
1[:chat/message {:room-id "ops"
2 :text "deploy finished"
3 :sent-at "2026-03-29T12:00:00Z"}]
This is usually better than ad hoc string payloads or implicitly structured maps. The event key gives you a stable dispatch surface.
Stable event names also make logging, metrics, and compatibility review far easier. When every client and server change talks about named events instead of raw payload blobs, the protocol becomes something the team can actually maintain.
Clients disconnect. Tabs sleep. Networks flap. The application has to decide:
Those are application-level policies, not just transport details.
The same applies to subscription lifecycle. If a client reconnects, decide whether it:
The hardest real-time bugs are often not transport bugs. They are lifecycle bugs:
A real-time system should decide explicitly what happens when a client reconnects, resubscribes, or replays an event.
Real-time web design goes bad when:
Another common mistake is using WebSockets where a simpler request-response or SSE model would be enough.
A persistent socket does not mean infinite client throughput. Real-time systems still need a stance on:
Ignoring that turns “real time” into “eventual backlog.”
A healthy real-time system is honest about what it drops, what it retries, and what it guarantees. Very few applications need guaranteed delivery for every message. Many do need explicit degradation rules.
Choose Sente when the app genuinely needs event-oriented, low-latency, bidirectional communication. Keep the message protocol explicit, treat auth and reconnect semantics as first-class design problems, and prefer simpler transports when the interaction does not need a persistent full-duplex channel.