출시·고도화 중
NestJS · Express 안내서 · 4/6
서버 애플리케이션의 데이터는 요청 본문으로 들어와 검증을 거치고, 데이터베이스에 저장됩니다. 이 장에서는 Nest의 ValidationPipe와 class-validator로 입력을 검증하고, 설정 모듈로 환경 변수를 읽고, ORM으로 데이터베이스를 다루는 방법을 살펴봅니다.
Nest의 프로바이더는 기본적으로 애플리케이션 전체에서 인스턴스 하나를 공유합니다. 따라서 요청마다 달라지는 값을 서비스의 필드에 저장하면 다른 사용자의 요청과 섞일 수 있습니다. 요청 사이에 유지해야 하는 상태는 데이터베이스나 Redis 같은 외부 저장소에 두고, 서버 프로세스는 상태를 갖지 않게 만드는 것이 좋습니다. 그래야 인스턴스를 여러 개 띄워도 같은 결과를 냅니다.
요청 본문의 모양은 DTO(데이터 전송 객체) 클래스로 정의하고, class-validator의 데코레이터로 규칙을 붙입니다. 먼저 패키지를 설치합니다.
npm i class-validator class-transformer// src/users/dto/create-user.dto.ts
import { IsEmail, IsInt, IsOptional, IsString, Length, Min } from "class-validator";
export class CreateUserDto {
@IsString()
@Length(2, 30)
name: string;
@IsEmail()
email: string;
@IsOptional()
@IsInt()
@Min(0)
age?: number;
}main.ts에서 ValidationPipe를 전역으로 등록하면, @Body()로 받는 모든 DTO가 자동으로 검증되고 규칙을 어기면 400 응답과 함께 오류 목록이 돌아갑니다.
// src/main.ts
import { ValidationPipe } from "@nestjs/common";
import { NestFactory } from "@nestjs/core";
import { AppModule } from "./app.module";
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.useGlobalPipes(
new ValidationPipe({
whitelist: true, // DTO에 없는 속성은 제거
forbidNonWhitelisted: true, // 없는 속성이 있으면 400
transform: true, // 평범한 객체를 DTO 인스턴스로 변환
}),
);
await app.listen(process.env.PORT ?? 3000);
}
bootstrap();컨트롤러에서는 create(@Body() dto: CreateUserDto)처럼 타입만 지정하면 됩니다. 수정용 DTO는 @nestjs/mapped-types의 PartialType(CreateUserDto)로 모든 속성을 선택 사항으로 바꿔 만들 수 있습니다. nest g resource가 이 방식으로 UpdateUserDto를 만들어 줍니다.
데이터베이스 주소나 비밀 값은 코드에 적지 않고 환경 변수로 받습니다. @nestjs/config의 ConfigModule은 .env 파일과 실제 환경 변수를 읽어 ConfigService로 제공합니다.
npm i @nestjs/config루트 모듈에 ConfigModule.forRoot({ isGlobal: true })를 등록하면(아래 TypeORM 예제 참고) 다른 모듈에서 따로 가져오지 않고도 constructor(private config: ConfigService)로 주입받아 this.config.get<string>("DATABASE_URL")처럼 읽을 수 있습니다. 실제 환경 변수가 .env의 값보다 우선하며, .env 파일은 저장소에 올리지 않습니다.
Nest는 TypeORM용 공식 통합 패키지 @nestjs/typeorm을 제공합니다. PostgreSQL을 쓴다면 드라이버와 함께 설치합니다.
npm i @nestjs/typeorm typeorm pg엔티티 클래스로 테이블을 정의하고, 루트 모듈에서 연결을, 기능 모듈에서 쓸 엔티티를 등록합니다.
// src/users/entities/user.entity.ts
import { Column, Entity, PrimaryGeneratedColumn } from "typeorm";
@Entity()
export class User {
@PrimaryGeneratedColumn()
id: number;
@Column()
name: string;
@Column({ unique: true })
email: string;
}// src/app.module.ts
import { Module } from "@nestjs/common";
import { ConfigModule, ConfigService } from "@nestjs/config";
import { TypeOrmModule } from "@nestjs/typeorm";
import { UsersModule } from "./users/users.module";
@Module({
imports: [
ConfigModule.forRoot({ isGlobal: true }),
TypeOrmModule.forRootAsync({
inject: [ConfigService],
useFactory: (config: ConfigService) => ({
type: "postgres",
url: config.get<string>("DATABASE_URL"),
autoLoadEntities: true,
synchronize: false, // 운영에서는 마이그레이션 사용
}),
}),
UsersModule,
],
})
export class AppModule {}UsersModule의 imports에는 TypeOrmModule.forFeature([User])를 넣어 이 모듈에서 쓸 엔티티를 등록합니다. 서비스에서는 @InjectRepository로 저장소(Repository)를 주입받아 씁니다.
import { Injectable } from "@nestjs/common";
import { InjectRepository } from "@nestjs/typeorm";
import { Repository } from "typeorm";
import { CreateUserDto } from "./dto/create-user.dto";
import { User } from "./entities/user.entity";
@Injectable()
export class UsersService {
constructor(@InjectRepository(User) private readonly users: Repository<User>) {}
findAll() {
return this.users.find();
}
create(dto: CreateUserDto) {
return this.users.save(this.users.create(dto));
}
}synchronize: true는 엔티티에 맞춰 스키마를 자동으로 바꾸므로 개발 초기에는 편하지만, 데이터가 사라질 수 있어 운영에서는 끄고 TypeORM 마이그레이션으로 스키마를 관리합니다.
Prisma는 schema.prisma 파일에 모델을 적고, 그로부터 타입이 붙은 클라이언트를 생성하는 ORM입니다. npm i -D prisma와 npx prisma init으로 시작하고, npx prisma migrate dev로 마이그레이션을 만들어 적용합니다. Nest용 공식 모듈은 없으므로, 생성된 PrismaClient를 감싸는 PrismaService 프로바이더를 만들어 onModuleInit에서 연결하고 다른 서비스에 주입하는 방식을 씁니다. 클라이언트 생성 위치와 설정은 Prisma 버전에 따라 달라지므로 Prisma 문서의 NestJS 안내를 함께 확인합니다.
Express는 검증이나 데이터베이스 계층을 제공하지 않습니다. express.json()으로 본문을 읽은 뒤 Zod나 Joi 같은 검증 라이브러리로 직접 검사하고, 데이터베이스는 ORM이나 드라이버를 모듈로 만들어 라우터에서 가져다 씁니다. Nest가 모듈과 의존성 주입으로 정리해 주는 부분을 직접 설계한다고 보면 됩니다.
ValidationPipe로 자동 검증합니다.ConfigModule과 ConfigService로 환경 변수를 읽습니다.@nestjs/typeorm으로 연결하고 저장소를 주입받아 쓰며, Prisma는 직접 만든 PrismaService로 연결합니다.
댓글 0개
로그인 · 로그인하면 댓글을 남길 수 있습니다.
첫 댓글을 남겨 보세요.