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

  1. Validación: Valida todos los datos recibidos
  2. Rate limiting: Previene spam
  3. Heartbeat: Detecta conexiones muertas
  4. Namespaces: Separa lógica de negocio
  5. 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.