You might not need WebSockets
This feels ill advised and I don't believe that HTTP streaming was designed with this pattern in mind
Perhaps I'm wrong, but I believe HTTP streaming is for chunking large blobs. I worry that if you use this pattern and treat streaming like a pub/sub mechanism, you'll regret it. HTTP intermediaries don't expect this traffic pattern (e.g., NGINX, CloudFlare, etc.). And I suspect every time your WiFi connection drops while the stream is open, the fetch API will raise an error as if the request failed.
However, I agree you probably don't need WebSockets for many of the ways they're used—server-sent events are a simpler solution for many situations where people reach for WebSockets... It's a shame SSEs never received the same fanfare.
>> If it wasn’t, we couldn’t stream video without loading the entire file first
I don't believe this is correct. To my knowledge, video stream requests chunks by range and is largely client controlled. It isn't a single, long lived http connection.
> Bonus: Making it easy with eventkit
Why not just use SSE? https://developer.mozilla.org/en-US/docs/Web/API/Server-sent...
The problem with HTTP2 is that the server-push aspect was tacked on top of an existing protocol as an afterthought. Also, because HTTP is a resource transfer protocol, it adds a whole bunch of overheads like request and response headings which aren't always necessary but add to processing time. The primary purpose of HTTP2 was to allow servers to preemptively push files/resources to clients to avoid round-trip latency; to reduce the reliance on script bundles.
WebSockets is a simpler protocol built from the ground up for bidirectional communication. It provides a lot more control over the flow of data as everything passes over a single connection which has a single lifecycle. It makes it a lot easier to manage state and to recover cleanly from a lost connection when you only have one logical connection. It makes it easier to process messages in a specific order and to do serial processing of messages. Having just one connection also greatly simplifies things in terms of authentication and access control.
I considered the possibility of switching the transport to HTTP2 for https://socketcluster.io/ years ago, but it's a fundamentally more complex protocol which adds unnecessary overheads and introduces new security challenges so it wasn't worth it.
Me: For this POC you've given me, I will do an old-fashioned HTTP form submit, no need for anything else.
Architect: But it must have websockets!
Me: Literally nothing in this POC needs XHR, much less websockets. It's a sequential buy flow with nothing else going on.
Architect: But it has to have websockets, I put them on the slide!
(Ok he didn't say the part about putting it on the slide, but it was pretty obvious that's what happened. Ultimately I caved of course and gave him completely unnecessary websockets.)
Having deployed WebSockets into production, I came to regret that over the next years. Be it ngnix terminating connections after 4/8 hours, browsers not reconnecting after sleep and other issues, I am of the opinion that WebSockets and other forms of long standing connections should be avoided if possible.
People interested in HTTP streaming should check out Braid-HTTP: https://braid.org. It adds a standard set of semantics that elegantly extend HTTP with event streaming into a robust state synchronization protocol.
Oof, what a headline to be top of hn the day after you implement websockets into a project.
I just realized that modern web applications are a group form of procrastination. Procrastination is a complex thing. But essentially, it's putting something off because of some perceived pain, even though the thing may be important or even inevitable, and eventually the procrastination leads to negative outcomes.
Web applications were created because people were averse to creating native applications, for fear of the pain involved with creating and distributing native applications. They were so averse to this perceived pain that they've done incredibly complex, even bizarre things, just so they don't have to leave the web browser. WebSockets are one of those things: taking a stateless client-server protocol (HTTP) and literally forcing it to turn into an entirely new protocol (WebSockets) just so people could continue to do things in a web browser that would have been easy in a native application (bidirectional stateful sockets, aka a tcp connection).
I suppose this is a normal human thing. Like how we created cars to essentially have a horseless buggy. Then we created paved roads to make that work easier. Then we built cities around paved roads to keep using the cars. Then we built air-scrubbers into the cars and changed the fuel formula when we realized we were poisoning everyone. Then we built electric cars (again!) to try to keep using the cars without all the internal combustion issues. Then we built self-driving cars because it would be easier than expanding regional or national public transportation.
We keep doing the easy thing, to avoid the thing we know we should be doing. And avoiding it just becomes a bigger pain in the ass.
I don't know why the topic of websockets is so weird. 80% of the industry seem to have this skewed idealised perception of websockets as the next frontier of their web development career and cannot wait to use them for anything remotely connected to streaming/ realtime use cases. When pointing out the nuances and that websockets should actually be avoided for anything where they are not absolutely needed without alternatives people get defensive and offended, killing every healthy discussion about realistic tradeoffs for a solution. Websockets have a huge number of downsides especially losing many of the niceties and simplicity of http tooling, reasonability, knowledge and operations of http. As many here pointed, the goto solution for streaming server changes is h2 / h3 and SSE. Everything that can be accomplished in the other direction with batching and landing in the ballpark of max 0.5req/s per client does NOT need websockets.
Discord and Slack do the article's suggestion of using web sockets for the receiving side only (mostly) and having you switch to http on the calling side. It works pretty well, you have to keep two sets of books but the web socket side is almost always for events that you, the client, should be responding to so it works somewhat like a reverse http but that works with your firewall. It also allows Discord to implement trivial, from the client's perspective, sharding. It really is clever as hell, scaling up a bot/integration is as easy as just turning on sharding and launching multiple instances of your bot. It handles spreading events across them.
The author throws away their own suggestion but it clearly works, works well, and scales well into "supermassive" size. They don't even mention the real downside to web sockets which is that they're stateful and necessarily tied to a particular server which makes them not mesh at all with your stateless share-nothing http servers.
I usually start with the long polling/SSE and migrate to WebSockets when needed. It is cheap and reliable with almost no performance overhead when compared to WebSockets.
You don't need websockets SSE works fine for realtime collaborative apps.
Websockets sound great on paper. But, operationally they are a nightmare. I have had the misfortune of having to use them at scale (the author of Datastar had a similar experience). To list some of the challenges:
- firewalls and proxies, blocked ports
- unlimited connections non multiplexed (so bugs lead to ddos)
- load balancing nightmare
- no compression.
- no automatic handling of disconnect/reconnect.
- no cross site hijacking protection
- Worse tooling (you can inspect SSE in the browser).
- Nukes mobile battery because it hammers the duplex antenna.
You can fix some of these problems with websockets, but these fixes mostly boil down to sending more data... to send more data... to get you back to your own implementation of HTTP.
SSE on the other hand, by virtue of being regular HTTP, work out of the box with, headers, multiplexing, compression, disconnect/reconnect handling, h2/h3, etc.
If SSE is not performant enough for you then you should probably be rolling your own protocol on UDP rather than using websockets. Or wait until WebTransport is supported in Safari (any day now ).
Here's the article with a real time multiplayer Game of Life that's using SSE and compression for multiplayer.
https://example.andersmurphy.com
It's doing a lot of other dumb stuff explained a bit more here, but the point is you really really don't need websockets (and operationally you really don't want them):
https://andersmurphy.com/2025/04/07/clojure-realtime-collabo...
I personally view WebSockets as a nicer TCP that has all the messaging functionality you end up building anyway than as an alternative to HTTP.
You can also use long polling, which keeps alive a connection so the server can respond immediately when there’s new data. For example:
Server
const LONG_POLL_SERVER_TIMEOUT = 8_000
function longPollHandler(req, response) {
// e.g. client can be out of sync if the browser tab was hidden while a new event was triggered
const clientIsOutOfSync = parseInt(req.headers.last_received_event, 10) !== myEvents.count
if (clientIsOutOfSync) {
sendJSON(response, myEvents.count)
return
}
function onMyEvent() {
myEvents.unsubscribe(onMyEvent)
sendJSON(response, myEvents.count)
}
response.setTimeout(LONG_POLL_SERVER_TIMEOUT, onMyEvent)
req.on('error', () => {
myEvents.unsubscribe(onMyEvent)
response.destroy()
})
myEvents.subscribe(onMyEvent)
}
Client (polls when tab is visible) pollMyEvents()
document.addEventListener('visibilitychange', () => {
if (!document.hidden)
pollMyEvents()
})
pollMyEvents.isPolling = false
pollMyEvents.oldCount = 0
async function pollMyEvents() {
if (pollMyEvents.isPolling || document.hidden)
return
try {
pollMyEvents.isPolling = true
const response = await fetch('/api/my-events', {
signal: AbortSignal.timeout(LONG_POLL_SERVER_TIMEOUT + 1000),
headers: { last_received_event: pollMyEvents.oldCount }
})
if (response.ok) {
const nMyEvents = await response.json()
if (pollMyEvents.oldCount !== nMyEvents) { // because it could be < or >
pollMyEvents.oldCount = nMyEvents
setUIState('eventsCount', nMyEvents)
}
pollMyEvents.isPolling = false
pollMyEvents()
}
else
throw response.status
}
catch (_) {
pollMyEvents.isPolling = false
setTimeout(pollMyEvents, 5000)
}
}
Working example at Mockaton:
https://github.com/ericfortis/mockaton/blob/6b7f8eb5fe9d3baf...Websocket is not ment to be sent as streams (TCP equvalent), but as datagrams aka packets (UDP equivalents). Correct me if I am wrong, but websockets api in Javascript libraray for browsers is pretty poor and does not have ability to handle backpressure and I am sure it can not handle all possible errors (assertions about delivery). If you want to use websockets as TCP streams including seasson handling, great care should be taken as this is not natively availabe in neither rfc6455 and in browser.
> We can’t reliably say “the next message” received on the stream is the result of the previous command since the server could have sent any number of messages in between now and then.
Doing so is a protocol decision though, isn't it?
If the protocol specifies that the server either clearly identifies responses as such, or only ever sends responses, and further doesn't send responses out of order, I don't see any difference to pipelined HTTP: The client just has to count, nothing more. (Then again, if that's the use case, long-lived HTTP connections would do the trick just as well.)
One thing I couldn’t get working with websockets is how do you keep websocket connections active during code deployments without disconnecting current connected clients?
Sounds very tricky to me to get right even at scale.
I think an article like that would benefit from focusing more on protocols, rather than particular APIs to work with those: referencing the specifications and providing examples of messages. I am pretty sure that the article is about chunked transfer encoding [1], but it was not mentioned anywhere. Though possibly it tries to cover newer HTTP versions as well, abstracting from the exact mechanisms. In which case "JS API" in the title would clarify it.
As for the tendency described, this seems to be an instance of the law of the instrument [2], combined with some instruments being more trendy than others. Which comes up all the time, but raising awareness of more tools should indeed be useful.
Why not use a library like socket.io? It handles the socket lifecycle, reconnection etc.
What does this solve? Genuine question. You still have to manage connectivity, and synchronization. Also not so sure that stream reading will necessarily be quantized chunks of your updates sent from the server.
We are looking into adopting bidirectional streams, and have identified gRPC as a likely ideal candidate. It provides a layer on top of the blobs (partial responses) sent by either side, and takes over the required chunking and dechunking. And it doesn’t have the authentication issues that Websockets have. I‘d appreciate any insights on this matter.
The world needs more of these "you might not need" articles.
Too many technology fads make things needlessly complicated, and complexity makes systems unreliable.
You might not need Kubernetes
You might not need The Cloud
You might not need more than SQLite
...and so on.
WebSockets are full duplex, so both sides of a connection are equally transmitting sides. There first section fails to understands this and then builds some insane concern for state on top of this faulty notion. WebSockets don't care about your UI framework just like your car doesn't care what time you want to eat dinner.
> You have to manage the socket lifecycle
You have to do the very same thing with HTTP keep-alive or use a separate socket for each and every HTTP request, which is much slower. Fortunately the browser makes this stupid simple in regards to WebSockets with only a few well named events.
> When a new WebSocket connection is initiated, your server has to handle the HTTP “upgrade” request handshake.
If the author cannot split a tiny string on CRLF sequences they likely shouldn't be programming and absolutely shouldn't be writing an article about transmission. There is only 1 line of data you really need from that handshake request: Sec-WebSocket-Key.
Despite the upgrade header in the handshake the handshake is not actually HTTP. According to RFC6455 it is a tiny bit of text conforming to the syntax of RFC2616, which is basically just: lines separated by CRLF, terminated by two CRLFs, and headers separated from values with a colon. Really its just RFC822 according to RFC2616.
This is not challenging.
I take it this article is written by a JavaScript framework junkie that cannot program, because there is so much in the article that is just wrong.
EDITED: because people get sad.
We have multiple mission-critical, industrial-grade WebSocket monitoring applications that have been running rock-solid for the last eight years without any hiccups in manufacturing environments. It seems like you're taking an easy-to-maintain codebase and turning it into a complex monstrosity.
I wrote a subsystem the other day that used websockets for a server to distribute video conversion tasks.
After futzing with silly things like file transfers and communication protocols I chucked it out and rewrote it so the client does HTTP long polling of the server and uploads its renders via hTTP POST.
So much easier.
I liked vert.x's strategy of seamlessly downgrading the form of connection based on what is available.
Web sockets are very low level, so first you want to use a library in order to work seamlessly with all 100 different implementations of websockets, but then you need to make your own protocol ontop of it. And implement ping and reconnect.
I think at this point in my career my goal is to continue to never, ever, work on a public-facing website. 20 years into this foray of a career and I’ve avoided it so far.
If you use a proper framework, you don't have to manage the socket lifecycle and it doesn't complicate your server.
> It makes your server code more complex.
And, that is why we have frameworks to at least in the case of Web Sockets, make things as easy as regular old REST.
With HTTP streaming the browser shows that it's still loading data. Is there some mitigation for it after the initial loading?
Reads like a series of strawman arguments if you replace "WebSockets" with socket.io.
- "messages aren’t transactional": You can process request and return a value to sender in socket.io application layer. Is that transactional enough?
- "If you’re sending messages that don’t necessarily need to be acknowledged (like a heartbeat or keyboard inputs), then Websockets make a great fit". But socket.io has acknowledgements.
- "When a new WebSocket connection is initiated, your server has to handle the HTTP “upgrade” request handshake.". You can bypass handshake and go straight to WS even in Websockets, and if you don't socket.io handles upgrade for you pretty nicely so you not parsing HTTP header ..
Maybe I'm naive? But I thought its if you need stateful, use websockets. Else, use short/long poll or SSE.
Why do you need to implement your own web socket server? Why not use AWS appsync events?
My HTTP streaming has slowed to more of a trickle the last couple of years.
You probably do. Reliable SSE is a complete nightmare.
I don't need them but I do like them.
I see the shiny thing and I'm not delusional enough to think I need it.
Setinterval.
just use meteor.js https://www.meteor.com/ ?
WebSockets can't go through proxies.
The article forgot to mention websockets add state to the server! Load balancing will require sticking sessions. At scale this tends to mean separating websocket servers completely from http servers.
It's a minor point in the article, but sending a RequestID to the server so that you get request/response cycles isn't weird nor beyond the pale.
It's pretty much always worth it to have an API like `send(message).then(res => ...)` in a serious app.
But I agree. The upgrade request is confusing, and it's annoying how your websocket server is this embedded thing running inside your http server that never integrates cleanly.
Like instead of just reusing your middleware that reads headers['authorization'] from the websocket request, you access this weird `connectionParams` object that you pretend are request headers, heh.
But the idiosyncrasies aren't that big of a deal (ok, I've just gotten used to them). And the websocket browser API is nicer to work with than, say, EventSource.