Introduction

Modern web applications often need real-time communication. Examples include:

  • Notifications
  • Chat applications
  • Live dashboards
  • Stock prices
  • Progress tracking
  • Monitoring systems

Most developers immediately think about WebSockets, but another technology is simpler and often sufficient:

Server-Sent Events (SSE).

This article explains:

  1. What SSE is
  2. How it works internally
  3. When to use it
  4. How to implement it in NestJS
  5. Common pitfalls
  6. Authentication challenges
  7. How to test SSE endpoints in Node.js and Vitest

What is Server-Sent Events (SSE)?

Server-Sent Events is an HTTP-based technology that allows a server to continuously push events to a client over a single long-lived HTTP connection.

Unlike a normal HTTP request:

text
Client ---> Request
Server ---> Response
Connection Closed

SSE keeps the connection open:

text
Client ---> Request
Server ---> Event 1
Server ---> Event 2
Server ---> Event 3
Connection remains open

The client passively listens for events.

SSE is One-Way Communication

SSE only allows:

text
Server ---> Client

It does not support:

text
Client ---> Server

If bidirectional communication is required, WebSockets are usually a better choice.

SSE vs Polling

Polling

text
Every 5 seconds:

Client ---> GET /notifications
Server ---> []

Problems:

  • Wasted requests
  • Increased server load
  • Delayed updates
  • Poor scalability

SSE

text
Client ---> GET /notifications/subscribe
Server ---> Keep connection open

Server ---> Notification 1
Server ---> Notification 2
Server ---> Notification 3

Advantages:

  • Near real-time
  • Less overhead
  • Simpler than WebSockets
  • Uses plain HTTP

SSE Message Format

SSE uses plain text.

Example:

text
data: Hello World

JSON payload:

text
data: {"id":1,"title":"New Message"}

Named event:

text
event: notification
data: {"id":1}

Message with ID:

text
id: 123
data: {"id":1}

Browser API

typescript
const es = new EventSource('/notifications/subscribe');

es.onmessage = (event) => {
  const data = JSON.parse(event.data);
  console.log(data);
};

SSE in NestJS

NestJS has first-class support for SSE.

Controller:

typescript
@Sse('subscribe')
sse() {
  return interval(1000).pipe(
    map(i => ({
      data: { count: i },
    })),
  );
}

Client receives:

Recommended by LinkedIn

text
data: {"count":1}

data: {"count":2}

data: {"count":3}

Real Example: Notification Service

typescript
@Sse('subscribe')
sse(
  @User() user: RequestUser,
) {
  return this.inAppService.listen(user.id);
}

Service:

typescript
listen(userId: string): Observable<MessageEvent> {
  return this.subject.asObservable();
}

When a notification is created:

typescript
this.subject.next({
  data: notification,
});

Authentication with SSE

SSE endpoints are often protected.

Example:

typescript
@UseGuards(SessionGuard)
@Sse('subscribe')

This works perfectly in browsers because cookies are automatically sent.

The Testing Problem

During end-to-end testing, we encountered:

text
401 Unauthorized

even though authentication worked for normal requests.

The SSE request looked like this:

typescript
new EventSource(url, {
  headers: {
    Cookie: cookie,
  },
});

The problem was:

text
eventsource@4

does not support custom headers anymore.

The Cookie header was silently ignored.

The actual request became:

http
GET /notifications/in-app/subscribe
Accept: text/event-stream

without:

http
Cookie: auth_session=...

The server returned:

text
401 Unauthorized

Solution: Use Fetch Instead of EventSource

typescript
const response = await fetch(url, {
  headers: {
    Cookie: cookie,
    Accept: 'text/event-stream',
  },
});

This allows cookies to be sent properly.

The response stream can then be parsed manually.

When Should You Use SSE?

SSE is excellent for:

  • Notifications
  • Progress tracking
  • Monitoring dashboards
  • Stock prices
  • Live metrics
  • Log streaming

When Should You Use WebSockets Instead?

Use WebSockets when:

  • Bidirectional communication is required
  • Chat applications
  • Multiplayer games
  • Collaborative editing
  • Frequent client-to-server messages

SSE Advantages

  • Simple HTTP protocol
  • Automatic reconnection
  • Lightweight
  • Firewall friendly
  • Easy to scale
  • Native browser support

SSE Limitations

  • One-way communication
  • Text-only protocol
  • Browser connection limits
  • Less flexible than WebSockets

Final Thoughts

Server-Sent Events are one of the most underused features of modern web development. For many real-time applications, especially notifications and dashboards, SSE provides a simpler, more scalable, and easier-to-maintain alternative to WebSockets.

Understanding the protocol, its authentication model, and its testing challenges can save hours of debugging and lead to significantly cleaner real-time architectures.