WebSocket gateway
The project uses Socket.io with a session-based authentication model. WebSocket connections are persistent, so the starter authenticates once during the handshake and trusts the connection afterwards. This keeps per-message latency low, which is important for real-time systems. The trade-off is that invalidating a user who is already connected is harder; you have to disconnect them explicitly rather than relying on a token expiring on every message.
Centralized connection handling
Section titled “Centralized connection handling”NestJS applies the connection logic of one gateway to all gateways on the same namespace. The starter uses this to centralize authentication in a base gateway and extend it for domain-specific features.
Create a base gateway that handles authentication in handleConnection:
export class WSManager implements OnGatewayInit, OnGatewayConnection { @Inject(AuthenticationService) private readonly authService: AuthenticationService;
handleConnection(client: Socket, ...args: any[]) { const user = this.authService.authenticateHandshake(client.handshake); if (!user) { client.disconnect(true); return; } client.user = user; }}Use property-based injection on the parent class so subclasses do not have to pass dependencies through their constructors.
Extend it for each feature:
@WebSocketGateway({ namespace: '/notifications' })export class NotificationGateway extends WSManager { @SubscribeMessage('subscribe-to-notifications') handleSubscription(client: Socket) { // client.user is already set }}If a namespace needs extra connection logic, call super.handleConnection() and add your own:
@WebSocketGateway({ namespace: '/chat' })export class ChatGateway extends WSManager { constructor(private readonly chatService: ChatService) { super(); }
handleConnection(client: Socket, ...args: any[]) { super.handleConnection(client, ...args); const room = this.chatService.resolveRoom(client.handshake); client.join(room); }}How the starter implements it
Section titled “How the starter implements it”The generated project ships with a single WsConnectionsManagerGateway on the default namespace. It reads the session cookie from the handshake, looks up the Redis session key sess:<sessionId>, attaches the user to the socket, and joins a per-user room.
async handleConnection(client: Socket) { const sessionId = this.extractSessionIdFromSocket(client); if (!sessionId) { client.disconnect(true); return; } const userId = await this.resolveUserIdFromSession(sessionId); if (!userId) { client.disconnect(true); return; } const user = await this.userService.findById(userId); if (!user) { client.disconnect(true); return; } client['user'] = user; await client.join(`user_${user.id}`);}Redis adapter
Section titled “Redis adapter”The starter includes @socket.io/redis-adapter in its dependencies but does not wire it by default. To scale across multiple Node.js processes, create the Redis adapter in the gateway’s afterInit or in a dedicated module and attach it to the server.
Guards in WebSocket context
Section titled “Guards in WebSocket context”Guards are still useful for authorization (RBAC) on specific messages. You can guard a @SubscribeMessage handler with a custom guard that checks client.user or roles. The connection itself is already authenticated, so guards only need to decide if the user may perform the action.