WebSocket Overview
WebSocket is a protocol for full-duplex communication over a single TCP connection. NestJS provides comprehensive WebSocket support through @nestjs/websockets and @nestjs/platform-socket.io packages, enabling developers to easily build real-time applications.
NestJS WebSocket Basics
Install Dependencies
bashnpm install @nestjs/websockets @nestjs/platform-socket.io
Create WebSocket Gateway
Gateways are classes in NestJS that handle WebSocket connections, similar to how controllers handle HTTP requests.
typescriptimport { WebSocketGateway, WebSocketServer, SubscribeMessage, OnGatewayInit, OnGatewayConnection, OnGatewayDisconnect } from '@nestjs/websockets'; import { Server, Socket } from 'socket.io'; @WebSocketGateway({ cors: { origin: '*', }, }) export class ChatGateway implements OnGatewayInit, OnGatewayConnection, OnGatewayDisconnect { @WebSocketServer() server: Server; afterInit(server: Server) { console.log('WebSocket server initialized'); } handleConnection(client: Socket) { console.log(`Client connected: ${client.id}`); } handleDisconnect(client: Socket) { console.log(`Client disconnected: ${client.id}`); } @SubscribeMessage('message') handleMessage(client: Socket, payload: any): void { this.server.emit('message', payload); } }
Register Gateway
typescriptimport { Module } from '@nestjs/common'; import { ChatGateway } from './chat.gateway'; @Module({ providers: [ChatGateway], }) export class ChatModule {}
WebSocket Gateway Decorators
@WebSocketGateway()
Configure WebSocket gateway.
typescript@WebSocketGateway({ namespace: '/chat', // Namespace cors: { // CORS configuration origin: '*', }, path: '/ws', // Path transports: ['websocket'], // Transport methods }) export class ChatGateway {}
@WebSocketServer()
Inject WebSocket server instance.
typescript@WebSocketServer() server: Server;
@SubscribeMessage()
Subscribe to WebSocket messages.
typescript@SubscribeMessage('message') handleMessage(client: Socket, payload: any): void { // Handle message }
@MessageBody()
Extract message body.
typescript@SubscribeMessage('message') handleMessage(@MessageBody() data: any): void { console.log(data); }
@ConnectedSocket()
Get connected Socket instance.
typescript@SubscribeMessage('message') handleMessage(@ConnectedSocket() client: Socket): void { console.log(client.id); }
Gateway Lifecycle Hooks
OnGatewayInit
typescriptafterInit(server: Server) { console.log('Gateway initialized'); }
OnGatewayConnection
typescripthandleConnection(client: Socket) { console.log(`Client connected: ${client.id}`); }
OnGatewayDisconnect
typescripthandleDisconnect(client: Socket) { console.log(`Client disconnected: ${client.id}`); }
Real-time Chat Application Example
Chat Gateway
typescriptimport { WebSocketGateway, WebSocketServer, SubscribeMessage, OnGatewayConnection, OnGatewayDisconnect } from '@nestjs/websockets'; import { Server, Socket } from 'socket.io'; interface Message { user: string; text: string; timestamp: Date; } @WebSocketGateway({ cors: { origin: '*', }, }) export class ChatGateway implements OnGatewayConnection, OnGatewayDisconnect { @WebSocketServer() server: Server; private connectedClients: Map<string, Socket> = new Map(); handleConnection(client: Socket) { this.connectedClients.set(client.id, client); this.server.emit('user-joined', { userId: client.id, userCount: this.connectedClients.size, }); } handleDisconnect(client: Socket) { this.connectedClients.delete(client.id); this.server.emit('user-left', { userId: client.id, userCount: this.connectedClients.size, }); } @SubscribeMessage('join-room') handleJoinRoom(client: Socket, room: string): void { client.join(room); client.emit('joined-room', room); } @SubscribeMessage('leave-room') handleLeaveRoom(client: Socket, room: string): void { client.leave(room); client.emit('left-room', room); } @SubscribeMessage('send-message') handleMessage(client: Socket, payload: Message): void { const message: Message = { ...payload, timestamp: new Date(), }; this.server.emit('message', message); } @SubscribeMessage('private-message') handlePrivateMessage(client: Socket, payload: { to: string; message: Message }): void { const recipient = this.connectedClients.get(payload.to); if (recipient) { recipient.emit('private-message', payload.message); } } }
Frontend Client
typescriptimport { io, Socket } from 'socket.io-client'; class ChatClient { private socket: Socket; constructor(url: string) { this.socket = io(url); } connect() { this.socket.on('connect', () => { console.log('Connected to server'); }); this.socket.on('message', (message: Message) => { console.log('Received message:', message); }); this.socket.on('user-joined', (data) => { console.log('User joined:', data); }); this.socket.on('user-left', (data) => { console.log('User left:', data); }); } sendMessage(message: Message) { this.socket.emit('send-message', message); } sendPrivateMessage(to: string, message: Message) { this.socket.emit('private-message', { to, message }); } joinRoom(room: string) { this.socket.emit('join-room', room); } leaveRoom(room: string) { this.socket.emit('leave-room', room); } disconnect() { this.socket.disconnect(); } }
Using WebSocket Guards
typescriptimport { WebSocketGateway, SubscribeMessage, OnGatewayConnection } from '@nestjs/websockets'; import { UseGuards } from '@nestjs/common'; import { WsGuard } from './ws.guard'; @WebSocketGateway() @UseGuards(WsGuard) export class ChatGateway implements OnGatewayConnection { @SubscribeMessage('message') handleMessage(client: Socket, payload: any): void { // Only authenticated clients can handle messages } }
WebSocket Guard Example
typescriptimport { CanActivate, ExecutionContext, Injectable, UnauthorizedException } from '@nestjs/common'; import { Socket } from 'socket.io'; @Injectable() export class WsGuard implements CanActivate { canActivate(context: ExecutionContext): boolean { const client: Socket = context.switchToWs().getClient(); const token = client.handshake.auth.token; if (!token) { throw new UnauthorizedException(); } // Validate token return this.validateToken(token); } private validateToken(token: string): boolean { // Implement token validation logic return true; } }
Using WebSocket Interceptors
typescriptimport { WebSocketGateway, SubscribeMessage } from '@nestjs/websockets'; import { UseInterceptors } from '@nestjs/common'; import { LoggingInterceptor } from './logging.interceptor'; @WebSocketGateway() @UseInterceptors(LoggingInterceptor) export class ChatGateway { @SubscribeMessage('message') handleMessage(client: Socket, payload: any): void { // Messages will be processed by interceptor } }
Using WebSocket Pipes
typescriptimport { WebSocketGateway, SubscribeMessage, MessageBody } from '@nestjs/websockets'; import { UsePipes } from '@nestjs/common'; import { ValidationPipe } from './validation.pipe'; @WebSocketGateway() @UsePipes(new ValidationPipe()) export class ChatGateway { @SubscribeMessage('message') handleMessage(@MessageBody() message: Message): void { // Messages will be validated by pipe } }
Real-time Notification System
Notification Gateway
typescriptimport { WebSocketGateway, WebSocketServer, OnGatewayConnection, OnGatewayDisconnect } from '@nestjs/websockets'; import { Server, Socket } from 'socket.io'; interface Notification { type: 'info' | 'warning' | 'error' | 'success'; title: string; message: string; timestamp: Date; } @WebSocketGateway({ namespace: '/notifications', cors: { origin: '*' }, }) export class NotificationGateway implements OnGatewayConnection, OnGatewayDisconnect { @WebSocketServer() server: Server; private userSockets: Map<string, Set<string>> = new Map(); handleConnection(client: Socket) { const userId = client.handshake.query.userId as string; if (!this.userSockets.has(userId)) { this.userSockets.set(userId, new Set()); } this.userSockets.get(userId).add(client.id); } handleDisconnect(client: Socket) { const userId = client.handshake.query.userId as string; const sockets = this.userSockets.get(userId); if (sockets) { sockets.delete(client.id); if (sockets.size === 0) { this.userSockets.delete(userId); } } } sendNotificationToUser(userId: string, notification: Notification) { const sockets = this.userSockets.get(userId); if (sockets) { sockets.forEach(socketId => { this.server.to(socketId).emit('notification', notification); }); } } sendBroadcastNotification(notification: Notification) { this.server.emit('notification', notification); } }
Using Notification Gateway in Service
typescriptimport { Injectable } from '@nestjs/common'; import { NotificationGateway } from './notification.gateway'; @Injectable() export class NotificationService { constructor(private notificationGateway: NotificationGateway) {} sendOrderNotification(userId: string, orderId: string) { this.notificationGateway.sendNotificationToUser(userId, { type: 'info', title: 'Order Update', message: `Your order ${orderId} has been updated`, timestamp: new Date(), }); } sendSystemAlert(message: string) { this.notificationGateway.sendBroadcastNotification({ type: 'warning', title: 'System Notification', message, timestamp: new Date(), }); } }
Best Practices
- Namespaces: Use namespaces to organize different types of WebSocket connections
- Rooms: Use room functionality to manage user groups
- Authentication: Authenticate during connection
- Error Handling: Properly handle connection errors and message errors
- Resource Cleanup: Clean up resources on disconnect
- Message Validation: Use pipes to validate incoming messages
- Logging: Log connection and disconnect events
- Performance Monitoring: Monitor connection count and message throughput
Summary
NestJS WebSocket and real-time features provide:
- Complete WebSocket support
- Flexible gateway system
- Rich decorators and hooks
- Seamless integration with NestJS ecosystem
- Easy to build real-time applications
Mastering NestJS WebSocket functionality is key to building real-time applications. By properly using gateways, namespaces, rooms, and authentication mechanisms, you can build high-performance, scalable real-time applications such as chat applications, real-time notification systems, collaboration tools, etc.