# WebSockets in NestJS: Building Real-Time Features in Production

> A practical guide to WebSocket Gateways in NestJS with Socket.io — rooms, namespaces, Redis pub/sub for scaling, and JWT authentication.

URL: https://www.matthewswong.com/en/blog/websockets-nestjs-realtime/

- Author: Matthews Wong (https://www.matthewswong.com/en/about/)
- Published: 2025-06-10
- Updated: 2025-06-10
- Category: Web Dev

Real-time features — live notifications, collaborative editing, status dashboards — are table stakes for modern web applications. NestJS's WebSocket Gateway abstraction makes building these features surprisingly clean. I've implemented real-time notification systems and live dashboard updates in production NestJS apps using Socket.io, and the NestJS abstraction handles 90% of the boilerplate you'd otherwise write yourself.

## NestJS WebSocket Gateway: The Basics

NestJS wraps Socket.io (or ws) in a Gateway decorator pattern. A Gateway is structurally identical to a Controller — it handles incoming events instead of HTTP requests. The @WebSocketGateway() decorator registers the class as a WebSocket server. @SubscribeMessage('event-name') handles specific events. @WebSocketServer() injects the underlying Socket.io server instance for broadcasting. The Gateway integrates fully with NestJS's dependency injection, so you can inject services, repositories, and config — exactly like a controller.

### Rooms and Namespaces

Rooms let you broadcast to a subset of connected clients — perfect for user-specific notifications or group chat. A client joins a room with socket.join('room-id'), and the server broadcasts to it with this.server.to('room-id').emit('event', data). Namespaces separate concerns at the connection level — a /notifications namespace and a /chat namespace are independent WebSocket endpoints. Use namespaces when different features need completely separate connection lifecycles and auth policies.

> From building a live order status dashboard in an ERP system: emit events only for data the client has permission to see. Use the connection handshake to associate the socket with a user ID and their accessible resource IDs, then on every emit, filter the target room by permission. Don't broadcast all order updates to all connected clients and filter on the frontend — that leaks business data to unauthorized users and wastes bandwidth.

## Scaling with Redis Pub/Sub

A single NestJS instance keeps all socket connections in memory. When you scale to multiple instances behind a load balancer, socket connections are distributed across instances — instance A can't emit to a socket connected to instance B. The solution is Redis as a shared pub/sub broker. Install the socket.io-adapter-redis package, configure it with your Redis connection, and Socket.io handles cross-instance broadcasting automatically. Every instance publishes events to Redis, and Redis fans them out to all instances that have the target client.

### JWT Authentication for WebSocket Connections

WebSocket connections don't carry headers after the initial HTTP upgrade. Pass JWT in the auth option of the Socket.io client handshake: io(url, '{ auth: { token: ''your-jwt'' }' '}'). On the NestJS Gateway side, implement a WebSocket middleware or use the handleConnection lifecycle hook to verify the token before allowing the connection. Reject invalid tokens by calling socket.disconnect(). This prevents unauthenticated sockets from listening to any events.

> **Don't Store Application State in Gateway Memory**

> It's tempting to keep a Map'<'userId, socketId> in the Gateway class for direct targeting. This breaks the moment you have more than one server instance — instance A's map doesn't know about connections on instance B. Always use Redis or your database as the source of truth for socket-to-user mapping. Store the mapping in Redis on connection, clear it on disconnect, and look it up on every emit. This adds one Redis roundtrip per targeted emit but ensures correctness at any scale.

## Error Handling and Reconnection

WebSocket connections drop — network interruptions, server restarts, mobile apps going to background. Socket.io's client handles reconnection automatically, but your Gateway needs to handle the reconnect event gracefully: re-join rooms, re-sync state. On the server side, implement the OnGatewayDisconnect interface to clean up when a client disconnects — remove from rooms, update presence status in the database, cancel any server-side timers tied to that client.

## What I Use and Why

For any NestJS project needing real-time features, I use @nestjs/websockets with the Socket.io adapter. The NestJS Gateway abstraction is clean and integrates perfectly with the rest of the NestJS ecosystem. For production deployments with more than one instance, Redis pub/sub via @socket.io/redis-adapter is the standard solution. I don't use plain WebSocket (ws package) unless I need sub-millisecond latency and can't afford Socket.io's overhead — for ERP and SaaS apps, Socket.io's reconnection and room management features are worth the trade-off.

## Production Checklist

Before shipping WebSocket features to production: (1) Configure Redis adapter for multi-instance deployments. (2) Implement JWT auth in the connection handshake — no unauthenticated sockets. (3) Use rooms for permission-scoped broadcasts, not global events. (4) Handle disconnection and reconnection gracefully. (5) Set a maximum connection limit and circuit-break if Redis is unavailable — degrade gracefully to polling rather than crashing. (6) Monitor active socket connections in your observability stack — unexpected spikes indicate connection leaks.

## Sources & Further Reading

- NestJS Docs — WebSockets Gateways — https://docs.nestjs.com/websockets/gateways
- VideoSDK — Mastering Real-Time Communication with NestJS WebSocket in 2025 — https://www.videosdk.live/developer-hub/websocket/nest-js-websocket
- DEV Community — Implementing Rate Limiting in NestJS with Custom Redis Storage — https://dev.to/mythili007/implementing-rate-limiting-in-nestjs-with-custom-redis-storage-4fnd

## Source links

- https://docs.nestjs.com/websockets/gateways
- https://www.videosdk.live/developer-hub/websocket/nest-js-websocket
- https://dev.to/mythili007/implementing-rate-limiting-in-nestjs-with-custom-redis-storage-4fnd

## FAQ

### Why does NestJS use a Gateway pattern instead of plain WebSocket handlers?

NestJS wraps Socket.io in a Gateway decorator that is structurally identical to a Controller, so it plugs directly into the existing dependency injection system. This means you can inject services, repositories, and config into a Gateway exactly as you would in a controller, and NestJS handles 90% of the boilerplate you would otherwise write yourself.

### How do you scale WebSocket connections across multiple NestJS instances?

A single NestJS instance keeps all socket connections in memory, so instance A cannot emit to a socket connected to instance B when you scale horizontally. The solution is to install the @socket.io/redis-adapter package and configure it with a Redis connection — Socket.io then publishes events to Redis and Redis fans them out to whichever instance holds the target client.

### How is JWT authentication handled for WebSocket connections?

WebSocket connections do not carry HTTP headers after the initial upgrade, so the JWT must be passed in the auth option of the Socket.io client handshake (e.g., io(url, { auth: { token: 'your-jwt' } })). On the server side, you verify the token inside the handleConnection lifecycle hook or a WebSocket middleware and call socket.disconnect() to reject any invalid tokens before they can listen to any events.

### Why should socket-to-user mappings be stored in Redis rather than in Gateway memory?

Keeping a Map<userId, socketId> in the Gateway class breaks the moment you run more than one server instance, because instance A's in-memory map has no knowledge of connections held by instance B. The correct approach is to store the mapping in Redis on connection, clear it on disconnect, and look it up on every emit — this adds one Redis round-trip per targeted emit but guarantees correctness at any scale.

### What is the recommended production checklist before shipping WebSocket features?

The post lists six steps: configure the Redis adapter for multi-instance deployments, enforce JWT auth in the connection handshake, use rooms for permission-scoped broadcasts instead of global events, handle disconnect and reconnect gracefully with OnGatewayDisconnect, set a maximum connection limit and circuit-break to polling if Redis is unavailable, and monitor active socket connections in your observability stack for unexpected spikes that signal connection leaks.

---

- HTML version: https://www.matthewswong.com/en/blog/websockets-nestjs-realtime/
- Bahasa Indonesia: https://www.matthewswong.com/id/blog/websockets-nestjs-realtime/
- Site index for agents: https://www.matthewswong.com/llms.txt
- Sitemap: https://www.matthewswong.com/sitemap.xml
- API description (OpenAPI): https://www.matthewswong.com/openapi.json
