Server-Sent Events en TypeScript: streaming de datos sin dolor de cabeza
\n\nImagine: su servidor genera datos — registros, notificaciones, actualizaciones del tipo de cambio — y debe enviarlos instantáneamente al frontend. ¿WebSocket? Potente, pero excesivo. ¿Polling? Lento y consume tráfico. Hay un tercer camino — elegante, simple e increíblemente útil: Server-Sent Events (SSE).
\n\nEn este artículo analizaremos SSE en TypeScript de principio a fin. Escribiremos un cliente y un servidor, aprenderemos a manejar errores y reconexiones, y, sobre todo, entenderemos cuándo SSE realmente gana frente a otros enfoques. ¡Allá vamos!
\n\n¿Qué son Server-Sent Events y para qué sirven?
\n\nServer-Sent Events es un estándar de HTML5 que permite al servidor enviar datos al cliente a través de una única conexión HTTP en tiempo real. A diferencia de WebSocket, SSE es unidireccional: solo el servidor envía mensajes. Pero ahí radica su fortaleza — es extremadamente simple y no requiere bibliotecas adicionales.
\n\n¿Cuándo elegir SSE?
\n\n- \n
- Streaming unidireccional: notificaciones, registros, tipos de cambio, tickers de acciones. \n
- Integración simple: funciona mediante el
EventSourceestándar del navegador — sin paquetes npm. \n - Reconexión automática: el navegador restablece la conexión por sí mismo cuando se interrumpe. \n
- Compatibilidad con HTTP/2: la multiplexación no interfiere, a diferencia de WebSocket. \n
Pero recuerde: SSE no puede enviar datos del cliente al servidor (para eso use solicitudes POST normales) y no es compatible con navegadores antiguos (IE — un saludo).
\n\nParte del servidor: escribimos SSE en TypeScript + Express
\n\nEmpecemos con el backend. Crearemos un servidor simple en Node.js con Express y TypeScript. Instale las dependencias:
\n\nnpm init -y\nnpm install express cors\nnpm install -D typescript @types/express @types/node ts-node\nnpx tsc --init\n\nAhora escribamos el servidor. El punto clave es establecer los encabezados correctos y el formato de los mensajes.
\n\n// server.ts\nimport express, { Request, Response } from 'express';\nimport cors from 'cors';\n\nconst app = express();\napp.use(cors());\n\n// Endpoint para SSE\napp.get('/events', (req: Request, res: Response) => {\n // Establecemos los encabezados para SSE\n res.writeHead(200, {\n 'Content-Type': 'text/event-stream',\n 'Cache-Control': 'no-cache',\n 'Connection': 'keep-alive',\n });\n\n // Enviamos un mensaje inicial\n res.write('data: {"message": "Conexión establecida"}\\n\\n');\n\n // Enviamos un evento cada 3 segundos\n const intervalId = setInterval(() => {\n const data = JSON.stringify({\n time: new Date().toISOString(),\n value: Math.random() * 100,\n });\n res.write(`data: ${data}\\n\\n`);\n }, 3000);\n\n // Manejamos la desconexión del cliente\n req.on('close', () => {\n clearInterval(intervalId);\n res.end();\n console.log('Cliente desconectado');\n });\n});\n\nconst PORT = 3000;\napp.listen(PORT, () => console.log(`Servidor SSE iniciado en el puerto ${PORT}`));\n\nObserve el formato: cada mensaje comienza con data: y termina con dos saltos de línea \\n\\n. También se pueden especificar id, event y retry — pero esto es lo básico.
Envío de eventos con nombre
\n\nA veces es necesario diferenciar los tipos de mensajes. Añadimos el campo event:
res.write('event: update\\n');\nres.write(`data: ${JSON.stringify({ id: 1, title: 'Nuevo post' })}\\n\\n`);\n\n// Y luego — otro evento\nres.write('event: notification\\n');\nres.write('data: {"level": "info", "text": "El servidor está funcionando"}\\n\\n');\n\nEn el cliente podremos suscribirnos a eventos específicos.
\n\nParte del cliente: recibimos eventos en TypeScript
\n\nAhora escribamos el cliente. En el navegador se usa el EventSource incorporado. Pero si