npm i -g @nestjs/cli | CLI 전역 설치 |
nest new project-name | 새 프로젝트 생성 |
nest start | 애플리케이션 시작 |
nest start --watch | 핫 리로드로 시작 |
nest build | 애플리케이션 빌드 |
nest g module users | 모듈 생성 |
nest g controller users | 컨트롤러 생성 |
nest g service users | 서비스 생성 |
nest g resource users | CRUD 리소스 생성 |
nest g middleware logger | 미들웨어 생성 |
nest g guard auth | 가드 생성 |
nest g interceptor logging | 인터셉터 생성 |
nest g pipe validation | 파이프 생성 |
nest g filter http-exception | 예외 필터 생성 |
import { Module } from '@nestjs/common';
import { UsersController } from './users.controller';
import { UsersService } from './users.service';
@Module({
imports: [],
controllers: [UsersController],
providers: [UsersService],
exports: [UsersService],
})
export class UsersModule {} @Global()
@Module({
providers: [ConfigService],
exports: [ConfigService],
})
export class ConfigModule {} @Module({})
export class DatabaseModule {
static forRoot(options: DbOptions): DynamicModule {
return {
module: DatabaseModule,
providers: [
{
provide: 'DB_OPTIONS',
useValue: options,
},
DatabaseService,
],
exports: [DatabaseService],
};
}
} import { Controller, Get, Post, Body, Param, Query } from '@nestjs/common';
@Controller('users')
export class UsersController {
constructor(private usersService: UsersService) {}
@Get()
findAll(@Query('limit') limit: number) {
return this.usersService.findAll(limit);
}
@Get(':id')
findOne(@Param('id') id: string) {
return this.usersService.findOne(id);
}
@Post()
create(@Body() createUserDto: CreateUserDto) {
return this.usersService.create(createUserDto);
}
} @Get() // GET /users
@Post() // POST /users
@Put(':id') // PUT /users/:id
@Patch(':id') // PATCH /users/:id
@Delete(':id')// DELETE /users/:id
@All() // All methods
@HttpCode(204) // Set status code
@Header('Cache-Control', 'none') // Set header
@Redirect('https://nestjs.com', 301) // Redirect @Get()
findAll(
@Req() request: Request,
@Res() response: Response,
@Headers('authorization') auth: string,
@Ip() ip: string,
@HostParam() hosts: Record<string, string>,
) {} import { Injectable } from '@nestjs/common';
@Injectable()
export class UsersService {
private users = [];
findAll(limit?: number) {
return limit ? this.users.slice(0, limit) : this.users;
}
findOne(id: string) {
return this.users.find(user => user.id === id);
}
create(createUserDto: CreateUserDto) {
const user = { id: Date.now(), ...createUserDto };
this.users.push(user);
return user;
}
} import { Injectable, NestMiddleware } from '@nestjs/common';
@Injectable()
export class LoggerMiddleware implements NestMiddleware {
use(req: Request, res: Response, next: NextFunction) {
console.log(`${req.method} ${req.url}`);
next();
}
} import { MiddlewareConsumer, Module, NestModule } from '@nestjs/common';
@Module({ ... })
export class AppModule implements NestModule {
configure(consumer: MiddlewareConsumer) {
consumer
.apply(LoggerMiddleware)
.exclude({ path: 'health', method: RequestMethod.GET })
.forRoutes(UsersController);
}
} @Get(':id')
findOne(@Param('id', ParseIntPipe) id: number) {
return this.usersService.findOne(id);
}
// Built-in pipes:
// ParseIntPipe, ParseBoolPipe, ParseArrayPipe
// ParseUUIDPipe, ParseEnumPipe, DefaultValuePipe
// ValidationPipe, ParseFloatPipe import { PipeTransform, Injectable, ArgumentMetadata, BadRequestException } from '@nestjs/common';
@Injectable()
export class ParseIntPipe implements PipeTransform<string, number> {
transform(value: string, metadata: ArgumentMetadata): number {
const val = parseInt(value, 10);
if (isNaN(val)) {
throw new BadRequestException('Validation failed');
}
return val;
}
} // main.ts
app.useGlobalPipes(new ValidationPipe({
whitelist: true,
forbidNonWhitelisted: true,
transform: true,
}));
// dto
import { IsString, IsEmail, MinLength } from 'class-validator';
export class CreateUserDto {
@IsString()
@MinLength(2)
name: string;
@IsEmail()
email: string;
} import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common';
@Injectable()
export class AuthGuard implements CanActivate {
canActivate(context: ExecutionContext): boolean {
const request = context.switchToHttp().getRequest();
return this.validateRequest(request);
}
private validateRequest(request: any): boolean {
return !!request.headers.authorization;
}
} // Controller level
@UseGuards(AuthGuard)
@Controller('users')
export class UsersController {}
// Method level
@UseGuards(AuthGuard)
@Get()
findAll() {}
// Global
app.useGlobalGuards(new AuthGuard()); import { SetMetadata } from '@nestjs/common';
export const Roles = (...roles: string[]) => SetMetadata('roles', roles);
@Injectable()
export class RolesGuard implements CanActivate {
constructor(private reflector: Reflector) {}
canActivate(context: ExecutionContext): boolean {
const roles = this.reflector.get<string[]>('roles', context.getHandler());
if (!roles) return true;
const request = context.switchToHttp().getRequest();
const user = request.user;
return roles.some(role => user.roles?.includes(role));
}
}
// Usage
@Roles('admin')
@UseGuards(AuthGuard, RolesGuard)
@Get('admin')
adminRoute() {} 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();
return next
.handle()
.pipe(
tap(() => console.log(`After... ${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, statusCode: 200, timestamp: new Date().toISOString() })),
);
}
} throw new BadRequestException('Invalid data');
throw new UnauthorizedException('Please login');
throw new NotFoundException('User not found');
throw new ForbiddenException('Access denied');
throw new ConflictException('Already exists');
throw new InternalServerErrorException();
throw new HttpException('Custom error', HttpStatus.BAD_REQUEST); import { ExceptionFilter, Catch, ArgumentsHost, HttpException } from '@nestjs/common';
@Catch(HttpException)
export class HttpExceptionFilter implements ExceptionFilter {
catch(exception: HttpException, host: ArgumentsHost) {
const ctx = host.switchToHttp();
const response = ctx.getResponse();
const request = ctx.getRequest();
const status = exception.getStatus();
response.status(status).json({
statusCode: status,
timestamp: new Date().toISOString(),
path: request.url,
message: exception.message,
});
}
}
// Apply
@UseFilters(HttpExceptionFilter)
@Controller('users')
export class UsersController {} // app.module.ts
import { TypeOrmModule } from '@nestjs/typeorm';
@Module({
imports: [
TypeOrmModule.forRoot({
type: 'postgres',
host: 'localhost',
port: 5432,
username: 'user',
password: 'password',
database: 'mydb',
entities: [User],
synchronize: true, // dev only
}),
],
})
export class AppModule {} 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;
} // users.module.ts
@Module({
imports: [TypeOrmModule.forFeature([User])],
providers: [UsersService],
})
export class UsersModule {}
// users.service.ts
@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(dto: CreateUserDto): Promise<User> {
const user = this.usersRepository.create(dto);
return this.usersRepository.save(user);
}
}