Controller, Interface

Kim sejin·2023년 12월 5일

NestJs

목록 보기
4/4
post-thumbnail

컨트롤러

Nest의 컨트롤러는 MVC 패턴에서 말하는 컨트롤러를 의미한다. 컨트롤러는 들어오는 요청(request)를 받고 처리된 결과를 응답(response)으로 돌려주는 인터페이스 역할을 한다.

컨트롤러는 엔드포인트 라우팅 매커니즘을 통해 각 컨트롤러가 받을 수 있는 요청을 분류한다.


Controller 생성 방법

nest g controller [이름]
nest g resource [name] // CRUD 보일러플레이트 생성


와일드카드 사용

라우팅 패스는 와일드 카드를 이용하여 작성할 수 있다. (*) 문자를 사용하면 문자열 가운데 어떤 문자가 와도 상관없이 라우팅 패스를 구성할 수 있다.

@Get('he*lo)
getHello(): string{
	return this.appService.getHello();
}	

이 코드는 helo, hello, he__lo 같은 경로로 요청 받을 수 있다. * 이외에 ?, +, () 문자 역시 정규 표현식에서의 와일드 카드와 동일하게 동작한다. 하지만 -, . 은 문자열 취급을 받기에 요청할 수 있다.

와일드 카드는 컨트롤러의 패스를 정할 때만 사용하는 것이 아니라, 컴포넌트에서 이름을 정할 때도 사용할 수 있다.


요청 객체

클라이언트는 요청을 보내면 종종 서버가 원하는 정보를 함께 전송한다. Nest는 요청과 함께 전달되는 데이터를 핸들러가 다룰 수 있는 객체로 변환한다. 이 변환된 객체는 @Req 데커레이터를 이용해 다룰 수 있다.

@Get()
getHello(@Req() req: Request): string{
	console.log(req);
    return this.appService.getHello();
}

요청 객체는 보통 HTTP 객체를 나타낸다. 요청 객체(req)가 어떻게 구성되어 있는지 console에 출력하면 쿼리스트링, 매개변수, 헤더 등 다양한 정보를 가지고 있다.

이 외에도 @Query(), @Param(), @Body() 데커레이터가 존재한다.


응답

각 요청의 성공 응답 코드는 POST일 경우엔 201이고, 나머지는 200인 것을 확인할 수 있다. NestJs에서는 string, number, boolean과 같이 원시 타입을 리턴을 경우 직렬화 없이 보내지만, 객체를 리턴한다면 직렬화를 통해 json으로 자동 변환해준다.


Res()

@Get()
findAll(@Res() res) {
	const users = userService.findAll()
    return res.status(200).send(users);
}

위 코드처럼 @Res 데커레이터를 사용하여 Express의 응답 객체를 다룰 수 있다.

PUT과 PATCH의 차이점?
PUT은 전체 리소스를 교환할 때 사용되고, PATCH는 리소스 일부를 업데이트할 때 사용한다.


HttpCode

만약 200, 201처럼 상태코드를 다른 값으로 바꾸길 원한다면 어떻게 해야할까 이를 위해서 Nest에서는 또 다른 데커레이터 @HttpCode를 마련해놓았다. 밑의 예시를 보자.

@HttpCode(202)
@Patch(':id')
update(@Param('id') id: string, @Body() updateUserDto: UpdateUserDto) {
 	return this.usersService.update(+id, updateUserDto); 
}

Exception

만약에 요청을 처리하던 중 에러가 발생하거나 예외를 던져야 한다면 어떻게 해야할까 예를 들어 유저가 정보 조회를 요청하였는데 id는 1부터 시작해야한다. id가 1보다 작은 값일 경우 400 Bad Request 예외를 던져야한다. 밑의 예제를 보자

@Get(':id')
findOne(@Param('id') id: string){
	if( +id < 1){
    	throw new BadRequestException("id는 0보다 큰 값이어야 합니다.");
    }
   return this.usersService.findOne(+id);
}

id를 0으로 요청하면

{
	"statusCode" : 400,
    "message" : "id는 0보다 큰 값이어야 합니다.",
    "error": "Bad Request"
}

NotFoundException 객체의 생성자로 전달한 메세지와 함께 상태 코드가 400인 에러가 발생한다.

헤더

응답이 커스텀 헤더를 추가하고 싶다면 @Header 데커레이터를 사용하면 된다. 인수로 이름과 값을 받는다. 물론 라이브러리에서 제공하는 응답 객체를 사용해서 res.header()로 직접 설정도 가능하다.

@Header('Custom', 'Test Header')
@Get(':id')
findOneWithHeader(@Param('id') id: string) {
	return this.usersService.findOne(+id);
}

리디렉션

서버가 요청을 처리한 후, 요청을 보낸 클라이언트를 다른 페이지로 이동하고 싶은 경우가 있다. 이를 리디렉션이라 한다.

@Redirect('https://nestjs.com', 301)
@Get(':id')
findOne(@Param('id') id: string) {
 	return this.usersService.fineOne(+id) 
}

(위 코드는 findOne 메서드가 동작하지 않고, GET(':id')로 요청을 보내면 클라이언트가 https://nestjs.com으로 리디렉션이 된다.)


요청 처리 결과에 따라 동적으로 리디렉트하고자 한다면 응답으로 다음과 같은 객체를 리턴하면 된다.

{
	"url": string,
    "statusCode": number
}

예를 들어 쿼리 매개변수로 버전 숫자를 전달받아 해당 버전의 페이지로 이동한다.

@Get('redirect/docs')
@Redirect('https://docs.nestjs.com', 302)
getDocs(@Query('version') version){
	if (version && version == '5'){
      	return { url: https://docs.nestjs.com/v5/' };
    }	
}

Nest는 자바스크립트 객체를 리턴하면 JSON String으로 보내게 된다.

이제 브라우저에서 http://localhost:3000/redirect/docs?version=5을 입력하면 https://docs.nestjs.com/v5로 이동한다.

(version이 5가 아닌 경우, 쿼리 파라미터가 주어지지 않은 경우 https://docs.nestjs.com로 리다이렉션된다.)

라우트 매개변수

라우트(라우팅) 매개변수는 패스 매개변수라고도 합니다. 라우트 매개변수를 전달받는 방법은 2가지가 있다. 먼저 매개변수가 여러 개 전달된 경우로 한번에 받는 방법이다.

@Delete(':userId/memo/:memoId')
deleteUserMemo(@Param() params: {[key: string]: string}){
	return 'userId: ${params.userId}, memoId: ${params.memoId}
}

다른 방법으로는 라우팅 매개변수를 따로 받는 것이다. REST API를 구성할 때는 라우팅 매개변수의 개수가 많아지지 않게 설계하는 것이 좋다.

@Delete(':userId/memo/:memoId')
deleteUserMemo(@Param('userId') userId: string, @Param('memoId' memoId: string){
	return 'userId: ${userId}, memoId: ${memoId}
}

하위 도메인 라우팅

실제로 회사가 사용하고 있는 도메인은 example.com이고, API 요청은 api.example.com으로 받기로 하였다. 즉, http://example.com, http://api.example.com로 들어온 요청을 서로 다르게 처리하고 싶다.

또한 하위 도메인에서 처리하지 못하는 요청은 원래의 도메인에서 처리하고 싶다. 이런 경우 하위 도메인 라우팅 기법을 쓸 수 없다.

먼저 컨트롤러를 생성한다.

nest g co Api

@Controller({host: 'api.example.com'})
export class ApiControll{
	@Get()
    index(): string{
    	return 'Hello, API'
	}

}

위의 코드는 host가 api.example.com으로 요청이 들어올 때만 해당 메서드를 실행할 수 있다.

페이로드

POST, PUT, PATCH 요청은 보통 처리에 필요한 데이터를 실어 보낸다. 이 데이터 덩어리, 즉 Payload를 Body라고 한다. NestJS에는 데이터 전송 객체(DTO)가 구현되어 있어 본문을 쉽게 다룰 수 있다.

export class CreateUserDto{
	name: string;
    email: string;
}

@Post()
create(@Body() createUserDto: CreateUserDto){
	const { name, email } = createUserDto;
    
    
    return '유저를 생성했습니다. 이름 ${name}, 이메일: ${email}';

}

이제 유저 생성 API를 요청하고, 본문에 데이터가 잘 들어가있는지 확인해보자

실행결과는 유저를 생성했습니다. 이름: YOUR_NAME, 이메일: YOUR_EMAIL@gmail.com로 출력된다.

지금까지는 백엔드의 애플리케이션의 관문이라 할 수 있는 컨트롤러를 Nest에서 어떻게 사용하고 있는지 살펴보았다. 컨트롤러는 서버로부터 들어오는 요청을 처리하고 응답을 가공하는 것이다. 서버에서 제공하는 기능을 어떻게 클라이언트와 주고받을지에 대한 인터페이스를 정의하고 데이터 구조를 기술한다.

유저 서비스의 인터페이스

@Controller('user')
export class UsersController {
	@Post()
    async createUser(@Body() dto: CreateUserDto): Promise<void> { // 1
    	console.log(dto);
        return;
    }
    
    @Post('/email-verity)
    async verifyEmail(@Query() dto: VerifyEmailDto): Promise<string> { // 2
    	console.log(dto);
        return;
    }
	
    @Post('/login')
    async getUserInfo(@Param('id' userId: string): Promise<UserInfo> { // 3
    	console.log(userId);
        return;
    }

}

1번에서 이메일 인증 시 URL에 포함되는 쿼리 매개변수를 @Query 데커레이터와 함께 선언한 DTO로 받는다.

export class VerifyEmailDto{
	signupVerifyToken: string;
}

2번은 로그인을 할 때 유저가 입력한 데이터는 본문으로 전달되도록 한다.

export class UserLoginDto {
 	email: string;
  	password: string;
}

3번은 유저 정보 조회 시 유저 아이디를 패스 매개변수 id로 받는다.

export interface UserInfo {
  id: string;
  name: string;
  email: string;
}

출처

이 포스팅은 한용재 님의 [Nestjs로 부르는 백엔드 프로그래밍]을 참조하여 작성하였습니다.

0개의 댓글