Files
What you'll learn
- Import HttpModule with options and make requests through HttpService, converting its observables with firstValueFrom
- Read an AxiosError: response status, timeout code, no response at all
- Translate upstream failures into NotFoundException, GatewayTimeoutException and BadGatewayException instead of leaking a 500
HTTP Module
So far every request has come to the cats API. Real services also make requests: to a payment provider, a geocoder, another team's API. The shelter does not keep breed facts; a breeds service does, and the cats API asks it. Nest's HTTP module wraps axios, the most used HTTP client in the Node world, and hands it over as an injectable HttpService, which returns Observables. This lesson makes the calls, and spends most of its time on the part that separates a toy from a service: what to do when the other side is slow, wrong or gone.
HttpModule and HttpService
@nestjs/axios provides the module; axios does the work:
@Module({
imports: [HttpModule],
providers: [CatsService],
})
export class CatsModule {}
Inject HttpService and call it like axios, with the same methods, get, post, put, delete, patch, and the same config object:
@Injectable()
export class CatsService {
constructor(private readonly httpService: HttpService) {}
findAll(): Observable<AxiosResponse<Cat[]>> {
return this.httpService.get('http://localhost:3000/cats');
}
}
The difference from raw axios is the return type: an Observable of the response rather than a Promise, so rxjs operators apply. Most services want a promise in the end, and firstValueFrom converts:
async findAll(): Promise<Cat[]> {
const { data } = await firstValueFrom(
this.httpService.get<Cat[]>('http://localhost:3000/cats').pipe(
catchError((error: AxiosError) => {
this.logger.error(error.response.data);
throw 'An error happened!';
}),
),
);
return data;
}
AxiosResponse carries data, status, headers; the generic on get<Cat[]> types data. When you want axios itself, for an interceptor or a feature the wrapper does not expose, this.httpService.axiosRef is the underlying instance.
Configuration
HttpModule.register({ timeout: 5000, maxRedirects: 5 }) passes axios defaults to every request the module makes; baseURL and headers belong there too. registerAsync({ useFactory, inject }) builds them from configuration, as every module in this chapter does, and useClass/useExisting take a factory class implementing HttpModuleOptionsFactory.
When the other side fails
An outbound request can fail in three ways, and each deserves a different answer to your own client. The upstream answered with an error: error.response exists, with status and data; a 404 there usually means your client asked for something that does not exist, so answer 404 too. The upstream did not answer in time: with timeout set, axios aborts and reports error.code === 'ECONNABORTED' (ETIMEDOUT in some paths), and the honest answer is 504 Gateway Timeout. The upstream could not be reached or broke: no response, or a 5xx; answer 502 Bad Gateway. Nest has an exception class for each, and catchError inside the pipe is where you translate. What you must not do is let an AxiosError escape unmapped: it becomes a 500 with Internal server error, and the client learns nothing.
A timeout is not optional. Without one, a hung upstream hangs your handler, and enough of those hang your server. Set it in register(), once.
In this runtime
The breeds service, https://api.catbreeds.example, is simulated by fixtures declared for this lesson: GET /breeds and GET /breeds/siamese answer, GET /breeds/sphynx is a 404, and GET /breeds/slow takes longer than your timeout allows. The code you write is exactly the code you would ship against the real thing.
Your task
The cats API asks the breeds service about breeds.
CatsModuleimports the HTTP module with a two-second timeout.BreedsService.findAllfetches${BREEDS_API}/breeds;findOne(slug)fetches${BREEDS_API}/breeds/${slug}. Both convert the observable to a promise and returndata.- Translate failures: a
404from the upstream isNotFoundExceptionNo such breed: <slug>; a timeout isGatewayTimeoutExceptionThe breeds service did not answer in time; anything else isBadGatewayExceptionThe breeds service failed.
Run after step 2 and request GET /breeds/sphynx: an unmapped AxiosError is a 500, and the console shows what axios knew that your client was not told.
When it fails
Nest can't resolve dependencies of the BreedsService (?), argumentHttpService:HttpModuleis not imported by the module that provides the service.500 Internal server errorfor a breed that does not exist: theAxiosErrorescaped; catch it and look aterror.response?.status.GET /breeds/slowanswers after a long wait, or as a502: notimeoutinregister(), or the timeout code is not recognised; checkerror.code.- The handler returns an object with
_subscribein it: the observable was returned as data; convert withfirstValueFrom.
Remember
HttpModule.register({ timeout })in the module;HttpServicein the constructor;get<T>(url)returnsObservable<AxiosResponse<T>>.firstValueFrom(observable.pipe(catchError(...)))gives a promise, and the pipe is where errors are translated.- Map upstream failures deliberately:
404→NotFoundException, timeout →GatewayTimeoutException, the rest →BadGatewayException. - Always set a timeout on outbound requests.
Stuck? Show a hint
cats.module.ts: imports: [..., HttpModule.register({ timeout: 2000 })] from '@nestjs/axios'. Service: constructor(private readonly http: HttpService) {}; const { data } = await firstValueFrom(this.http.get<Breed>(url).pipe(catchError((error: AxiosError) => { ... throw ...; }))); inside catchError: error.response?.status === 404 → NotFoundException; error.code === 'ECONNABORTED' → GatewayTimeoutException; otherwise BadGatewayException.
Press Run tests to start the app. Its log appears here.Graded endpoints
HttpService fetched the list from the breeds service and the handler returned its data
A breed the service knows
The upstream answered 404; catchError translated the AxiosError into this API's own 404
The upstream needs 2.6 seconds and the timeout is 2: axios aborted with ECONNABORTED, answered as 504
A 503 from the upstream is not this API's fault and not the client's: 502
A cat whose breed the service can describe
The database and the upstream, combined in one handler