5. nestjs homepage https://nestjs.com
최종 업데이트 : 2025-01-26
src/
├──app,controller.spec.ts
├──app.controller.ts
├──app.module.ts
├──app.service.ts
└──main.ts
nestjs는 typescript기반입니다.
svelte와 react보다 우선 설명하는 이유입니다.
폴더구조를 보시면 pynest의 구조와 비슷함을 알수있습니다.
await app.listen(process.env.PORT ?? 8000);
기본적으로 3000포트로 되어있는데
다른 api와 front와의 연결을 위해 8000port로 설정을 변경해줍니다.
@nestjs/swagger
main.ts
import { SwaggerModule, DocumentBuilder } from '@nestjs/swagger';
const config = new DocumentBuilder()
.setTitle('Cats example')
.setDescription('The cats API description')
.setVersion('1.0')
.addTag('cats')
.build();
const documentFactory = () => SwaggerModule.createDocument(app, config);
SwaggerModule.setup('api', app, documentFactory);
nestjs는 기본적으로 스웨거를 지원하지 않기떄문에 스웨거 설정을 하고
실행하여 확인해봅니다.
http://localhost:8000/api
nest g resource signup
src/
└── signup/
├── dto/
├── ├── create-signup.dto.ts
├── └── update-signup.dto.ts
├── entities
├── └── signup.entity.ts
├── signup.controller.spec.ts
├── signup.controller.ts
├── singup.module.ts
├── signup.service.spec.ts
└── signup.service.ts
signup resource를 추가해줍니다.
pynest보단 조금 더 복잡한 구조가 보입니다.
database를 연결하지 않았기에 pynest가 좀 심플해져있는것이고
구조는 최대한 맞춰서 진행할예정입니다.
현 예제에서는 데이터베이스는 사용하지 않습니다.
signup.module.ts
import { Module } from '@nestjs/common';
import { SignupService } from './signup.service';
import { SignupController } from './signup.controller';
@Module({
controllers: [SignupController],
providers: [SignupService],
})
export class SignupModule {}
app.module.ts
imports: [SignupModule],
signup.module.ts도 pynest와 같은 구조를 보임을 알 수 있습니다.
app.module.ts에서도 SignupModule을 import함을 확인가능합니다.
dto/create-signup.dto.ts
import { ApiProperty } from '@nestjs/swagger';
import { IsNotEmpty, IsString, MaxLength, MinLength } from 'class-validator';
export class CreateSignupDto {
@ApiProperty({
description: '사용자 ID',
maxLength: 20,
})
@IsString()
@IsNotEmpty()
@MaxLength(20)
id: string;
@ApiProperty({
description: '비밀번호',
minLength: 8,
})
@IsString()
@IsNotEmpty()
@MinLength(8)
password: string;
}
python측의 model로 이해하시면좋습니다.
python의 경우 pydantic을 이용하기때문에 이를 스웨거에서 자동화합니다.
데이터베이스를 고려하여 작성된 nestjs를 확인해봅니다.
python도 orm등을 사용하면 해당 코드를 작성하는 부분을 추후 보여드리겠습니다.
import { ApiProperty } from '@nestjs/swagger';을 선언하고
@ApiProperty을 선언하여 스웨거에 보일 부분을 작성합니다.
데이터베이스에 생성될 컬럼이라 생각하고 id와 password의 속성을 지정합니다.
response/success-response.ts
import { Expose } from 'class-transformer';
export class SuccessResponse {
@Expose({ name: 'status_code' })
statusCode: number;
content: string;
constructor(statusCode: number, content: string) {
this.statusCode = statusCode;
this.content = content;
}
}
response를 만들고 success-response.ts파일을 추가합니다.
언어마다 카멜케이스 등 기본적으로 사용되는 규칙이 있습니다.
반환되는 규칙이 다르면 프론트쪽에서 해당부분을 맞춰야하기때문에
class-transformer을 이용하여 카멜케이스를 스네이크케이스로 반환하도록 설정합니다.
src/
└── exception
├── custom.exception.ts
├── exception-response.ts
└── http-exception.filter.ts
custom.exception.ts
import { HttpException, HttpStatus } from '@nestjs/common';
import { ExceptionResponse } from './exception-response';
export class CustomException extends HttpException {
constructor(statusCode: HttpStatus, content: string) {
const response = new ExceptionResponse(statusCode, content);
super(response, statusCode);
}
}
exception-response.ts
import { Expose } from 'class-transformer';
export class ExceptionResponse {
@Expose({ name: 'status_code' })
statusCode: number;
content: string;
constructor(statusCode: number, content: string) {
this.statusCode = statusCode;
this.content = content;
}
}
http-exception.filter.ts
import { ExceptionFilter, Catch, ArgumentsHost, HttpException } from '@nestjs/common';
import { Response } from 'express';
import { instanceToPlain } from 'class-transformer';
import { ExceptionResponse } from './exception-response';
@Catch(HttpException)
export class HttpExceptionFilter implements ExceptionFilter {
catch(exception: HttpException, host: ArgumentsHost) {
const ctx = host.switchToHttp();
const response = ctx.getResponse<Response>();
const status = exception.getStatus();
const exceptionResponse = exception.getResponse();
const errorResponse = exceptionResponse instanceof ExceptionResponse
? exceptionResponse
: new ExceptionResponse(
status,
typeof exceptionResponse === 'string'
? exceptionResponse
: (exceptionResponse as any).content || '오류가 발생했습니다.'
);
response
.status(status)
.json(instanceToPlain(errorResponse));
}
}
예외관련모듈을 만들어줍니다.
python에서는 exception_handler를 따로 설정하지 않았는데
이런식으로 하나씩 배우면서 java까지 진행한 후
2번째 학습부터 조금씩 구조화를 시켜보겠습니다.
src/signup/signup.service.ts
private usersDb: Record<string, string> = {};
signup(data: CreateSignupDto): { statusCode: number; content: string } {
const { id, password } = data;
if (this.usersDb[id]) {
throw new CustomException(
HttpStatus.CONFLICT,
'이미 사용 중인 ID입니다.'
);
}
const hashedPassword = crypto.createHash('sha256').update(password).digest('hex');
this.usersDb[id] = hashedPassword;
const response = new SuccessResponse(HttpStatus.CREATED, '회원가입 성공');
return response
}
private usersDb: Record<string, string> = {};
파이썬에서 사용되었던 딕셔너리를 만들어줍니다.
타입스크립트로는 레코드타입이라고도 합니다.
signup(data: CreateSignupDto): { statusCode: number; content: string }
function 함수이름(매개변수: 매개변수의 타입): 반환 타입
이처럼 타입스크립트를 사용하면 jsdocs처럼 별도주석을 달지 않아도 의미 표현이 가능합니다.
나머진 기술적으로 크게 어렵지 않습니다.
넘어오는 id와 password를 받아서 레코드타입에 있는지 확인하고
패스워드암호화를 진행한 후 레코드타입에 데이터를 넣고 성공메세지를 보내줍니다.
src/signup/signup.controller.ts
constructor(private readonly signupService: SignupService) {}
@Post()
@ApiOperation({ summary: '회원가입 요청' })
signup(@Body() createSignupDto: CreateSignupDto): SuccessResponse {
return this.signupService.signup(createSignupDto);
}
constructor(private readonly signupService: SignupService) {}
__init__처럼 constructor을 이용하여 서비스를 초기화하고
@post() 로 Post 메소드를 선언하고
@ApiOperation로 스웨거에 어떤 API인지 알려주고
@Body를 CreateSignupDto로 정의하여
this.signupService.signup(createSignupDto)를 사용하여
SuccessResponse 형태로 반환해줍니다.
fastapi, pynest를 거쳐서 설명하였음으로 크게 어려운건 없을거라 생각합니다.
언어마다 문법이 다른거지 어떤걸 할지 그것을 하려면 뭘해야할지 안다면 대동소이합니다.
타입스크립트의 맛을 보았으니 svelte와 react를 학습 한 후에
java spring까지 살펴보고 난 다음 학습내용 준비가 끝나면 반복해서 해보겠습니다.