NestJS WebSockets
WebSockets create a persistent, two-way connection between the client and server. Unlike HTTP where the client always initiates a request, WebSockets let the server push data to the client at any time. Chat applications, live dashboards, real-time notifications, multiplayer games, and collaborative editing tools all use WebSockets because polling the server every few seconds is inefficient and slow.
HTTP vs WebSocket Communication
HTTP (Request-Response):
Client → "give me new messages" → Server
Server → "here are 0 messages" → Client
(repeated every few seconds — wasteful)
WebSocket (Persistent Connection):
Client ←——————— Connection open ———————— Server
←— "New message from Alice!" ←—
←— "Bob is typing..." ←—
→— "User sends a message" →—
(server pushes data the instant something happens)
Setting Up WebSockets in NestJS
npm install @nestjs/websockets @nestjs/platform-socket.io socket.io
Creating a Gateway
A Gateway is the WebSocket equivalent of a Controller. It handles WebSocket connections and events:
import {
WebSocketGateway, WebSocketServer,
SubscribeMessage, MessageBody,
ConnectedSocket, OnGatewayConnection, OnGatewayDisconnect
} from '@nestjs/websockets';
import { Server, Socket } from 'socket.io';
@WebSocketGateway({
cors: { origin: '*' },
namespace: 'chat', // optional: ws://localhost:3000/chat
})
export class ChatGateway implements OnGatewayConnection, OnGatewayDisconnect {
@WebSocketServer()
server: Server;
handleConnection(client: Socket) {
console.log(`Client connected: ${client.id}`);
}
handleDisconnect(client: Socket) {
console.log(`Client disconnected: ${client.id}`);
}
@SubscribeMessage('sendMessage')
handleMessage(
@MessageBody() data: { room: string; message: string; sender: string },
@ConnectedSocket() client: Socket,
) {
// Broadcast to everyone in the room
this.server.to(data.room).emit('receiveMessage', {
sender: data.sender,
message: data.message,
timestamp: new Date().toISOString(),
});
}
@SubscribeMessage('joinRoom')
handleJoinRoom(
@MessageBody() room: string,
@ConnectedSocket() client: Socket,
) {
client.join(room);
client.emit('joinedRoom', room);
this.server.to(room).emit('userJoined', { userId: client.id, room });
}
}
Event Flow Diagram
Client A joins room "general":
Client A → emit('joinRoom', 'general') → Gateway.handleJoinRoom()
Gateway → client.join('general')
Gateway → emit('userJoined', ...) to room 'general'
Client A sends a message:
Client A → emit('sendMessage', { room, message, sender })
Gateway.handleMessage() runs
Gateway → server.to('general').emit('receiveMessage', {...})
Client A ← receives message
Client B ← receives message (also in 'general' room)
Client C ← does NOT receive (not in 'general' room)
Emitting from a Service
Sometimes you need to push data to clients from a service (not triggered by a WebSocket event) — for example, when a new database record is created via a REST endpoint:
@Injectable()
export class NotificationsService {
constructor(
@InjectModel(Notification.name)
private notificationModel: Model<Notification>,
) {}
async create(userId: string, message: string) {
const notification = await this.notificationModel.create({ userId, message });
// Push to the specific user's socket room
this.chatGateway.server.to(`user-${userId}`).emit('notification', notification);
return notification;
}
}
Registering the Gateway as a Module
@Module({
providers: [ChatGateway, ChatService],
})
export class ChatModule {}
WebSocket Guards
Guards work on WebSocket gateways too. Apply them with @UseGuards() on the gateway class or individual message handlers:
@WebSocketGateway()
@UseGuards(WsJwtGuard)
export class ChatGateway {
@SubscribeMessage('privateMessage')
@UseGuards(WsJwtGuard)
handlePrivateMessage(@MessageBody() data, @ConnectedSocket() client: Socket) {
const user = client.data.user; // set by the guard
// ...
}
}
Client-Side Connection (Browser)
import { io } from 'socket.io-client';
const socket = io('http://localhost:3000/chat');
socket.on('connect', () => {
socket.emit('joinRoom', 'general');
});
socket.on('receiveMessage', (data) => {
console.log(`${data.sender}: ${data.message}`);
});
// Send a message
socket.emit('sendMessage', {
room: 'general',
message: 'Hello everyone!',
sender: 'Alice',
});
WebSockets in NestJS follow the same structural patterns as HTTP — gateways instead of controllers, @SubscribeMessage() instead of @Get(), the same guards and pipes. This consistency means the skills you build learning NestJS HTTP handling transfer directly to building real-time features with WebSockets.
