Bidirectional Real-Time Channels
@fastify/websocket
Add first-class WebSocket routes to Fastify with the same plugin model. Pair them with the standard Fastify lifecycle for authentication and validation.
What you'll learn
- Install @fastify/websocket
- Mark a route as websocket and handle the connection
- Pair with preHandler hooks for auth
@fastify/websocket integrates the ws library into Fastify. WebSocket routes are declared with the same app.get(...) call — just pass { websocket: true }.
Install & Register
npm install @fastify/websocket import websocket from '@fastify/websocket'
await app.register(websocket, {
options: { maxPayload: 1048576 },
}) A Chat Echo Endpoint
app.get('/ws', { websocket: true }, (socket, req) => {
socket.on('message', (raw) => {
const text = raw.toString()
socket.send(`echo: ${text}`)
})
socket.on('close', (code, reason) => {
req.log.info({ code, reason: reason.toString() }, 'ws closed')
})
}) Each connected client gets its own socket. Keep a per-app map of active sockets if you need to broadcast.
Auth With preHandler
Because WebSocket routes share Fastify’s lifecycle, you can authenticate the upgrade request with a normal preHandler — no separate auth path.
app.get(
'/ws',
{
websocket: true,
preHandler: async (req, reply) => {
try {
await req.jwtVerify()
} catch {
reply.code(401).send()
}
},
},
(socket, req) => {
socket.send(`hello ${(req.user as { sub: string }).sub}`)
},
) If preHandler rejects, the upgrade is refused — the client sees a clean 401 instead of a half-open socket.