Wil je een chatapplicatie bouwen, live notificaties tonen of een dashboard dat direct updatet zodra er nieuwe data binnenkomt? Dan kom je al snel uit bij WebSockets. In deze gids leer je hoe WebSockets en realtime apps in Node.js werken, wanneer je ze wel of niet moet gebruiken en hoe je ze op een schaalbare manier opzet.
We kijken naar de basis van het protocol, de belangrijkste libraries (ws en Socket.IO), authenticatie, schaalbaarheid met Redis en veelvoorkomende valkuilen. Aan het einde heb je een duidelijk beeld van wat er nodig is om een productierijpe realtime applicatie te bouwen.
Wat is een WebSocket?
Een WebSocket is een protocol dat bovenop TCP draait en een persistente, full-duplex verbinding biedt tussen client en server. Na een eerste HTTP-handshake (een "upgrade request") blijft de verbinding open. Zowel de browser als de server kan op elk moment een bericht sturen.
Dit is fundamenteel anders dan het standaard HTTP request/response model dat we behandelden in HTTP servers en routing in Node.js uitgelegd. Bij HTTP initieert de client elk verzoek en wacht op een antwoord. Bij WebSockets hebben beide kanten gelijkwaardige spreekrechten.
De specificatie staat beschreven in RFC 6455 en wordt ondersteund door alle moderne browsers via de ingebouwde WebSocket API.
Hoe werkt de handshake?
Een WebSocket-verbinding begint als een gewone HTTP-request met speciale headers:
GET /ws HTTP/1.1
Host: example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Sec-WebSocket-Version: 13
De server antwoordt met status 101 Switching Protocols en vanaf dat moment gaat de verbinding over op het WebSocket-frameprotocol. Dat frameprotocol is veel lichter dan HTTP, waardoor je met minimale overhead duizenden berichten per seconde kunt versturen.
Wanneer gebruik je WebSockets?
WebSockets zijn geen vervanging voor HTTP. Ze zijn een specifiek gereedschap voor specifieke problemen. Gebruik ze als:
- Je lage latency nodig hebt (chat, multiplayer games, trading)
- De server data moet pushen zonder dat de client erom vraagt (notificaties, live feeds)
- Je bidirectionele communicatie nodig hebt (collaborative editing, whiteboards)
- Je veel kleine, frequente berichten verstuurt
Voor een standaard CRUD API blijft REST of GraphQL een betere keuze. Zie ook REST API design best practices voor Node.js.
Alternatieven overwegen
Voor simpele server-naar-client updates kan Server-Sent Events (SSE) genoeg zijn. SSE is unidirectioneel, werkt over HTTP en is ideaal voor dingen als live dashboards of notificatiestreams. Voor complexe bidirectionele flows pak je WebSockets.
WebSockets in Node.js: de ws-library
De populairste lichte WebSocket-library voor Node.js is ws. Hij is snel, compact en vormt de basis onder veel andere libraries zoals Socket.IO.
Minimale server
import { WebSocketServer } from 'ws';
const wss = new WebSocketServer({ port: 8080 });
wss.on('connection', (socket) => {
console.log('Nieuwe client verbonden');
socket.on('message', (data) => {
const message = data.toString();
console.log('Ontvangen:', message);
// Echo terug naar alle clients
wss.clients.forEach((client) => {
if (client.readyState === client.OPEN) {
client.send(`Echo: ${message}`);
}
});
});
socket.on('close', () => {
console.log('Client verbroken');
});
});
Client in de browser
const socket = new WebSocket('ws://localhost:8080');
socket.addEventListener('open', () => {
socket.send('Hallo server!');
});
socket.addEventListener('message', (event) => {
console.log('Server zei:', event.data);
});
Met minder dan twintig regels code heb je een werkende realtime echo-server. Maar productieklaar is dit nog lang niet.
Socket.IO: batterijen inbegrepen
Socket.IO is een library die bovenop WebSockets draait en een hoop zaken uit handen neemt:
- Automatic reconnect als de verbinding wegvalt
- Rooms en namespaces om clients te groeperen
- Fallback naar HTTP long-polling voor oudere browsers of strikte proxy's
- Acknowledgements, zodat je per bericht een bevestiging kunt vragen
- Middleware voor authenticatie en validatie
Socket.IO server met rooms
import { Server } from 'socket.io';
import { createServer } from 'node:http';
const httpServer = createServer();
const io = new Server(httpServer, {
cors: { origin: 'https://wedecom.net' },
});
io.on('connection', (socket) => {
socket.on('join-room', (roomId) => {
socket.join(roomId);
socket.to(roomId).emit('user-joined', socket.id);
});
socket.on('chat-message', ({ roomId, message }) => {
io.to(roomId).emit('chat-message', {
from: socket.id,
message,
at: new Date().toISOString(),
});
});
});
httpServer.listen(3000);
Rooms zijn cruciaal voor applicaties met meerdere kanalen (chatrooms, documenten, games). Je hoeft niet zelf bij te houden welke client in welke kamer zit, Socket.IO doet dat voor je.
Authenticatie bij WebSockets
Een open WebSocket-endpoint is een recept voor misbruik. Je moet verbindingen authenticeren, net zoals je dat doet bij een REST API. We hebben de basis hiervan besproken in Authentication in Node.js: JWT vs sessions uitgelegd.
JWT via query parameter of header
import jwt from 'jsonwebtoken';
io.use((socket, next) => {
const token = socket.handshake.auth.token;
if (!token) return next(new Error('No token'));
try {
const payload = jwt.verify(token, process.env.JWT_SECRET);
socket.data.userId = payload.sub;
next();
} catch {
next(new Error('Invalid token'));
}
});
Aan de clientkant:
import { io } from 'socket.io-client';
const socket = io('wss://api.wedecom.net', {
auth: { token: localStorage.getItem('jwt') },
});
Zet JWT's niet in de URL als query parameter, die belanden in server logs. Gebruik de auth-optie of een custom header tijdens de handshake.
Berichten valideren
Alles wat binnenkomt via een WebSocket moet je als untrusted input behandelen. Een client kan willekeurige JSON sturen. Gebruik een validator zoals Zod om payloads te controleren.
import { z } from 'zod';
const ChatMessage = z.object({
roomId: z.string().uuid(),
message: z.string().min(1).max(2000),
});
socket.on('chat-message', (payload, ack) => {
const result = ChatMessage.safeParse(payload);
if (!result.success) {
return ack?.({ ok: false, error: 'Invalid payload' });
}
// verwerk bericht
});
Meer over deze aanpak lees je in Validatie met Zod: type-safe data valideren in Node.js.
Schaalbaarheid: meerdere Node.js-instances
Zodra je meer dan één Node.js-proces draait, krijg je een probleem. Elke client is verbonden met precies één proces. Als gebruiker A op server 1 zit en gebruiker B op server 2, kunnen ze elkaars berichten niet zien zonder extra infrastructuur.
Oplossing: Redis pub/sub adapter
Socket.IO heeft een kant-en-klare Redis adapter die berichten over alle instances publiceert:
import { createAdapter } from '@socket.io/redis-adapter';
import { createClient } from 'redis';
const pubClient = createClient({ url: 'redis://localhost:6379' });
const subClient = pubClient.duplicate();
await Promise.all([pubClient.connect(), subClient.connect()]);
io.adapter(createAdapter(pubClient, subClient));
Nu fungeert Redis als een message bus. Elk bericht dat je naar een room broadcast, wordt doorgestuurd naar alle Node.js-instances, die het op hun beurt naar hun lokale clients sturen.
Load balancer met sticky sessions
Een WebSocket-verbinding moet altijd bij hetzelfde backend-proces blijven. Configureer je load balancer (Nginx, HAProxy, AWS ALB) met sticky sessions of gebruik consistent hashing op basis van het client-IP of een cookie.
Event loop en backpressure
WebSockets draaien op dezelfde event loop als de rest van je Node.js-applicatie. Als je synchronously zware berekeningen doet in een message handler, blokkeer je alle andere verbindingen. Zie Wat is Node.js en hoe werkt de event loop? voor een opfrisser.
Offload zware taken
Stuur cpu-intensieve jobs naar een queue of worker thread in plaats van ze inline te verwerken. Meer hierover lees je in Background jobs en workers in Node.js uitgelegd.
Backpressure aan de zendkant
Als je in hoog tempo berichten naar een client stuurt die traag consumeert, loopt de send buffer vol. Controleer socket.bufferedAmount (in de browser) of gebruik de flow-control opties van ws. Drop berichten, combineer ze of pauzeer de stream.
Veelvoorkomende valkuilen
- Geen heartbeat, browsers en proxy's sluiten stille verbindingen. Socket.IO doet dit automatisch; met pure
wsmoet je zelf eenping/pong-mechanisme inbouwen. - Onbegrensde message size, zet een
maxPayload-limiet om OOM-crashes te voorkomen. - Geen rate limiting per connection, een kwaadwillende client kan je server overladen. Tel berichten per seconde en disconnect bij overschrijding.
- Origin niet valideren, controleer de
Origin-header tijdens de handshake om CSRF-achtige aanvallen te voorkomen. - State alleen in memory, bij een restart verlies je alle rooms en presence-data. Persist belangrijke state in Redis of een database, zoals besproken in Werken met databases in Node.js: praktische gids.
Observability
Realtime apps zijn lastig te debuggen omdat berichten vluchtig zijn. Log connection events, bericht-aantallen per seconde, disconnect-redenen en pub/sub latency. Integreer met tools zoals Prometheus of OpenTelemetry. Zie ook Error handling op schaal in Node.js uitgelegd voor hoe je errors centraal afvangt.
Veelgestelde vragen
Wat is een WebSocket?
Een WebSocket is een netwerkprotocol dat een persistente, bidirectionele verbinding opent tussen client en server. In tegenstelling tot HTTP blijft de verbinding open, zodat beide kanten op elk moment berichten kunnen versturen zonder dat de client telkens moet pollen.
Wanneer gebruik je WebSockets in plaats van HTTP?
Gebruik WebSockets wanneer je lage latency en realtime updates nodig hebt, zoals chat, live notificaties, multiplayer games of collaborative editing. Voor standaard request/response flows zoals een REST API blijft HTTP de betere keuze.
Wat is het verschil tussen ws en Socket.IO?
ws is een lichte, pure WebSocket-implementatie voor Node.js. Socket.IO is een uitgebreidere library bovenop WebSockets met rooms, automatic reconnect, fallback naar HTTP long-polling en een eigen protocol. Socket.IO is handiger voor productieapps, ws is minimalistischer.
Hoe schaal je WebSockets naar meerdere servers?
Single-server WebSockets schalen slecht omdat clients aan één proces vastzitten. Voor horizontale schaalbaarheid gebruik je een pub/sub-backend zoals Redis, zodat berichten tussen Node.js-instances gedeeld worden. Een load balancer met sticky sessions of op basis van consistent hashing is ook nodig.
Zijn WebSockets veilig?
WebSockets zijn zo veilig als je ze maakt. Gebruik altijd wss:// (TLS), authenticeer verbindingen met tokens, valideer alle inkomende berichten en beperk de rate van berichten per client. Behandel WebSocket-input net zo voorzichtig als HTTP-input.
Conclusie
WebSockets openen de deur naar echt interactieve, realtime applicaties, maar ze komen met een eigen set aan uitdagingen rond schaalbaarheid, security en state management. Begin klein met ws of Socket.IO, voeg authenticatie en validatie toe vanaf dag één, en plan je schaalstrategie met Redis zodra je groeit.
Bouw je een product waarin realtime een kernervaring wordt? Begin dan met een duidelijk beeld van welke events er tussen client en server stromen, en behandel je WebSocket-laag net zo serieus als je REST API.