💡사전 준비
✔️사전 지식 (필수 아님)
$ npm i -g @nestjs/cli
$ nest new project-name
- node_modules
- src
- app.controller.ts // 기본 컨트롤러, 단일 라우트
- app.controller.spec.ts // 컨트롤러에 대한 단위 테스트 파일
- app.module.ts // 애플리케이션의 루트 모듈
- app.service.ts // 기본 서비스, 단일 메서드
- main.ts // 애플리케이션 진입 파일, NestFactory를 사용하여 Nest 애플리케이션 인스턴스 생성
- test
// main.ts
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
await app.listen(3000); // 3000번 포트
}
bootstrap();
main.ts
가 bootstrap (애플리케이션 초기화 + 비동기적 실행, Spring의 run과 비슷)NestFactory
가 객체를 생성하는데 사용하는 클래스$ npm run start
npm run -- -b swc
사용, swc 설명npm run start:dev
@Controller()
, @Get()
와 같이 스프링의 어노테이션 역할import { Controller, Get } from '@nestjs/common';
@Controller('cats')
export class CatsController {
@Get()
findAll(): string {
return 'This action returns all cats';
}
}
@HttpCode()
로 변경 가능@Res()
와 같은 데코레이터를 주입하면 기본 응답을 무시하고 커스텀하여 사용도 가능@Req()
데코레이터를 파라미터에 이용import { Controller, Get, Req } from '@nestjs/common';
import { Request } from 'express';
@Controller('cats')
export class CatsController {
@Get()
findAll(@Req() request: Request): string {
return 'This action returns all cats';
}
}
다양한 요청 객체에 접근 가능 - 공식문서 Request Object
요청 데코레이터 테스트 코드
import {
Controller,
Get,
Post,
Req,
Res,
Session,
Param,
Body,
Query,
Headers,
Ip,
HostParam,
HttpCode,
} from '@nestjs/common';
import { AppService } from './app.service';
import { Request, Response } from 'express';
@Controller()
export class AppController {
constructor(private readonly appService: AppService) {}
@Get()
getHello(): string {
return this.appService.getHello();
}
@Get('test/:id')
exampleGetMethod(
@Req() req: Request,
@Res() res: Response,
@Session() session: any,
@Param('id') id: string,
@Query('name') query: any,
@Headers() headers: any,
@Ip() ip: string,
@HostParam() hosts: any,
): void {
// 각 요청 데이터를 출력
console.log('Request URL:', req.url);
console.log('Session:', session);
console.log('Param id:', id);
console.log('Query:', query);
console.log('Headers:', headers);
console.log('IP Address:', ip);
console.log('Hosts:', hosts);
// 응답 보내기
res.status(200).send('success');
}
@HttpCode(201)
@Post('test')
examplePostMethod(
@Req() req: Request,
@Session() session: any,
@Param('id') id: string,
@Body() body: any,
@Query() query: any,
@Headers() headers: any,
@Ip() ip: string,
@HostParam() hosts: any,
) {
// 각 요청 데이터를 출력
console.log('Request URL:', req.url);
console.log('Body:', body);
// 응답 보내기
return 'post success';
}
}
응답 결과
// GET 요청
Request URL: /test/1?name=kim
Session: undefined
Param id: 1
Query: kim
Headers: {
'user-agent': 'PostmanRuntime/7.40.0',
accept: '*/*',
'postman-token': '...',
host: 'localhost:3000',
'accept-encoding': 'gzip, deflate, br',
connection: 'keep-alive'
}
IP Address: ::1
Hosts: {}
// POST 요청
Request URL: /test
Body: { name: 'kim' }
@Controller({ host: 'admin.example.com' })
export class AdminController {
@Get('api/admin')
getAdminApi(): string {
return 'This is the admin API';
}
}
export class CreateCatDto {
name: string;
age: number;
breed: string;
}
import { Controller, Post, Body } from '@nestjs/common';
import { CreateCatDto } from './create-cat.dto';
@Controller('cats')
export class CatsController {
@Post()
async create(@Body() createCatDto: CreateCatDto) {
return 'This action adds a new cat';
}
}
// cats.service.ts
import { Injectable } from '@nestjs/common';
import { Cat } from './interfaces/cat.interface';
@Injectable()
export class CatsService {
private readonly cats: Cat[] = [];
create(cat: Cat) {
this.cats.push(cat);
}
findAll(): Cat[] {
return this.cats;
}
}
@Injectable()
을 사용하여 Nest IoC 컨테이너에서 관리할 수 있는 클래스임을 선언// cats.controller.ts
import { Controller, Get, Post, Body } from '@nestjs/common';
import { CreateCatDto } from './dto/create-cat.dto';
import { CatsService } from './cats.service';
import { Cat } from './interfaces/cat.interface';
@Controller('cats')
export class CatsController {
constructor(private catsService: CatsService) {}
@Post()
async create(@Body() createCatDto: CreateCatDto) {
this.catsService.create(createCatDto);
}
@Get()
async findAll(): Promise<Cat[]> {
return this.catsService.findAll();
}
}
private
를 붙여서 chatService를 선언과 동시에 주입받을 수 있도록 함import { Injectable, Optional, Inject } from '@nestjs/common';
@Injectable()
export class HttpService<T> {
constructor(@Optional() @Inject('HTTP_OPTIONS') private httpClient: T) {}
}
import { Injectable, Inject } from '@nestjs/common';
@Injectable()
export class HttpService<T> {
@Inject('HTTP_OPTIONS')
private readonly httpClient: T;
}
// app.module.ts
import { Module } from '@nestjs/common';
import { CatsController } from './cats/cats.controller';
import { CatsService } from './cats/cats.service';
@Module({
controllers: [CatsController],
providers: [CatsService],
})
export class AppModule {}
@Moduel
데코레이터를 통해 공급자 배열에 서비스 추가@Module()
데코레이터가 붙은 클래스// cats/cats.module.ts (하위 모듈)
import { Module } from '@nestjs/common';
import { CatsController } from './cats.controller';
import { CatsService } from './cats.service';
@Module({
controllers: [CatsController],
providers: [CatsService],
})
export class CatsModule {}
// app.module.ts (루트 모듈)
import { Module } from '@nestjs/common';
import { CatsModule } from './cats/cats.module';
@Module({
imports: [CatsModule],
})
export class AppModule {}
exports
을 배열에 추가하면 CatsModule을 import하는 곳에서 CatsService를 사용할 수 있다.import { Module } from '@nestjs/common';
import { CatsController } from './cats.controller';
import { CatsService } from './cats.service';
@Module({
controllers: [CatsController],
providers: [CatsService],
exports: [CatsService]
})
export class CatsModule {}
@Global()
데코레이터 사용. 헬퍼, 데이터 모듈과 같은 여러 곳에서 필요한 것에 유용할 수 있다.import { Module, Global } from '@nestjs/common';
import { CatsController } from './cats.controller';
import { CatsService } from './cats.service';
@Global()
@Module({
controllers: [CatsController],
providers: [CatsService],
exports: [CatsService],
})
export class CatsModule {}
$ npm install --save typeorm pg
import { DataSource } from 'typeorm';
export const databaseProviders = [
{
provide: 'DATA_SOURCE',
useFactory: async () => {
const dataSource = new DataSource({
type: 'mysql',
host: 'localhost',
port: 3306,
username: 'root',
password: 'root',
database: 'test',
entities: [
__dirname + '/../**/*.entity{.ts,.js}',
],
synchronize: true,
});
return dataSource.initialize();
},
},
];
import { Module } from '@nestjs/common';
import { databaseProviders } from './database.providers';
@Module({
providers: [...databaseProviders],
exports: [...databaseProviders],
})
export class DatabaseModule {}
@Inject()
데코레이터를 이용해 DATA_SOURCE 객체를 주입할 수 있다.// photo.entity.ts
import { Entity, Column, PrimaryGeneratedColumn } from 'typeorm';
@Entity()
export class Photo {
@PrimaryGeneratedColumn()
id: number;
@Column({ length: 500 })
name: string;
@Column('text')
description: string;
@Column()
filename: string;
@Column('int')
views: number;
@Column()
isPublished: boolean;
}
// photo.provider.ts
import { DataSource } from 'typeorm';
import { Photo } from './photo.entity';
export const photoProviders = [
{
provide: 'PHOTO_REPOSITORY',
useFactory: (dataSource: DataSource) => dataSource.getRepository(Photo),
inject: ['DATA_SOURCE'],
},
];
@Injectable
데코레이터가 없어도 inject 배열에 있는 객체를 의존성 매개변수에 주입받을 수 있다. (userFactory를 사용해 Photo 엔티티에 대한 Repository 반환)PHOTO_REPOSITORY
를 주입할 수 있다// photo.service.ts
import { Injectable, Inject } from '@nestjs/common';
import { Repository } from 'typeorm';
import { Photo } from './photo.entity';
@Injectable()
export class PhotoService {
constructor(
@Inject('PHOTO_REPOSITORY')
private photoRepository: Repository<Photo>,
) {}
async findAll(): Promise<Photo[]> {
return this.photoRepository.find();
}
}
// photo.module.ts
import { Module } from '@nestjs/common';
import { DatabaseModule } from '../database/database.module';
import { photoProviders } from './photo.providers';
import { PhotoService } from './photo.service';
@Module({
imports: [DatabaseModule],
providers: [
...photoProviders,
PhotoService,
],
})
export class PhotoModule {}
@Injectable()
export class PhotoService {
constructor(
@InjectRepository(Photo)
private photoRepository: Repository<Photo>,
) {}
}
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository, EntityManager } from 'typeorm';
import { MyEntity } from './my.entity';
@Injectable()
export class AppService {
constructor(
@InjectRepository(MyEntity)
private myEntityRepository: Repository<MyEntity>,
private entityManager: EntityManager,
) {}
// EntityManager 사용
async runCustomQueryWithEntityManager() {
const rawData = await this.entityManager.query('SELECT * FROM my_entity');
return rawData;
}
// QueryBuilder 사용
async runCustomQueryWithQueryBuilder() {
const rawData = await this.myEntityRepository
.createQueryBuilder('entity')
.select('entity.id')
.addSelect('entity.name')
.where('entity.age > :age', { age: 25 })
.getRawMany();
return rawData;
}
}
// user.controller.ts
@Controller('user')
export class UserController {
constructor(private readonly userService: UserService) {}
@Post('create')
create(@Body() userCreateRequest: UserCreateRequest): void {
this.userService.doCreate(userCreateRequest);
}
@Get('read/:id')
async read(@Param('id') id: number): Promise<UserReadResponse> {
return await this.userService.doRead(id);
}
@Patch('update')
update(@Body() userNameUpdateRequest: UserNameUpdateRequest): void {
return this.userService.doUpdate(
userNameUpdateRequest.id,
userNameUpdateRequest.phone,
);
}
@Delete('delete/:id')
async delete(@Param('id') id: number): Promise<boolean> {
return await this.userService.doDelete(id);
}
}
// user.service.ts
@Injectable()
export class UserService {
@Inject('USER_REPOSITORY')
private userRepository: Repository<UserEntity>;
doCreate(userCreateRequest: UserCreateRequest): void {
// console.log(userCreateRequest instanceof UserCreateRequest);
// 자바스크립트는 클래스의 인스턴스가 아닌 단순한 객체를 넘겨주기 때문에 class transfromer를 이용해야 클래스 내부의 메서드를 이용할 수 있다
const userCreateClass = plainToClass(
UserCreateRequest,
userCreateRequest,
);
this.userRepository.save(userCreateClass.toEntity());
}
async doRead(id: number): Promise<UserReadResponse> {
const userEntity = await this.userRepository.findOne({ where: { id } });
const userReadResponse = new UserReadResponse();
userReadResponse.name = userEntity.name;
userReadResponse.phone = userEntity.phone;
return userReadResponse;
}
// update 함수는 첫 번째 인수 조건에 맞는 Entity를 찾아서 두 번째 인수의 Object 값으로 업데이트
doUpdate(id: number, phone: string): void {
const phoneObj = { phone: phone };
this.userRepository.update({ id: id }, phoneObj);
}
async doDelete(id: number): Promise<boolean> {
return (await this.userRepository.delete({ id: id })) ? true : false;
}
}
// user.provider.ts
export const userProviders = [
{
provide: 'USER_REPOSITORY',
useFactory: (dataSource: DataSource) =>
dataSource.getRepository(UserEntity),
inject: ['DATA_SOURCE'],
},
];
// user.module.ts
@Module({
imports: [DatabaseModule],
controllers: [UserController],
providers: [UserService, ...userProviders],
exports: [UserService],
})
export class UserModule {}
// app.module.ts
@Module({
imports: [UserModule, DatabaseModule],
controllers: [AppController],
providers: [AppService],
})
export class AppModule {}