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

  1. Make the handler synchronous and return an Observable, e.g. `interval(1000).pipe(map(() => ({ data: tick })))`.
  2. Bridge event sources with RxJS: `fromEvent(emitter, 'update')` or `new Observable(subscriber => ...)`, and map payloads to `{ data: ... }` message shape.
  3. Remove `async` from the @Sse() method signature — a Promise is never an acceptable substitute.
  4. 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

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


AI-assisted analysis of nestjs/nest@dd75d7bd8c (2026-08-21). Data as JSON: /api/errors/db1f404d22401b2c. Report an issue: GitHub.