Axios는 다양한 기능을 갖춘 HTTP 클라이언트 패키지로 널리 사용됩니다.
Nest는 Axios를 감싸서 내장된 HttpModule
을 통해 노출합니다.
HttpModule
은 Axios 기반의 메서드를 노출하는 HttpService
클래스를 내보냅니다.
또한 이 라이브러리는 HTTP 응답을 Observables
로 변환합니다.
Hint!
got
이나undici
와 같은 일반적인 목적의 Node.js HTTP 클라이언트 라이브러리를 직접 사용할 수도 있습니다.
$ npm i --save @nestjs/axios axios
설치 과정이 완료되면 HttpService
를 사용하기 위해 HttpModule
을 가져와 줍니다.
@Module({
imports: [HttpModule],
providers: [CatsService],
})
export class CatsModule {}
다음으로 일반적인 생성자 주입을 사용하여 HttpService
를 주입합니다.
Hint!
HttpModule
과HttpService
는@nestjs/axios
패키지에서 가져옵니다.
@Injectable()
export class CatsService {
constructor(private readonly httpService: HttpService) {}
findAll(): Observable<AxiosResponse<Cat[]>> {
return this.httpService.get('http://localhost:3000/cats');
}
}
Hint!
AxiosResponse
는 axios패키지에서 내보낸 인터페이스입니다. ($ npm i axios
)
모든 HttpService
메서드는 Observable
객체로 래핑된 AxiosResponse
를 반환합니다.
Axios는 HttpService
의 동작을 사용자가 정의하기 위해 다양한 옵션으로 구성할 수 있습니다.
더 자세한 내용은 여기에서 확인하실 수 있습니다.
기본 Axios 인스턴스를 구성하려면 HttpModule
의 register()
메서드에 옵션 객체를 전달하면 됩니다. 이 옵션 객체는 기반 Axios 생성자에 직접 전달됩니다.
@Module({
imports: [
HttpModule.register({
timeout: 5000,
maxRedirects: 5,
}),
],
providers: [CatsService],
})
export class CatsModule {}
모듈 옵션을 정적으로 전달하는 대신 registerAsync()
메서드를 사용하여 모듈 옵션을 비동기적으로 전달할 수 있습니다. 대부분의 동적 모듈과 마찬가지로, Nest는 비동기적인 구성을 다루기 위한 여러 기술을 제공합니다.
useFactory를 사용한 방법
HttpModule.registerAsync({
useFactory: () => ({
timeout: 5000,
maxRedirects: 5,
}),
});
여타 팩토리 프로바이더와 마찬가지로, 팩토리 함수는 비동기적일 수 있으며, inject를 통해 종속성을 주입할 수 있습니다.
HttpModule.registerAsync({
imports: [ConfigModule],
useFactory: async (configService: ConfigService) => ({
timeout: configService.get('HTTP_TIMEOUT'),
maxRedirects: configService.get('HTTP_MAX_REDIRECTS'),
}),
inject: [ConfigService],
});
useClass를 이용한 방식도 있습니다.
HttpModule.registerAsync({
useClass: HttpConfigService,
});
위의 구성은 HttpModule
내에서 HttpConfigService
를 인스턴스화하고, 옵션 객체를 생성하는 데 사용합니다. 이 예제에서 HttpConfigService
는 아래와 같이 HttpModuleOptionsFactory
인터페이스를 구현해야 합니다. HttpModule
은 제공된 클래스의 인스턴스화된 객체에서 createHttpOptions()
메서드를 호출합니다.
@Injectable()
class HttpConfigService implements HttpModuleOptionsFactory {
createHttpOptions(): HttpModuleOptions {
return {
timeout: 5000,
maxRedirects: 5,
};
}
}
만약 HttpModule
내에서 개인 복사본을 만들지 않고 기존의 옵션 프로바이더를 재사용하려면 useExisting
구문을 사용할 수 있습니다.
HttpModule.registerAsync({
imports: [ConfigModule],
useExisting: HttpConfigService,
});
HttpModule.register
의 옵션으로 충분하지 않거나 @nestjs/axios
에 의해 생성된 기본 Axios 인스턴스에 직접 액세스하려는 경우 다음과 같이 HttpService#axiosRef
를 통해 액세스할 수 있습니다.
@Injectable()
export class CatsService {
constructor(private readonly httpService: HttpService) {}
findAll(): Promise<AxiosResponse<Cat[]>> {
return this.httpService.axiosRef.get('http://localhost:3000/cats');
// ^ AxiosInstance 인터페이스
}
}
HttpService
메서드의 반환 값이 Observable
이므로 rxjs
의 firstValueFrom
또는 lastValueFrom
을 사용하여 요청의 데이터를 프로미스 형태로 가져올 수 있습니다.
import { catchError, firstValueFrom } from 'rxjs';
@Injectable()
export class CatsService {
private readonly logger = new Logger(CatsService.name);
constructor(private readonly httpService: HttpService) {}
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;
}
}
Hint!
firstValueFrom
와lastValueFrom
의 차이점에 대한 자세한 내용은RxJS
문서의 firstValueFrom와 lastValueFrom을 참조하십시오.
Observable이란?
RxJS 라이브러리의 개념으로, 비동기적으로 발생하는 데이터의 스트림을 나타내는 객체입니다.
이 스트림은 여러 이벤트나 값을 순차적으로 방출할 수 있으며, 이를 통해 비동기 데이터 처리 및 조작을 용이하게 할 수 있습니다. Observable은 데이터 스트림을 다루기 위한 다양한 연산자와 메서드를 제공하여 데이터의 변형, 필터링, 병합 등 다양한 작업을 할 수 있게 해줍니다.
AxiosResponse란?
Axios 라이브러리의 HTTP 요청에 대한 응답 객체를 나타냅니다. 이 객체에는 서버로부터 받은 응답 데이터, 상태 코드, 응답 헤더 등이 포함되어 있습니다.
RxJS?
RxJS는 Reactive Extensions For JavaScript 라이브러리입니다. RxJS는 이벤트 스트림을 Observable 이라는 객체로 표현한 후 비동기 이벤트 기반의 프로그램 작성을 돕습니다. 이벤트 처리를 위한 API로 다양한 연산자를 제공하는 함수형 프로그래밍 기법도 도입되어 있습니다.
Nest가 Axios응답을 굳이 Observable로 변환해서 반환해주는 이유는?
비동기 데이터 처리 / Reactive Programming / 연산자 사용 / 비동기 이슈 관리 등의 이유
그래서 HTTP module이란걸 사용하여 Axios 요청을 할 수 있다.
HttpModule
은 사용자에세 HTTP 요청 및 응답을 보다 편리하게 작업할 수 있도록 해준다.
HttpModule Config
Axios 직접 사용
HttpModule
은 Axios를 추상화하고, HTTP 요청 및 응답을 다루는 데 필요한 기능을 제공하여 개발자가 더 편리하게 작업할 수 있도록 도와주는 목적으로 사용됩니다.
다만, axiosRef
를 직접 사용하는 것은 더 많은 컨트롤이 필요한 특정 상황에서 유용한 도구입니다.
즉, NestJS의 HttpModule
은 기본적으로는 간편한 HTTP 요청 처리를 위해 설계되었지만, 필요한 경우 axiosRef
를 통해 더 세밀한 컨트롤을 할 수 있습니다. 따라서 HttpModule
을 사용하더라도 대부분의 상황에서 원하는 작업을 구현할 수 있습니다.
사용 예시
import { Injectable } from '@nestjs/common';
import { HttpService } from '@nestjs/axios';
import { Observable, map } from 'rxjs';
import { Axios, AxiosResponse } from 'axios';
@Injectable()
export class HttpTestService {
constructor(
private readonly httpService: HttpService,
){}
setTest(): Observable<AxiosResponse<any>> {
return this.httpService.get('http://localhost:3000/v1/cats/setCache').pipe(
map(response => response.data)
);
}
getTest(): Observable<AxiosResponse<any>> {
return this.httpService.get('http://localhost:3000/v1/cats/getCache').pipe(
map(response => response.data)
);
}
getString(): Observable<AxiosResponse<any>> {
return this.httpService.get('http://localhost:3000/v3/cats').pipe(
map(response => response.data) // Extract data from the response
);
}
}
발생한 오류
ERROR [ExceptionsHandler] Converting circular structure to JSON
--> starting at object with constructor 'ClientRequest'
| property 'socket' -> object with constructor 'Socket'
--- property '_httpMessage' closes the circle
TypeError: Converting circular structure to JSON
--> starting at object with constructor 'ClientRequest'
| property 'socket' -> object with constructor 'Socket'
--- property '_httpMessage' closes the circle
at JSON.stringify (<anonymous>)
원래 코드
import { Injectable } from '@nestjs/common';
import { HttpService } from '@nestjs/axios';
import { Observable, map } from 'rxjs';
import { Axios, AxiosResponse } from 'axios';
@Injectable()
export class HttpTestService {
constructor(
private readonly httpService: HttpService,
){}
setTest(): Observable<AxiosResponse<any>> {
return this.httpService.get('http://localhost:3000/v1/cats/setCache');
}
getTest(): Observable<AxiosResponse<any>> {
return this.httpService.get('http://localhost:3000/v1/cats/getCache');
}
getString(): Observable<AxiosResponse<any>> {
return this.httpService.get('http://localhost:3000/v3/cats');
}
}