@fastify/websocket

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.

4 min read Level 3/5 #fastify#websocket#realtime
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.

GraphQL With Mercurius →