728x90
반응형
1. NestJS란
NestJS는 TypeScript로 작성된 프로그레시브 Node.js 프레임워크입니다. Angular에서 영감을 받은 아키텍처로, 데코레이터, 의존성 주입, 모듈 시스템을 사용합니다. Express 또는 Fastify를 기반으로 동작합니다.
npm install -g @nestjs/cli
nest new my-project
2. 기본 구조
2.1 모듈 (Module)
// app.module.ts
import { Module } from '@nestjs/common';
import { UsersModule } from './users/users.module';
import { PostsModule } from './posts/posts.module';
@Module({
imports: [UsersModule, PostsModule],
controllers: [],
providers: [],
})
export class AppModule {}
2.2 컨트롤러 (Controller)
// users/users.controller.ts
import {
Controller,
Get,
Post,
Put,
Delete,
Body,
Param,
Query,
HttpStatus,
HttpCode,
} from '@nestjs/common';
import { UsersService } from './users.service';
import { CreateUserDto } from './dto/create-user.dto';
@Controller('users')
export class UsersController {
constructor(private readonly usersService: UsersService) {}
@Get()
findAll(@Query('page') page: number = 1) {
return this.usersService.findAll(page);
}
@Get(':id')
findOne(@Param('id') id: string) {
return this.usersService.findOne(+id);
}
@Post()
@HttpCode(HttpStatus.CREATED)
create(@Body() createUserDto: CreateUserDto) {
return this.usersService.create(createUserDto);
}
@Put(':id')
update(@Param('id') id: string, @Body() updateUserDto: UpdateUserDto) {
return this.usersService.update(+id, updateUserDto);
}
@Delete(':id')
@HttpCode(HttpStatus.NO_CONTENT)
remove(@Param('id') id: string) {
return this.usersService.remove(+id);
}
}
2.3 서비스 (Service)
// users/users.service.ts
import { Injectable, NotFoundException } from '@nestjs/common';
import { CreateUserDto } from './dto/create-user.dto';
@Injectable()
export class UsersService {
private users = new Map<number, User>();
private nextId = 1;
findAll(page: number) {
return Array.from(this.users.values());
}
findOne(id: number) {
const user = this.users.get(id);
if (!user) {
throw new NotFoundException(`User ${id} not found`);
}
return user;
}
create(createUserDto: CreateUserDto) {
const user = {
id: this.nextId++,
...createUserDto,
createdAt: new Date(),
};
this.users.set(user.id, user);
return user;
}
update(id: number, updateUserDto: UpdateUserDto) {
const user = this.findOne(id);
const updated = { ...user, ...updateUserDto };
this.users.set(id, updated);
return updated;
}
remove(id: number) {
this.findOne(id);
this.users.delete(id);
}
}
2.4 DTO (Data Transfer Object)
// users/dto/create-user.dto.ts
import { IsString, IsEmail, MinLength, IsOptional } from 'class-validator';
export class CreateUserDto {
@IsString()
@MinLength(2)
name: string;
@IsEmail()
email: string;
@IsString()
@IsOptional()
phone?: string;
}
// users/dto/update-user.dto.ts
import { PartialType } from '@nestjs/mapped-types';
import { CreateUserDto } from './create-user.dto';
export class UpdateUserDto extends PartialType(CreateUserDto) {}
3. 의존성 주입
// database.service.ts
import { Injectable } from '@nestjs/common';
@Injectable()
export class DatabaseService {
private connection: any;
async connect() {
// 데이터베이스 연결
}
async query(sql: string) {
// 쿼리 실행
}
}
// users.service.ts
import { Injectable } from '@nestjs/common';
import { DatabaseService } from '../database/database.service';
@Injectable()
export class UsersService {
constructor(private readonly db: DatabaseService) {}
async findAll() {
return this.db.query('SELECT * FROM users');
}
}
// users.module.ts
import { Module } from '@nestjs/common';
import { UsersController } from './users.controller';
import { UsersService } from './users.service';
import { DatabaseModule } from '../database/database.module';
@Module({
imports: [DatabaseModule],
controllers: [UsersController],
providers: [UsersService],
exports: [UsersService],
})
export class UsersModule {}
반응형
4. 미들웨어
// logger.middleware.ts
import { Injectable, NestMiddleware } from '@nestjs/common';
import { Request, Response, NextFunction } from 'express';
@Injectable()
export class LoggerMiddleware implements NestMiddleware {
use(req: Request, res: Response, next: NextFunction) {
const start = Date.now();
res.on('finish', () => {
const duration = Date.now() - start;
console.log(`${req.method} ${req.url} ${res.statusCode} - ${duration}ms`);
});
next();
}
}
// app.module.ts
import { Module, NestModule, MiddlewareConsumer } from '@nestjs/common';
@Module({
imports: [UsersModule],
})
export class AppModule implements NestModule {
configure(consumer: MiddlewareConsumer) {
consumer
.apply(LoggerMiddleware)
.forRoutes('*');
}
}
5. 가드 (Guards)
// auth.guard.ts
import {
Injectable,
CanActivate,
ExecutionContext,
UnauthorizedException,
} from '@nestjs/common';
import { JwtService } from '@nestjs/jwt';
@Injectable()
export class AuthGuard implements CanActivate {
constructor(private jwtService: JwtService) {}
async canActivate(context: ExecutionContext): Promise<boolean> {
const request = context.switchToHttp().getRequest();
const token = this.extractToken(request);
if (!token) {
throw new UnauthorizedException();
}
try {
const payload = await this.jwtService.verifyAsync(token);
request.user = payload;
} catch {
throw new UnauthorizedException();
}
return true;
}
private extractToken(request: any): string | undefined {
const [type, token] = request.headers.authorization?.split(' ') ?? [];
return type === 'Bearer' ? token : undefined;
}
}
// 사용
@Controller('users')
@UseGuards(AuthGuard)
export class UsersController {
@Get('profile')
getProfile(@Request() req) {
return req.user;
}
}
6. 파이프 (Pipes)
// 내장 파이프 사용
import { Controller, Get, Param, ParseIntPipe, Query, DefaultValuePipe } from '@nestjs/common';
@Controller('users')
export class UsersController {
@Get(':id')
findOne(@Param('id', ParseIntPipe) id: number) {
return this.usersService.findOne(id);
}
@Get()
findAll(
@Query('page', new DefaultValuePipe(1), ParseIntPipe) page: number,
@Query('limit', new DefaultValuePipe(10), ParseIntPipe) limit: number,
) {
return this.usersService.findAll(page, limit);
}
}
// 커스텀 파이프
import { PipeTransform, Injectable, BadRequestException } from '@nestjs/common';
@Injectable()
export class ParseDatePipe implements PipeTransform<string, Date> {
transform(value: string): Date {
const date = new Date(value);
if (isNaN(date.getTime())) {
throw new BadRequestException('Invalid date format');
}
return date;
}
}
7. 인터셉터 (Interceptors)
// logging.interceptor.ts
import {
Injectable,
NestInterceptor,
ExecutionContext,
CallHandler,
} from '@nestjs/common';
import { Observable } from 'rxjs';
import { tap } from 'rxjs/operators';
@Injectable()
export class LoggingInterceptor implements NestInterceptor {
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
const now = Date.now();
const request = context.switchToHttp().getRequest();
return next.handle().pipe(
tap(() => {
console.log(`${request.method} ${request.url} - ${Date.now() - now}ms`);
}),
);
}
}
// 응답 변환 인터셉터
@Injectable()
export class TransformInterceptor<T> implements NestInterceptor<T, Response<T>> {
intercept(context: ExecutionContext, next: CallHandler): Observable<Response<T>> {
return next.handle().pipe(
map((data) => ({
data,
timestamp: new Date().toISOString(),
statusCode: context.switchToHttp().getResponse().statusCode,
})),
);
}
}
8. 예외 필터
// http-exception.filter.ts
import {
ExceptionFilter,
Catch,
ArgumentsHost,
HttpException,
HttpStatus,
} from '@nestjs/common';
@Catch()
export class AllExceptionsFilter implements ExceptionFilter {
catch(exception: unknown, host: ArgumentsHost) {
const ctx = host.switchToHttp();
const response = ctx.getResponse();
const request = ctx.getRequest();
const status =
exception instanceof HttpException
? exception.getStatus()
: HttpStatus.INTERNAL_SERVER_ERROR;
const message =
exception instanceof HttpException
? exception.message
: 'Internal server error';
response.status(status).json({
statusCode: status,
message,
timestamp: new Date().toISOString(),
path: request.url,
});
}
}
// 전역 적용
// main.ts
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.useGlobalFilters(new AllExceptionsFilter());
await app.listen(3000);
}
9. 데이터베이스 연동 (TypeORM)
npm install @nestjs/typeorm typeorm pg
// user.entity.ts
import { Entity, Column, PrimaryGeneratedColumn, CreateDateColumn } from 'typeorm';
@Entity()
export class User {
@PrimaryGeneratedColumn()
id: number;
@Column()
name: string;
@Column({ unique: true })
email: string;
@CreateDateColumn()
createdAt: Date;
}
// app.module.ts
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { User } from './users/user.entity';
@Module({
imports: [
TypeOrmModule.forRoot({
type: 'postgres',
host: 'localhost',
port: 5432,
username: 'user',
password: 'password',
database: 'mydb',
entities: [User],
synchronize: true, // 개발용만
}),
TypeOrmModule.forFeature([User]),
],
})
export class AppModule {}
// users.service.ts
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { User } from './user.entity';
@Injectable()
export class UsersService {
constructor(
@InjectRepository(User)
private usersRepository: Repository<User>,
) {}
findAll(): Promise<User[]> {
return this.usersRepository.find();
}
findOne(id: number): Promise<User> {
return this.usersRepository.findOneBy({ id });
}
create(createUserDto: CreateUserDto): Promise<User> {
const user = this.usersRepository.create(createUserDto);
return this.usersRepository.save(user);
}
async remove(id: number): Promise<void> {
await this.usersRepository.delete(id);
}
}
10. 설정 관리
npm install @nestjs/config
// app.module.ts
import { Module } from '@nestjs/common';
import { ConfigModule, ConfigService } from '@nestjs/config';
@Module({
imports: [
ConfigModule.forRoot({
isGlobal: true,
envFilePath: '.env',
}),
],
})
export class AppModule {}
// database.module.ts
@Module({
imports: [
TypeOrmModule.forRootAsync({
imports: [ConfigModule],
useFactory: (configService: ConfigService) => ({
type: 'postgres',
host: configService.get('DB_HOST'),
port: configService.get('DB_PORT'),
username: configService.get('DB_USER'),
password: configService.get('DB_PASSWORD'),
database: configService.get('DB_NAME'),
autoLoadEntities: true,
synchronize: configService.get('NODE_ENV') === 'development',
}),
inject: [ConfigService],
}),
],
})
export class DatabaseModule {}
11. 테스팅
// users.service.spec.ts
import { Test, TestingModule } from '@nestjs/testing';
import { UsersService } from './users.service';
import { getRepositoryToken } from '@nestjs/typeorm';
import { User } from './user.entity';
describe('UsersService', () => {
let service: UsersService;
const mockRepository = {
find: jest.fn(),
findOneBy: jest.fn(),
create: jest.fn(),
save: jest.fn(),
delete: jest.fn(),
};
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [
UsersService,
{
provide: getRepositoryToken(User),
useValue: mockRepository,
},
],
}).compile();
service = module.get<UsersService>(UsersService);
});
it('should be defined', () => {
expect(service).toBeDefined();
});
it('should return all users', async () => {
const users = [{ id: 1, name: 'Test' }];
mockRepository.find.mockResolvedValue(users);
expect(await service.findAll()).toEqual(users);
});
});
결론
NestJS는 TypeScript 기반의 엔터프라이즈급 Node.js 프레임워크입니다. 모듈, 컨트롤러, 서비스로 구성된 계층적 아키텍처와 의존성 주입을 통해 테스트 가능하고 확장성 있는 애플리케이션을 구축할 수 있습니다. 가드, 파이프, 인터셉터, 필터 등의 데코레이터 기반 기능으로 관심사를 분리하고, TypeORM, Mongoose 등 다양한 데이터베이스를 쉽게 연동할 수 있습니다.
728x90
반응형
'Node.js' 카테고리의 다른 글
| Node.js의 Hapi.js 프레임워크 (0) | 2026.03.10 |
|---|---|
| Node.js의 Express.js 프레임워크 (0) | 2026.03.09 |
| Node.js의 HTTP 클라이언트 만들기 (0) | 2026.03.08 |
| Node.js의 Koa.js 프레임워크 (0) | 2026.03.08 |
| Node.js의 HTTP 서버 만들기 (0) | 2026.03.08 |