WebSockets in NestJS (Part 2)
In Part 1, we covered how WebSockets work and how to implement basic communication using NestJS gateways. That foundation is enough for…
WebSockets in NestJS (Part 2)
In Part 1, we covered how WebSockets work and how to implement basic communication using NestJS gateways. That foundation is enough for simple real-time features, but real applications require more structure.
In production systems, we need to handle multiple users efficiently, secure connections, validate incoming data, and ensure the system remains stable under load. This part focuses on those practical patterns.
Key Points
- Real-time apps require structured communication
- Users must be grouped efficiently
- Authentication is required for security
- Data validation prevents bad input
- Error handling ensures system stability
Room Management (Group Communication)
Rooms allow you to group multiple users and send messages only to relevant clients instead of broadcasting to everyone.

- A room is a logical group of sockets
- A user can join multiple rooms
- Messages can be targeted to specific rooms
- Useful for chat, orders, live sessions
Join Room
@SubscribeMessage('joinRoom')
handleJoinRoom(
@MessageBody() data: { roomId: string },
@ConnectedSocket() client: Socket,
) {
client.join(data.roomId);
this.server.to(data.roomId).emit('userJoined', {
userId: client.id,
});
}
Send Message to Room
@SubscribeMessage('roomMessage')
handleRoomMessage(
@MessageBody() data: { roomId: string; message: string },
@ConnectedSocket() client: Socket,
) {
this.server.to(data.roomId).emit('receiveMessage', {
userId: client.id,
message: data.message,
});
}
How It Works
When a client joins a room, the socket is internally mapped to that group. After joining, any message sent using server.to(roomId) will only reach clients in that room. This avoids unnecessary broadcasting and keeps communication efficient.
Authentication with JWT
WebSocket connections do not automatically use HTTP authentication, so we need to validate users manually.

- Token is passed during connection
- Server verifies the token
- User data is stored in the socket
- Invalid users are disconnected
Guard Implementation
import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common';
import { JwtService } from '@nestjs/jwt';
import { Socket } from 'socket.io';
@Injectable()
export class WsAuthGuard implements CanActivate {
constructor(private jwtService: JwtService) {}
canActivate(context: ExecutionContext): boolean {
const client = context.switchToWs().getClient<Socket>();
const token = client.handshake.auth?.token;
if (!token) {
client.disconnect();
return false;
}
try {
const decoded = this.jwtService.verify(token);
client.data.user = decoded;
return true;
}
catch {
client.disconnect();
return false;
}
}
}
Apply Guard
@UseGuards(WsAuthGuard)
@SubscribeMessage('sendMessage')
handleMessage(@ConnectedSocket() client: Socket) {
const user = client.data.user;
}
How It Works
The client sends a token during the connection handshake. The guard extracts and verifies this token. If valid, user data is attached to the socket and becomes accessible in all event handlers. If invalid, the connection is terminated immediately.
DTO Validation
Validating incoming data is essential to maintain system reliability and avoid unexpected errors.
- Prevents invalid inputs
- Ensures correct data format
- Uses class-validator with NestJS
- Applied using validation pipes
DTO Example
import { IsString, IsNotEmpty } from 'class-validator';
export class SendMessageDto {
@IsString()
@IsNotEmpty()
message: string;
@IsString()
@IsNotEmpty()
roomId: string;
}
Apply Validation
@UsePipes(new ValidationPipe({ whitelist: true }))
@SubscribeMessage('sendMessage')
handleMessage(
@MessageBody() dto: SendMessageDto,
@ConnectedSocket() client: Socket,
) {
this.server.to(dto.roomId).emit('receiveMessage', dto);
}
How It Works
The validation pipe automatically checks the incoming payload against the DTO. If the data does not match the expected structure, the request is rejected before reaching your business logic, keeping your system safe and consistent.
Error Handling
Handling errors properly ensures that the system does not crash and clients receive meaningful feedback.
Use try-catch blocks
- Send structured error responses
- Avoid breaking the connection
- Keep the system stable
Example
@SubscribeMessage('sendMessage')
handleMessage(@MessageBody() data: any, @ConnectedSocket() client: Socket) {
try {
if (!data.message) {
throw new Error('Message required');
}
this.server.emit('receiveMessage', data);
} catch (error) {
client.emit('error', {
message: error.message,
});
}
}
How It Works
Errors are captured inside the handler and sent back to the client as events. This prevents crashes and allows the frontend to handle issues gracefully.
Real-World Example: Order System
Let’s combine all concepts into a real use case.

Scenario
- User tracks an order
- Backend updates order status
- Only relevant users receive updates
Implementation
@SubscribeMessage('subscribeOrder')
handleSubscribe(
@MessageBody() data: { orderId: string },
@ConnectedSocket() client: Socket,
) {
const room = `order_${data.orderId}`;
client.join(room);
}
updateOrderStatus(orderId: string, status: string) {
const room = `order_${orderId}`;
this.server.to(room).emit('orderUpdate', {
orderId,
status,
});
}
How It Works
Each order is treated as a room. Users interested in that order join the room. Whenever the order status changes, the server emits updates only to that room, ensuring efficient and targeted communication.
Performance Considerations
Efficient communication becomes critical as the number of users grows.
- Avoid unnecessary global broadcasts
- Use rooms for targeted messaging
- Reduce event frequency when possible
- Clean up disconnected clients
Example
this.server.to(roomId).emit('update', data); // efficient
Instead of:
this.server.emit('update', data); // inefficient
How It Works
Targeting specific rooms reduces network load and improves performance. Broadcasting to all users should only be used when absolutely necessary.
Summary
At this point, you’re not just using WebSockets, you’re understanding how to design real-time systems properly.
- Rooms enable scalable communication by sending data only to relevant users instead of broadcasting to everyone
- JWT authentication secures connections and ensures only valid users can interact with the system
- DTO validation protects data flow by preventing invalid inputs from reaching business logic
- Error handling keeps the system stable and avoids unexpected crashes during runtime
- Proper structuring improves maintainability, aligning with NestJS architecture for long-term scalability
These patterns together form the base of a production-ready WebSocket system, not just a basic setup.
Final Note
With Part 1 and Part 2 combined, we now have a complete understanding of building real-time systems using WebSockets in NestJS.
We’re ready to implement:
- Chat systems
- Notification services
- Order tracking systems
- Live dashboards
The real value now comes from applying these concepts in actual projects. Once implemented, the flow and patterns will become much more intuitive, and you’ll be able to design efficient real-time features with confidence.
Enjoyed this guide? Drop your thoughts below — we’d love to hear them!
Brought to you by Pavankumar Patel from the Silversky Technology crew. Curious what else we’re building? Explore more at silverskytechnology.com.
메타데이터
- post_id
- 0bd3de2282ed
- slug
- websockets-in-nestjs-part-2-0bd3de2282ed
- url
- https://medium.com/@silverskytechnology/websockets-in-nestjs-part-2-0bd3de2282ed
- canonical_url
- https://medium.com/@silverskytechnology/websockets-in-nestjs-part-2-0bd3de2282ed
- author_url
- https://medium.com/@silverskytechnology
- status
- ok
- fetched_at
- 2026-06-09 15:37:30