WebSockets: Aplicaciones en Tiempo Real con Node.js
18 de diciembre de 2025
Osman Jimenez
WebSockets Node.js Tiempo Real
Comunicación Bidireccional en Tiempo Real
WebSockets permiten comunicación full-duplex entre cliente y servidor. Perfectos para chat, notificaciones, juegos y dashboards en tiempo real.
Servidor WebSocket con Socket.IO
// server.js
import express from 'express';
import { createServer } from 'http';
import { Server } from 'socket.io';
const app = express();
const httpServer = createServer(app);
const io = new Server(httpServer, {
cors: {
origin: 'http://localhost:4200',
methods: ['GET', 'POST']
}
});
io.on('connection', (socket) => {
console.log('Cliente conectado:', socket.id);
// Escuchar eventos
socket.on('message', (data) => {
console.log('Mensaje recibido:', data);
// Broadcast a todos
io.emit('message', data);
// O solo al emisor
socket.emit('message-received', { status: 'ok' });
});
// Unirse a una sala
socket.on('join-room', (roomId) => {
socket.join(roomId);
io.to(roomId).emit('user-joined', socket.id);
});
// Desconexión
socket.on('disconnect', () => {
console.log('Cliente desconectado:', socket.id);
});
});
httpServer.listen(3000, () => {
console.log('Servidor en puerto 3000');
});Cliente en Angular
// Instalar
npm install socket.io-client
// socket.service.ts
import { Injectable } from '@angular/core';
import { io, Socket } from 'socket.io-client';
import { Observable } from 'rxjs';
@Injectable({ providedIn: 'root' })
export class SocketService {
private socket: Socket;
constructor() {
this.socket = io('http://localhost:3000');
}
emit(event: string, data: any): void {
this.socket.emit(event, data);
}
on(event: string): Observable {
return new Observable((observer) => {
this.socket.on(event, (data: T) => {
observer.next(data);
});
return () => this.socket.off(event);
});
}
disconnect(): void {
this.socket.disconnect();
}
} Componente de Chat
// chat.component.ts
export class ChatComponent implements OnInit, OnDestroy {
messages = signal([]);
messageInput = '';
private socketService = inject(SocketService);
private destroy$ = new Subject();
ngOnInit() {
this.socketService.on('message')
.pipe(takeUntil(this.destroy$))
.subscribe((message) => {
this.messages.update(msgs => [...msgs, message]);
});
}
sendMessage() {
if (this.messageInput.trim()) {
this.socketService.emit('message', {
text: this.messageInput,
timestamp: Date.now()
});
this.messageInput = '';
}
}
ngOnDestroy() {
this.destroy$.next();
this.destroy$.complete();
this.socketService.disconnect();
}
} Salas (Rooms)
// Servidor
io.on('connection', (socket) => {
// Unirse a sala
socket.on('join-room', (roomId) => {
socket.join(roomId);
// Notificar a la sala
socket.to(roomId).emit('user-joined', {
userId: socket.id,
roomId
});
});
// Mensaje a sala específica
socket.on('room-message', ({ roomId, message }) => {
io.to(roomId).emit('message', message);
});
// Salir de sala
socket.on('leave-room', (roomId) => {
socket.leave(roomId);
socket.to(roomId).emit('user-left', socket.id);
});
});Autenticación
// Servidor con middleware
io.use((socket, next) => {
const token = socket.handshake.auth.token;
if (isValidToken(token)) {
socket.userId = getUserIdFromToken(token);
next();
} else {
next(new Error('Authentication error'));
}
});
// Cliente
const socket = io('http://localhost:3000', {
auth: {
token: 'jwt-token-here'
}
});
socket.on('connect_error', (err) => {
console.log('Error de conexión:', err.message);
});Reconexión Automática
const socket = io('http://localhost:3000', {
reconnection: true,
reconnectionDelay: 1000,
reconnectionDelayMax: 5000,
reconnectionAttempts: 5
});
socket.on('reconnect', (attemptNumber) => {
console.log('Reconectado después de', attemptNumber, 'intentos');
});
socket.on('reconnect_error', (error) => {
console.log('Error de reconexión:', error);
});Broadcasting Patterns
// A todos los clientes
io.emit('event', data);
// A todos excepto el emisor
socket.broadcast.emit('event', data);
// A una sala específica
io.to('room1').emit('event', data);
// A múltiples salas
io.to('room1').to('room2').emit('event', data);
// A un socket específico
io.to(socketId).emit('event', data);Namespaces
// Servidor
const chatNamespace = io.of('/chat');
const adminNamespace = io.of('/admin');
chatNamespace.on('connection', (socket) => {
console.log('Usuario en /chat');
});
adminNamespace.on('connection', (socket) => {
console.log('Admin en /admin');
});
// Cliente
const chatSocket = io('http://localhost:3000/chat');
const adminSocket = io('http://localhost:3000/admin');Escalabilidad con Redis
// Para múltiples instancias del servidor
import { createAdapter } from '@socket.io/redis-adapter';
import { createClient } from 'redis';
const pubClient = createClient({ url: 'redis://localhost:6379' });
const subClient = pubClient.duplicate();
Promise.all([pubClient.connect(), subClient.connect()]).then(() => {
io.adapter(createAdapter(pubClient, subClient));
});Monitoreo y Debugging
// Servidor
io.on('connection', (socket) => {
console.log('Conexiones activas:', io.engine.clientsCount);
// Rooms del socket
console.log('Salas:', socket.rooms);
// Todos los sockets en una sala
const sockets = await io.in('room1').fetchSockets();
console.log('Sockets en room1:', sockets.length);
});
// Cliente - Debug mode
const socket = io('http://localhost:3000', {
debug: true
});Mejores Prácticas
- Validación: Valida todos los datos recibidos
- Rate limiting: Previene spam
- Heartbeat: Detecta conexiones muertas
- Namespaces: Separa lógica de negocio
- Error handling: Maneja desconexiones gracefully
Conclusión
WebSockets con Socket.IO hacen trivial implementar funcionalidades en tiempo real. Son esenciales para aplicaciones modernas que requieren actualizaciones instantáneas y colaboración en tiempo real.