일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |
31 |
- spring security
- builder
- nestjs
- Google OAuth
- 일급 객체
- Spring
- lombok
- synchronized
- Volatile
- middleware
- java
- Dependency Injection
- 일급 컬렉션
- OAuth 2.0
- factory
- Today
- Total
HJW's IT Blog
NestJS Document 알아보기 : Controller 본문
Overview
프로젝트 기본 구조
src
- app.controller.spec.ts
- app.controller.ts
- app.module.ts
- app.service.ts
- main.ts
프로젝트를 처음 생성하게 되면, 위와 같이 core file 들이 생성된다. 다음은 위 파일들에 대한 간단한 설명이다.
app.controller.ts : 하나의 라우트를 가진 기본 controller
app.controller.spec.ts : controller 에 대한 단위 테스트
app.module.ts : application 의 루트 모듈
app.service.ts : 하나의 메소드를 가진 기본 service
main.ts : Nest Application 인스턴스를 생성하기 위한 NestFactory 를 포함한 엔트리 파일
main.ts 파일에는 어플리케이션을 부트스트랩 하는 비동기 함수가 포함되어 있다.
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
await app.listen(process.env.PORT ?? 3000);
}
bootstrap();
위 코드에서 NestFactory 클래스를 사용하여 Nest 어플리케이션 인스턴스를 생성한다.
NestFactory 는 어플리케이션을 생성하는 여러가지 정적 메서드를 제공하는데, 그중 기본적으로 사용되는 create() 는 INestApplicaion 인터페이스를 만족하는 어플리케이션 객체를 반환한다.
INestApplication 주요 메서드
위에서 설명했듯이, INestApplication 은 NestJS 에서 어플리케이션 인스턴스를 나타내는 인터페이스 이다. 이 어플리케이션은 HTTP 서버로 동작하기 때문에, 서버 역할을 하며 사용할 수 있는 다양한 메서드와 기능을 정의한다. 다음은 주요 메서드 들이다.
app.listen(); // 서버를 특정 포트에서 시작하고, HTTP 요청을 수신할 준비가 되었다는것을 알림
app.getHttpServer(); // 어플리케이션에서 사용중인 기본 HTTP 서버 인스턴스에 접근할 수 있다
app.setGlobalPrefix(); // 모든 라우트에 대해 글로벌 경로 prefix 를 설정할 수 있다. ex( 'api')
app.use(); // 미들웨어 설정 함수
app.close(); // 종료 함수
app.enableCors(); // CORS 를 활성화한다. (다른 도메인에서의 요청을 허용)
NestJs 와 Express / Fastify
NestJs는 플렛폼 독립적인 프레임워크를 지향한다. 특정 플렛폼에 종속되지 않고, 여러 어플리케이션 유형에서 재사용 가능한 로직을 만들 수 있게 해준다. 기본적으로 NestJS 는 EXPRESS 와 FASIFY 어댑터를 지원하고 있다. 즉, 어댑터를 사용한다면 express 혹은 fastify 의 메서드를 사용할 수 있다는 의미이다.
// default
const app = await NestFactory.create(AppModule);
// express
const app = await NestFactory.create<NestExpressApplication>(AppModule);
// fastify
const app = await NestFactory.create<NestFastifyApplication>(
AppModule,
new FastifyAdapter(),
);
default로 인스턴스를 생성하게 되면, Express 를 래핑하여 어플리케이션이 실행되지만, 명시적으로 타입을 지정해 주지 않았기 때문에, express의 메서드를 사용할 수 없다.
Controllers
Controller 는 기본적으로 클라이언트의 요청을 받아들이고, 클라이언트 에게 응답을 반환하는 역할을 한다.
컨트롤러는 주로 클래스와 데코레이터를 사용하여 정의되며, 이 데코레이터들은 클래스와 메서드 들을 Nest 에 필요한 메타데이터와 연결해 준다. 이 과정을 통해 Nest 는 요청과 해당 컨트롤러를 매핑할 수 있다.
기본적인 컨트롤러 생성 예시
import { Controller, Get } from '@nestjs/common'
@Controller('cats')
export class CatsController {
@Get()
findAll(): string {
return 'This action returns all cats';
}
}
위 예시에서 @Controller('cats') 는 /cats 경로를 통해 들어오는 요청들을 처리하는 controller 임을 명시하는 것이다. Get() 의 경우, HTTP GET 요청을 처리하는 핸들러를 정의하는 것이다.
라우팅 처리 방식
NestJS 에서 라우팅 경로는 controller의 경로 프리픽스와 각 메서드의 데코레이터에 정의된 경로가 합쳐져서 결정된다. 예를 들어, 위 @Controller('cats') 와 @Get('breed') 을 결합하게 되면 /cats/breed 의 경로로 매핑이 되는 것이다.
<aside> 💡
특정 경로의 controller 를 생성하고 싶다면 CLI 에 다음 명령어를 사용하자 : nest g controller [name]
</aside>
응답 처리 방식
NestJS 는 두가지 방식으로 응답을 처리한다.
- 기본 방식 : 리퀘스트 핸들러가 JS 객체 또는 배열을 반환하면, 자동으로 JSON으로 직렬화 된다. 만약 primitive type 을 반환한다면, 그대로 응답한다. 기본적으로 200 Status Code를 반환하며, Post 요청의 경우 201 을 반환한다. Status Code 의 변경은 @HttpCode(...) 데코레이터를 사용하면 된다.
- 라이브러리 특정 방식 : @Res() 데코레이터를 사용해, Express 와 같은 라이브러리의 응답 객체를 직접 주입 받을 수 있다.(ex. findAll(@Res() response)) ) 이를 통해 응답 객체의 메서드를 사용하여 응답을 보다 세밀한 제어가 가능해 진다. 하지만 @Res() 를 사용할 경우, 기본적인 Nest 의 응답 처리는 비활성화 되기 때문에, 둘 모두 사용하고 싶다면 @Res( {passthrough: true} ) 옵션을 사용해야 한다.
Request 객체
클라이언트 요청 세부정보에 대한 접근이 필요한 경우, Nest 는 기본 플랫폼의 요청 객체에 대한 접근을 제공한다. 이를 @Res() 데코레이터를 핸들러의 시그니처에 추가하여, Nest 에게 요청 객체를 주입하도록 할 수 있다. 다음은 예시 코드이다.
import { Controller, Get, Req } from '@nestjs/common';
import { Request } from 'express';
@Controller('cats')
export class CatsController {
@Get()
findAll(@Req() request: Request): string {
return 'This action returns all cats';
}
}
Request 객체는 HTTP 요청을 나타내며, 요청 query string, parameters, http header 및 body 에 대한 속성을 가지고 있다. Nest 는 이에 대한 데코레이터도 제공을 하는데, 다음은 데코레이터에 대한 설명이다.
Decorator 일반 플렛폼 객체
@Request(), @Req() | req |
@Response(), @Res() | res |
@Next() | next |
@Session() | req.session |
@Param(key?: string) | req.params / req.params[key] |
@Body(key?: string) | req.body / req.body[key] |
@Query(key?: string) | req.query / req.query[key] |
@Headers(name?: string) | req.headers / req.headers[name] |
@Ip() | req.ip |
@HostParams() | req.hosts |
<aside> 💡
기본 라이브러리의 타이핑을 가져와야 완전히 @Request, @Res 의 기능을 사용할 수 있다. (ex. @types/express)
</aside>
<aside> 💡
@Request, @Res 를 주입하게 되면, 해당 핸들러에 대해 Nest가 라이브러리 특정 모드로 전환되며, 응단 관리에 대한 책임이 개발자에게 넘어온다. 이 경우 res.json, res.send 를 통해 어떤 종류의 응답을 발행해야 한다.
</aside>
Resource
Nest 는 모든 표준 HTTP 메서드에 대한 데코레이터를 제공한다. 즉 앞서 본 cats 엔드포인트에 post 메서드 또한 사용하고 싶다면 다음과 같이 작성하면 된다
import { Controller, Get, Post } from '@nestjs/common';
@Controller('cats')
export class CatsController {
@Post()
create(): string {
return 'This action adds a new cat';
}
@Get()
findAll(): string {
return 'This action returns all cats';
}
}
@Get() , @Post() , @Put() , @Delete() , @Patch() @Options() @Head() @All() 을 작성할 수 있다.
Route Wildcards
Nest 는 패턴 기반 라우트도 지원한다. * 를 사용하게 되면 , 모든 문자 조합과 일치하는 결과를 나타낸다.
예를 들어 @Get('ab*cd)` 를 작성하면, 해당 경로는 abcd, ab_cd, abecd 등과 일치하다. ?,+,*,() 문자도 라우트 경로에서 사용될 수 있으며, 정규 표현식의 부분 집합이다.
Status Code
앞서 말했듯이, @HttpCode(code) 로 정의할 수 있다.
사용전, @nestjs/common 패키지를 가져와야 한다.
Header
사용자 정의 해더를 지정하려면, @Header() 데코레이터를 사용하거나, 라이브러리별 응답 객체를 사용해 res.header() 를 호출할 수 있다.
Redirection
응답을 특정 URL 로 리다이렉트 하려면 @Redirect() 데코레이터를 사용할 수 있다. (라이브러리별 응답 객체를 사용하여 res.redirect() 도 직접 호출 가능)
@Redirect() 는 2개의 인자를 받을 수 있다 (url, statuscode). Status Code 생략시, 기본값은 302 이다.
만약 리다이렉션을 동적으로 결정하고 싶다면, HttpRedirectResponse 인터페이스를 따르는 객체를 반환하면 된다. 이렇게 하면 반환값이 기존 @Redirect() 에 작성된 argument 를 덮어쓸 것이다.
@Get('docs')
@Redirect('<https://docs.nestjs.com>', 302)
getDocs(@Query('version') version) {
if (version && version === '5') {
return { url: '<https://docs.nestjs.com/v5/>' };
}
}
Route Parameters
요청으로 만약 위에서 생성한 GET /cats 라우트에 id 가 1인 고양이를 가져오는 상황을 가정해 보자. 이 상황에서 정적 경로를 가진 라우트는 동작하지 않는다. 즉, 매개변수가 있는 라우트를 정의하려면, 라우트 경로에 라우트 매개변수 토큰을 추가하여 요청 URL의 해당 위치에 있는 동적 값을 가져올 수 있다.
@Get(':id")
findOne(@Param() params: any): string{
return `This action returns a ${params.id} cat`;
}
특정 parameter 토큰을 데코레이터에 전달하여 메서드 본문에서 라우트 매개변수를 직접 이름으로 참조할 수도 있다.
@Get(':id")
findOne(@Param('id') params: any): string{
return `This action returns a ${id} cat`;
}
Subdomain Routing
@Controller() 는 들어오는 요청의 HTTP 호스트가 특정값과 일치하도록 요구하는 host 옵션을 사용할 수 있다.
@Controller({ host: 'admin.example.com' })
export class AdminController {
@Get()
index(): string {
return 'Admin page';
}
}
라우트 경로와 유사하게 host 옵션은 토큰을 사용하여 호스트 이름의 해당 위치에 있는 동적 값을 캡쳐할 수 있다.
@Controller({ host: ':account.example.com' })
export class AccountController {
@Get()
getInfo(@HostParam('account') account: string) {
return account;
}
}
Scope
Nest 에서는 거의 모든 것이 들어오는 요청간에 공유된다. DB 에 대한 연결 풀, 전역 상태를 가진 싱글톤 서비스 등이 있다. Node.js 는 각 요청이 별도의 스레드에 의해 처리되는 요청/응답 다중 스레드 무상태 모델을 따르지 않기 때문에, 싱글톤 서비스를 사용하는것이 safe 할 수 있다.
Asynchronicity
Nest 는 당연하게도 비동기성을 지원한다. Async 함수의 정의에 따라, 모든 비동기 함수는 Promise를 반환해야 한다.
위의 특성으로 인해, Nest는 지연된 값 을 자동으로 처리할 수 있다는 의미이다. NestJS 핸들러에서 비동기 함수를 사요할 때, Promise를 반환할 수 있는데, 이 Promise 가 resolve 상태가 될 때 까지 NestJS가 자동으로 기다려주며, 완료된 값을 클라이언트에게 응답으로 반환한다는 뜻이다.
@Get()
async findAll(): Promise<any[]>{
return [];
}
해당 코드의 경우, Promise<any[]> 를 반환하며, 빈 배열이 지연된 값 으로 처리된다. 즉, Nest 가 자동으로 처리해 주기 때문에, 개발자가 명시적으로 응답을 관리할 필요가 없다는 뜻이다.
Observable의 경우도, NestJS는 이 스트림을 자동으로 subscribe하고, Observable 이 방출하는 최종값을 사용하여 응답을 보낸다. 스트림이 완료되기 전 까지는 기다렸다가, 완료된 후 마지막 값을 클라이언트에게 반환한다.
@Get()
findAll(): Observable<any[]> {
return of([]);
}
위 예시는 RxJS 의 of([]) 로 Observable 을 생성하여 빈 배열을 방출하는데, NestJS는 해당 Observable 을 subscribe 하고 마지막 값인 빈 배열을 클라이언트에게 응답으로 보낸다.
Observable 이란
비동기 데이터 스트림을 처리하는 RxJS의 핵심 개념이다. Observable 은 데이터 흐름을 관리하며, 여러개의 값을 비 동기적으로 방출할 수 있다. 이는 데이터가 준비될 때마다 값을 내보내며, 데이터를 구독한 구독자는 이 데이터 들을 받아 처리할 수 있다.
Request Payloads
이전 POST 라우트 예제는 매개변수를 받지 않았다. 다음은 매개변수를 받는 상황에 대한 설명이다.
우선, DTO 스키마를 정의해야 한다.
export class CreateCatDto{
name : string;
age : number;
breed : string;
}
이제 해당 Dto를 CatsController에서 사용할 수 있다.
@Post()
async create(@Body() createCatDto : CreateCatDto){
return 'This action adds a new cat';
}
NestJS에서 @Body() 데코레이터를 사용할 경우, 자동으로 Request Body 데이터를 DTO로 메핑할 수 있다. 이는 NestJS 에 내장된 파라미터 데코레이터 기능 덕분이다.
클라이언트가 다음과 같은 JSON 을 POST 요청을 통해 보냈을 경우,
{
"name": "Kitty",
"age": 2,
"breed": "Persian"
}
@Body() 데코레이터는 이 요청 본문을 읽어들여, CreateCatDto 타입의 객체로 자동으로 변환한다.
'NestJS' 카테고리의 다른 글
NestJS Document 알아보기 : Exception Filters (0) | 2024.10.20 |
---|---|
NestJS Document 알아보기 : Middleware (0) | 2024.10.20 |
NestJS Document 알아보기 : Module (3) | 2024.10.20 |
NestJS Document 알아보기 : Providers (1) | 2024.10.19 |