WebSockets
tl;dr
HTTP closes after every answer, so the server can never speak first. A WebSocket upgrades once and stays open both ways — two nginx headers, one library, and about fifteen lines.
That's the whole idea. HTTP is one-way and it doesn't persist: the response arrives, the connection closes, and the server has no way to reach you again until you ask. A WebSocket is a persistent, bidirectional connection.
Two lines in nginx
nginx has to know an upgrade request is coming through:
location / {
proxy_pass http://127.0.0.1:3000;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
sudo nginx -t && sudo service nginx restart
Two headers. That's the answer to "why nginx instead of Node" from earlier — doing this yourself would be considerably less pleasant.
The server
Express from here, because the hand-rolled http server gets tedious once
things get real. It's not the newest option — Fastify has momentum — but it has
fifteen years of answers written about it, and being able to find the answer
is part of the job.
npm i express ws
const express = require("express")
const { createServer } = require("http")
const { WebSocketServer } = require("ws")
const app = express()
const server = createServer()
app.get("/", (req, res) => res.sendFile(__dirname + "/index.html"))
server.on("request", app)
const wss = new WebSocketServer({ server })
wss.on("connection", (ws) => {
const numClients = wss.clients.size
console.log("clients connected", numClients)
wss.broadcast(`Current visitors: ${numClients}`)
})
server.listen(3000, () => console.log("server started on port 3000"))
ws exists because you do not want to implement the WebSocket protocol by
hand. wss.clients.size is how many are connected, and broadcast sends to
all of them without you iterating.
The client
The only real subtlety is the protocol:
<script>
const proto = window.location.protocol === "https:" ? "wss" : "ws"
const ws = new WebSocket(`${proto}://${window.location.host}`)
ws.onmessage = (event) => console.log(event.data)
</script>
ws over HTTP, wss over HTTPS — the secure variant, same relationship as
http and https, and it matters because you'll be on 443 shortly. WebSocket is
built into the browser; there's nothing to install.
onmessage is optional in principle — a WebSocket could just trigger a re-render
and never surface data to you. Logging it is how you confirm the thing works.