nestjs/nest · error · ReferenceError
You must return an Observable stream to use Server-Sent Even
Error message
You must return an Observable stream to use Server-Sent Events (SSE).
What it means
An `@Sse()` route handler must return an RxJS Observable — the framework subscribes to it and streams each emitted value as a Server-Sent Event, completing the response when the observable completes. If the handler returns a Promise, array, or plain value, the response controller throws ReferenceError 'You must return an Observable stream to use Server-Sent Events (SSE)' as soon as it inspects the return value.
Source
Thrown at packages/core/router/router-response-controller.ts:294
if (closeRequested) {
settled = true;
endStream();
response.end();
resolve();
return;
}
settled = true;
finalize();
endStream();
reject(err);
});
});
}
private assertObservable(value: any) {
if (!isObservable(value)) {
throw new ReferenceError(
'You must return an Observable stream to use Server-Sent Events (SSE).',
);
}
}
private getOrCreateAbortController(
request: IncomingMessage,
): AbortController {
const carrier = request as IncomingMessage & {
[SSE_ABORT_CONTROLLER]?: AbortController;
};
if (!carrier[SSE_ABORT_CONTROLLER]) {
carrier[SSE_ABORT_CONTROLLER] = new AbortController();
}
return carrier[SSE_ABORT_CONTROLLER];
}
}
View on GitHub (pinned to dd75d7bd8c)
Solutions
- Make the handler synchronous and return an Observable, e.g. `interval(1000).pipe(map(() => ({ data: tick })))`.
- Bridge event sources with RxJS: `fromEvent(emitter, 'update')` or `new Observable(subscriber => ...)`, and map payloads to `{ data: ... }` message shape.
- Remove `async` from the @Sse() method signature — a Promise is never an acceptable substitute.
- Type the return as `Observable<MessageEvent>` so the compiler rejects invalid returns.
Example fix
// before
@Sse('updates')
async updates() { // Promise -> ReferenceError
return this.db.poll();
}
// after
import { interval } from 'rxjs';
import { map } from 'rxjs/operators';
@Sse('updates')
updates(): Observable<MessageEvent> {
return interval(1000).pipe(map(() => ({ data: { hello: 'world' } })));
} Defensive patterns
Strategy: type-guard
Validate before calling
// Guarantee an Observable leaves the handler, whatever the source is
import { isObservable, of, from, Observable } from 'rxjs';
@Sse('updates')
updates(): Observable<MessageEvent> {
const source = this.buildSource(); // may be value, array or promise
if (isObservable(source)) return source as Observable<MessageEvent>;
if (Array.isArray(source)) return from(source) as Observable<MessageEvent>;
return from(Promise.resolve(source)) as Observable<MessageEvent>;
} Type guard
import { isObservable, Observable } from 'rxjs';
const isSseStream = (v: unknown): v is Observable<{ data: unknown }> =>
isObservable(v); Prevention
- Declare every @Sse() handler as returning Observable<MessageEvent> so the compiler rejects async/Promise returns.
- Never mark @Sse() methods async; stream pushes belong in RxJS operators, not awaited calls.
- Bridge emitters/queues with fromEvent/webSocketSubject instead of polling promises.
When it happens
Trigger: The @Sse() method is declared `async` (async functions always return a Promise) or returns `Promise.from(...)`, a plain object, an EventEmitter, or `of(...).toPromise()`; a plain `@Get()` handler is converted to @Sse() without rewriting the body; mixing SSE push semantics with one-shot database results.
Common situations: Adding realtime notifications/progress endpoints; developers used to returning arrays/objects from handlers; wrapping event emitters with `fromEvent` forgotten; TypeScript not flagging it because the method was typed loosely.
Related errors
- Conflicting HTTP routes detected: - ${messages} Adjust rou
- An invalid controller has been detected. "${className}" does
- HTTP adapter does not support filtering on host: "${host}"
- Cannot ${method} ${url}
AI-assisted analysis of nestjs/nest@dd75d7bd8c (2026-08-21).
Data as JSON: /api/errors/db1f404d22401b2c.
Report an issue: GitHub.