If you ask ten developers how to build a real-time application, approximately eleven of them will tell you to use WebSockets. That’s because WebSockets are cool. They’re bidirectional. They’re fast. They’re efficient. They’re designed specifically for real-time communication.

They’re also the answer to a surprising number of problems nobody actually has.

Don’t misunderstand me. WebSockets are fantastic technology. If your application genuinely needs full duplex communication between the browser and the server, they’re exactly the right tool. Mine didn’t. Like a lot of software projects, I almost reached for WebSockets simply because the word “chat” appeared somewhere in the requirements. Fortunately, I stopped myself before accidentally turning a straightforward feature into a weekend project… followed by three months of debugging.

Let’s Talk About the Actual Requirements

Before picking a technology, write down what your application actually needs to do. In my case, the browser needed to:

  • Receive new chat messages.
  • Receive status updates.
  • Receive notifications that someone had joined or left the conversation.
  • Automatically reconnect if the connection dropped.

That’s about it. Notice what’s missing? The browser never needed to push data over a persistent connection. Whenever the user sends a message, they’re already making an ordinary HTTP POST.

Browser
    │
    │ POST /sendMessage
    ▼
ColdFusion

The browser already knows how to send data. Every browser on Earth has been doing it since approximately the Jurassic period. And by Jurassic period I mean <frameset>s and “Best Viewed with IE” buttons.

The only thing the browser couldn’t do was receive unsolicited updates from the server. That’s a much smaller problem.

One-Way Is Enough

Most support chats are surprisingly asymmetric. The browser sends a message. The request completes. Some time later the server has something new to say.

That’s not a conversation. It’s a series of independent HTTP requests followed by occasional notifications. Server-Sent Events were designed almost specifically for that problem.

Browser
    │
    │ EventSource
    ▼
ColdFusion

Server
    │
    │ event: message
    ▼
Browser

One persistent HTTP response. The server writes events whenever something interesting happens. The browser quietly listens. Everybody goes home happy.

The Browser Already Knows How

One of my favorite features of Server-Sent Events is how little JavaScript is required.

const events = new EventSource("/chat/events");

events.onmessage = (event) => {
	const message = JSON.parse(event.data);
	addMessage(message);
};

That’s basically it. No client libraries. No connection negotiation. No socket management. No “did the connection close?” No “why is Brave doing something different than Firefox?” The browser handles reconnection automatically.

Honestly, the hardest part is remembering the API exists because everyone is too busy talking about WebSockets.

ColdFusion (and I) Like Boring

ColdFusion has been serving HTTP responses for decades. Server-Sent Events are… just an HTTP response. That means no protocol upgrades. No special libraries. No dedicated WebSocket infrastructure. No reverse proxy gymnastics. No wondering whether your load balancer supports that clever GitHub project with 17 stars and one maintainer who disappeared in 2021.

As far as ColdFusion is concerned, you’re just writing text to an HTTP response that happens to stay open. That’s refreshingly boring. Boring is good. Boring pays my mortgage.

Automatic Reconnection Is Surprisingly Nice

Networks fail. Wi-Fi drops. Someone closes their laptop. A corporate firewall decides today is the day it hates happiness.

Server-Sent Events simply reconnect.

The browser quietly attempts another connection. You can even include event IDs so the browser tells the server the last event it successfully received.

Last-Event-ID: 41872

The server can then replay anything the browser missed. No complicated synchronization. No elaborate recovery process. No support ticket titled:“Chat stopped working after I unplugged my laptop while boarding a flight.”

Although you’ll still get that ticket. It’ll just turn out to be something completely unrelated.

What About Scaling?

This is usually the first objection. “But every client has a long-running connection!”

Yes.

So does every WebSocket. An open connection is an open connection. If your application needs to support fifty thousand concurrent users, you’ll need to think carefully about your architecture regardless of the transport protocol.

For the overwhelming majority of business applications, Server-Sent Events scale just fine. If you eventually outgrow them, congratulations. Your business has much nicer problems than choosing between EventSource and WebSockets.

But WebSockets Are Faster!

Probably. The question is, Can your users tell?

If support replies arrive in 40 milliseconds instead of 80 milliseconds, nobody notices. If replies arrive in two seconds because someone wrote an overly complicated messaging layer, everybody notices. Performance is only valuable when users can perceive it. Nobody has ever opened a support ticket because their chat reply arrived 37 milliseconds too slowly. Plenty have opened one because it never arrived at all.

Engineering effort is always expensive. Optimize the thing people actually experience.

The Downsides

Server-Sent Events aren’t magic. They’re one-way. If your application genuinely needs continuous bidirectional communication, WebSockets are still the better choice. Some examples include:

  • Multiplayer games.
  • Collaborative editors.
  • Financial trading dashboards.
  • Remote desktop software.
  • Applications where both sides constantly exchange data.

Those are real WebSocket problems. A customer support chat usually isn’t. Sometimes we hear the word “chat” and immediately picture Discord, Microsoft Teams, Slack, Zoom, and a small data center worth of infrastructure. Most support chats are considerably less exciting.

They’re closer to email with better timing.

The Biggest Lesson

The biggest lesson from this project wasn’t how Server-Sent Events work. It was remembering to solve today’s problem instead of tomorrow’s imaginary one. Developers love building for infinite scale, infinite flexibility, infinite extensibility, and infinite configuration.

Meanwhile, the product owner is just trying to answer a customer’s question before they abandon their shopping cart. Sometimes the simplest architecture really is the best architecture. Not because it’s elegant. Because it’s understandable. Six months from now, when you’ve forgotten how it works, you’ll thank Past You for choosing the solution that fits on a whiteboard instead of requiring a conference talk. Current David is always grateful when Past David leaves him something he can understand.

Past You is occasionally smarter than Current You. Not often, but occasionally.

Next Time

Now that we’ve settled on Server-Sent Events, it’s time to actually build the plumbing.

In the next article we’ll create the conversation model that ties browsers, database records, and Slack threads together into a single coherent system. We’ll look at why every conversation needs a durable identity, how to map that identity to Slack threads, and why spending an extra hour designing your data model can save you several weekends of regret later.

Because nothing says “I should have spent another hour on the schema” quite like discovering your database can’t answer the question, “Which customer is this message actually for?”

All Comments
Sort by:  Most Recent