Server-Sent Events in TypeScript: Data Streaming Without the Headache
\n\nImagine: your server generates data — logs, notifications, currency exchange rate updates — and needs to instantly send them to the frontend. WebSocket? Powerful, but overkill. Polling? Slow and consumes bandwidth. There is a third path — elegant, simple, and incredibly useful: Server-Sent Events (SSE).
\n\nIn this article, we will break down SSE in TypeScript from start to finish. We will write a client and server, learn how to handle errors and reconnect, and most importantly — understand when SSE truly wins over other approaches. Let's go!
\n\nWhat are Server-Sent Events and Why Do You Need Them?
\n\nServer-Sent Events is an HTML5 standard that allows a server to send data to a client over a single HTTP connection in real time. Unlike WebSocket, SSE is one-way: only the server sends messages. But that is precisely its strength — it is extremely simple and requires no additional libraries.
\n\nWhen to Choose SSE?
\n\n- \n
- One-way streaming: notifications, logs, currency rates, stock tickers. \n
- Simple integration: works via the standard
EventSourcein the browser — no npm packages needed. \n - Automatic reconnection: the browser restores the connection on its own when it drops. \n
- HTTP/2 compatibility: multiplexing does not interfere, unlike WebSocket. \n
But remember: SSE cannot send data from the client to the server (use regular POST requests for that) and is not supported in older browsers (looking at you, IE).
\n\nServer Side: Writing SSE in TypeScript + Express
\n\nLet's start with the backend. We will create a simple server using Node.js with Express and TypeScript. Install the dependencies:
\n\nnpm init -y\nnpm install express cors\nnpm install -D typescript @types/express @types/node ts-node\nnpx tsc --init\n\nNow let's write the server itself. The key point is setting the correct headers and the message format.
\n\n// server.ts\nimport express, { Request, Response } from 'express';\nimport cors from 'cors';\n\nconst app = express();\napp.use(cors());\n\n// SSE endpoint\napp.get('/events', (req: Request, res: Response) => {\n // Set headers for SSE\n res.writeHead(200, {\n 'Content-Type': 'text/event-stream',\n 'Cache-Control': 'no-cache',\n 'Connection': 'keep-alive',\n });\n\n // Send initial message\n res.write('data: {"message": "Connection established"}\\n\\n');\n\n // Send an event every 3 seconds\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 // Handle client disconnect\n req.on('close', () => {\n clearInterval(intervalId);\n res.end();\n console.log('Client disconnected');\n });\n});\n\nconst PORT = 3000;\napp.listen(PORT, () => console.log(`SSE server running on port ${PORT}`));\n\nNote the format: each message starts with data: and ends with two newlines \\n\\n. You can also specify id, event, and retry — but this is the basics.
Sending Named Events
\n\nSometimes you need to distinguish between message types. Let's add the event field:
res.write('event: update\\n');\nres.write(`data: ${JSON.stringify({ id: 1, title: 'New post' })}\\n\\n`);\n\n// And then — another event\nres.write('event: notification\\n');\nres.write('data: {"level": "info", "text": "Server is running"}\\n\\n');\n\nOn the client side, we will be able to subscribe to specific events.
\n\nClient Side: Receiving Events in TypeScript
\n\nNow let's write the client. In the browser, the built-in EventSource is used. But if