Async Handlers & Observables

Return a Promise or an Observable — Nest Awaits Both

Async Handlers & Observables

Nest awaits returned Promises and subscribes to returned RxJS Observables, so async handlers feel natural.

4 min read Level 2/5 #nestjs#async#rxjs
What you'll learn
  • Mark handlers async and return Promises
  • Return an RxJS Observable for streaming-style values
  • Mix both patterns in the same app

Most server work is asynchronous — database calls, HTTP fetches, file I/O. Nest handles two flavors of async return values out of the box: Promises (via async/await) and Observables (RxJS).

Async/Await — The Common Case

import { Controller, Get } from '@nestjs/common';
import { UsersService } from './users.service';

@Controller('users')
export class UsersController {
  constructor(private readonly users: UsersService) {}

  @Get()
  async list() {
    return await this.users.findAll();
  }
}

async makes the function return a Promise; Nest awaits it before serializing. Errors thrown inside an async handler are caught by Nest’s exception filter and turned into HTTP responses — just like sync errors.

Returning a Promise Directly

You don’t even need async if you’re just forwarding:

@Get()
list() {
  return this.users.findAll(); // already returns Promise<User[]>
}

Same result, one less keyword.

Observables — When and Why

Nest also subscribes to RxJS Observables. For a one-shot HTTP response it’s overkill, but it’s the default in @nestjs/axios and in microservice transports, so you’ll see them:

import { Observable, of, map } from 'rxjs';

@Get('cats')
cats(): Observable<string[]> {
  return of(['Mittens', 'Felix']).pipe(
    map(list => list.map(n => n.toUpperCase())),
  );
}

For a regular @Get, Nest emits whatever the observable produces (the last value if it produces multiple) and closes the response. For Server-Sent Events, the rules change — see the @Sse decorator.

Mixing Both

You can use whichever model fits the call. Many controllers mix Promise-returning DB calls with Observable-returning HTTP calls:

import { firstValueFrom } from 'rxjs';
import { HttpService } from '@nestjs/axios';

@Get('weather/:city')
async weather(@Param('city') city: string) {
  const { data } = await firstValueFrom(
    this.http.get(`/api/${city}`),
  );
  return data;
}

firstValueFrom converts an Observable’s first emission to a Promise — handy when you want to stay in async/await land.

Don’t Block the Event Loop

Async handlers free the event loop while waiting for I/O — that’s the whole point. If your handler does heavy CPU work (large image resize, hashing, JSON.parse of a giant string), offload it to a worker thread or a queue. Returning a Promise doesn’t help if the work itself is synchronous.

Streaming Responses →