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.
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 →