En esta página
WebSockets y comunicación en tiempo real
WebSockets en NestJS
Los WebSockets permiten comunicación bidireccional en tiempo real entre el servidor y los clientes. A diferencia de HTTP que es request-response, WebSockets mantienen una conexión persistente que permite al servidor enviar datos al cliente sin que este los solicite.
NestJS integra Socket.IO de forma nativa a través de @nestjs/websockets, ofreciendo la misma filosofía de decoradores y DI que ya conoces del lado HTTP.
Instalación
npm install @nestjs/websockets @nestjs/platform-socket.io socket.ioEl decorador @WebSocketGateway
Un gateway es el equivalente WebSocket de un controlador HTTP. Se configura con el decorador @WebSocketGateway:
@WebSocketGateway() // puerto 3000 por defecto
@WebSocketGateway(8080) // puerto específico
@WebSocketGateway({ namespace: '/chat' }) // namespace específico
@WebSocketGateway({
cors: { origin: '*' }, // CORS para WebSockets
transports: ['websocket', 'polling'], // transportes permitidos
pingInterval: 10000, // ping cada 10 segundos
pingTimeout: 5000, // timeout de ping
})Ciclo de vida del Gateway
import {
OnGatewayInit, OnGatewayConnection, OnGatewayDisconnect
} from '@nestjs/websockets';
import { Server, Socket } from 'socket.io';
@WebSocketGateway()
export class NotificacionesGateway
implements OnGatewayInit, OnGatewayConnection, OnGatewayDisconnect
{
@WebSocketServer()
server: Server;
// Llamado después de que el servidor WebSocket se inicializa
afterInit(_server: Server): void {
console.log('Gateway inicializado');
}
// Llamado cuando un cliente se conecta
handleConnection(client: Socket): void {
console.log(`Conectado: ${client.id}`);
}
// Llamado cuando un cliente se desconecta
handleDisconnect(client: Socket): void {
console.log(`Desconectado: ${client.id} — Razón: ${client.disconnected}`);
}
}Manejando mensajes con @SubscribeMessage
import { SubscribeMessage, MessageBody, ConnectedSocket } from '@nestjs/websockets';
@WebSocketGateway({ namespace: '/notificaciones' })
export class NotificacionesGateway {
@WebSocketServer()
server: Server;
// Respuesta simple (retorno del método)
@SubscribeMessage('saludo')
handleSaludo(@MessageBody() nombre: string): string {
return `¡Hola, ${nombre}!`;
}
// Emitir a un evento diferente en lugar de hacer ack
@SubscribeMessage('marcarLeido')
handleMarcarLeido(
@MessageBody() data: { notificacionId: string },
@ConnectedSocket() client: Socket,
): void {
// Emitir a todos EXCEPTO al cliente actual
client.broadcast.emit('notificacionLeida', data.notificacionId);
// Emitir solo al cliente actual
client.emit('confirmacionLeido', { ok: true });
// Emitir a todos (incluyendo al cliente)
this.server.emit('contadorActualizado', { pendientes: 0 });
}
}Rooms — Grupos de clientes
Las rooms permiten agrupar clientes y emitir mensajes solo a ese grupo:
// Unirse a una sala
@SubscribeMessage('joinRoom')
async handleJoinRoom(
@MessageBody() roomId: string,
@ConnectedSocket() client: Socket,
): Promise<void> {
await client.join(roomId);
// Notificar a todos en la sala que alguien se unió
this.server.to(roomId).emit('userJoined', { id: client.id });
}
// Abandonar una sala
@SubscribeMessage('leaveRoom')
async handleLeaveRoom(
@MessageBody() roomId: string,
@ConnectedSocket() client: Socket,
): Promise<void> {
await client.leave(roomId);
this.server.to(roomId).emit('userLeft', { id: client.id });
}
// Emitir a una sala específica desde cualquier punto
emitirASala(roomId: string, evento: string, datos: unknown): void {
this.server.to(roomId).emit(evento, datos);
}Emitir eventos desde servicios HTTP
Un patrón muy útil: emitir eventos WebSocket cuando ocurren acciones HTTP. Por ejemplo, notificar a los usuarios conectados cuando se crea un nuevo pedido:
// pedidos/pedidos.service.ts
@Injectable()
export class PedidosService {
constructor(
@InjectRepository(Pedido) private readonly repo: Repository<Pedido>,
private readonly notificacionesGateway: NotificacionesGateway,
) {}
async crearPedido(dto: CrearPedidoDto, usuarioId: string): Promise<Pedido> {
const pedido = await this.repo.save(this.repo.create({ ...dto, usuarioId }));
// Notificar en tiempo real al usuario que creó el pedido
this.notificacionesGateway.emitirAUsuario(usuarioId, 'pedidoCreado', {
pedidoId: pedido.id,
total: pedido.total,
mensaje: 'Tu pedido ha sido creado con éxito',
});
return pedido;
}
}Filtros de excepción para WebSockets
import { Catch, ArgumentsHost, BadRequestException } from '@nestjs/common';
import { BaseWsExceptionFilter, WsException } from '@nestjs/websockets';
import { Socket } from 'socket.io';
@Catch(WsException, BadRequestException)
export class WsExceptionFilter extends BaseWsExceptionFilter {
catch(exception: WsException | BadRequestException, host: ArgumentsHost): void {
const client = host.switchToWs().getClient<Socket>();
const mensaje =
exception instanceof WsException
? exception.getError()
: exception.message;
client.emit('error', {
tipo: 'error',
mensaje,
timestamp: new Date().toISOString(),
});
}
}Cliente Socket.IO en el frontend
Para conectarte desde Angular:
// En Angular — instalación: npm install socket.io-client
import { io } from 'socket.io-client';
const socket = io('http://localhost:3000/chat', {
auth: {
token: 'tu_jwt_token_aqui',
},
transports: ['websocket'],
});
socket.on('connect', () => console.log('Conectado al chat'));
socket.on('nuevoMensaje', (mensaje) => console.log('Nuevo mensaje:', mensaje));
// Enviar un mensaje
socket.emit('enviarMensaje', { salaId: 'sala-1', contenido: '¡Hola a todos!' });
// Manejar reconexión automática
socket.on('disconnect', (reason) => {
if (reason === 'io server disconnect') {
socket.connect(); // reconectar manualmente si el servidor nos desconectó
}
});Los WebSockets añaden una dimensión completamente nueva a tus APIs: la comunicación en tiempo real. En la próxima lección aprenderemos a gestionar la configuración de la aplicación con variables de entorno y @nestjs/config.
Inicia sesión para guardar tu progreso