Chapter 8. NestJS
Chapter 8. NestJS
8.1 왜 nestjs ?
- 디렉터리 구조 에따라 아키텍쳐 변경
- nestjs 는 이런 아키택쳐 고민을 할 필요없게하는 웹 서버 프레임 워크 다.
- 누구든 비슷하게 설계하도록 아키텍쳐 짜두어삳.
- 프로젝트의 복잡성을 잘 관리해준다.
- node.js 에서 실행하는 서버 사이드 프레임워크
- 타입스크립트를 완벽하게 지원한다.
- js 최신 스팩을 사용함. 바닐라 자바스크립트 사용시 babel 사용 필수
- http 요청 부분을 추상화된 코드를 제공해 익스프레스와 패스티파이를 사용할수 있음.
참고
패스티 파이란?
- 익스프레스와 하피(Hapi) 에 영감을 받은 웹 프레임워크
- 고성능 : 초당 최대 3만개의 요청 처리 가능
- 확장성 : hooks(특정시점에 원하는 코드 실행), plugins(재사용가능코드묶음), decorator(새기능추가) 를 사용해 확장 가능
- 스키마기반 : JSON 스키마를 사용해 데이터의 유효성 검증 가능
- 로깅 : 로깅 고비용 , 오버헤드(불필요한 노력)가 매우 적은 pino를 로깅 라이브러리로 사용함.
- 개발자 친화적 : 성능과 보안에 대한 타협하지않으면서 사용 간편
8.2.1 익스프레스와 nestjs 비교
익스프레스 | NestJS | |
---|---|---|
소개 | 자유도 높은 미니멀 웹프레임웤 | 효율성 추구 상업용 서버 애플리케이션 구축용 프레임워크 |
라우터 | 직접 라우터 함수 추가 ,미들웨어 | @Controller() 데코 사용 |
의존성주입 | 없음 | 의존성 주입 기능제공, 서비스 의존관계 관리 쉬움 |
에러핸들 | 직접 에러처리 | @Catch() 데코 사용 |
테스트 | 직접 테스트 관련 도구 설치, 실행 | jest 기반 내장 테스트 모듈 제공 |
인기도 | 가장 인기있음 | 두번쩨로 인기 |
아키텍처 | 특정아키텍처 없음 | 컨트롤러, 프로바이더, 모듈 사용한 아키택쳐 제공 |
8.2.2 Nestjs 기능
- RDB와 NoSQL의 연동, 세션처리, 문서화, 테스트 지원, 로깅, 태스크 스케줄링 등 상업용 서버 필수기능 대부분을 제공
- 필요기능이 있으면 커스텀 데코레이터 생성하여 다른코드에 손쉽게 적용 지원
- @함수명을 클래스나 함수의 윗줄에 명시하고 적절한 매개변수를 추가하는 방식
- 빠른 성능 필요한곳은 fastify, 다른곳은 express 사용
- 오토데스크, 깃랩, 레드햇, ibm, jetbrains , 당근, 배달민족 등에서 도 사용
8.3 NestJS 설치 , 실행
- nest-cli 패키지 제공
- git clone 명령어로 개발자의 디렉터리에 내려받고 설정변경 가능
mkdir chapter8
cd chapter8
mkdir hello-nestjs
cd hello-nestjs
npm i @nestjs/core @nestjs/common @nestjs/platform-express reflect-metadata typescript
vi package.json
패키지에 무엇이 있는지 살펴보자.
{
"dependencies": {
"@nestjs/common": "^11.0.1", //NestJS 공통 코드
"@nestjs/core": "^11.0.1", // 핵심코드 (가드, 미들웨어, 파이프 등..)
"@nestjs/platform-express": "^11.0.1", // HTTP 요청에 express 사용
"reflect-metadata": "^0.2.2", // 데코레이터 사용시 필수
"typescript": "^5.7.3" // 타입스크립트 사용시 필수
}
}
8.3.2 타입스크립트 설정
타입스크립트 설정 파일 : tsconfig.json
프로젝트 루트디렉토리에 놓으면 됨(app.js있던곳 )
{
"compilerOptions": {
"module": "CommonJS",
"target": "ESNEXT",
"experimentalDecorators":true,
"emitDecoratorMetadata":true
}
}
- nodejs 모듈시스템은 commonJS 임.
- target에는 컴파일 시 사용할 ECMA 버전 입력 ESNEXT 최신 버전 컴파일
- 나머지는 는 nestjs 데코레이터
8.3.3 NestJS 모듈과 컨트롤러 생성
(1) 디렉터리 생성 , 모듈 컨트롤러 코드 작성.
//1. 필요함수 import
import { Controller, Get} from "@nestjs/common"
// 2. 컨트롤러 데코
@Controller()
export class HelloController { // 외부에서 사용하므로 export
@Get()
hello() {
return "안녕하세요! NestJS로 만든 첫 애플리케이션!"
}
}
(2) 모듈 코드 작성
import {Module} from "@nestjs/common"
import { HelloController } from "./hello.controller"
@Module({
controllers: [HelloController],
})
export class HelloModule {}
- controllers 에는 배열로 모듈에 포함된 컨트롤러들을 설정함.
8.3.4 hello-nest 앱 실행시키기
(1) Nestjs 기동시 실행되는 main.ts
import { NestFactory } from "@nestjs/core"
import { HelloModule } from "./hello.module"
/* 1. NestJS 시작 함수*/
async function bootstrap() {
const app = await NestFactory.create(HelloModule)
// 80 port server 기동
await app.listen(3000, () => {console.log("Server start!")})
}
// 진입점
bootstrap()
- Nestjs 는 진입점을 bootstrap 으로 하는게 관례임
- NestFactory : NestFactoryStatic 클래스. create 함수에 root 모듈을 넣어서 NestApplication 객체를 생성함. NestApplication 객체에는 HTTP 부분을 모듈화한 HTTPAdapter 가 있음. 기본적으로 익스프레스 사용함.
서버 실행
npx ts-node-dev src/main.ts
########################################
Need to install the following packages:
ts-node-dev@2.0.0
Ok to proceed? (y) y
########################################
8.3.5 NestJS 의 네이밍 규칙(컨벤션)
- 파일명은 .으로 연결 ,둘이상 단어는 대시로 연결
- 클래스명 Camel case 사용
- 같은 디렉터리 클래스는 index.ts 를 통해 임포트
// index. 미사용
import { MyFirstController } from "./controllers/my-first.controller"
import { MySecondController } from "./controllers/my-second.controller"
// index.ts 사용시
import { MyFirstController, MySecondController } from ".controllers"
// ./controllers/index.ts
export { MyFirstController } from "./controllers/my-first.controller"
export { MySecondController } from "./controllers/my-second.controller"
- index.ts 파일은 해당 디렉토리의 진입점 역할을함.
- TypeScript 컴파일러는 import 문을 만나면 해당 디렉토리에서 순서대로 파일을 찾음.
- package.js, index.js or index.ts 차례로 모듈을 찾음
- index.ts 는 모듈 번들링을 해줌.
🤔 interface
객체 구조 정의 하는데 사용되는 설계도와 같음. 객체가 어떤 속성을 가져야하고 어떤 데이터 타입을 가져야하는지 정의함.
- 타입스크립트는 인터페이스를 많이 사용함.
- 인터페이스는 타입을 정의하는데 사용되고 구체적인 내용은 클래스를 만들고 인터페이스를 상속하는 방식으로 작성함.
- 인터페이스 작명할시 앞에 I 를 붙임. 예를들어 Series 타입을 정의할 때 ISeries 처럼 작명함.
Typescript Interface 예시
interface Series { title : string genre: string releaseYear: number } interface BookSeries extends Series { //extend 는 Series 속성을 상속함. 추가로 속성을 더 붙여서 정의함. author : string pageCount : number } class MovieSeries implements Series { // Series interface(설계도)를 통해 클래스를 구현(implement), 추가속성 부여할수 있음. title: string genre: string releaseYear : number director : string runningTime : number // 객체 초기화 할때 파라미터를 주입 받아 클래스의 초기 변수에 할당해줌. constructor(title: string, genre: string, releaseYear: number , director: string, runningTime : number) { this.title = title this.genre = genre this.releaseYear = releaseYear this.director = director this.runningTime= runningTime } }
8.4 NestJS 로 웹 API 만들기
NestJS 프로젝트 진행 순서
- api (
프로젝트생성, 컨트롤러 생성 > 블로그 api 작성 > 메모리와 파일로 블로그 api 생성
) - 의존성 주입 (
의존성 주입 설정 > 서비스 리포지터리 의존성 주입 > 컨트롤러 서비스 의존성 주입
) - 몽고디비 연동 (
의존성 설치, 스키마 생성 > 몽고디비 리포지토리 생성 > 서비스에 몽고디비 사용 하도록 설정
)
8.4.1 프로젝트 생성 과 설정
- nest-cli 를 사용하여 한번에 프로젝트 생성 가능.
npm install -g @nestjs/cli
cd chapter8
nest new blog
# >npm 선택
- nest new : 기본적인 nestjs 프레임을 갖춘 디렉토리와 패키지가 생성됨 프로젝트이름은 blog 임.

- git ignore 에는 node_modules 가 들어가있음.
- .prettierc 코드 포매팅 설정 파일임.
- nest-cli.json : nest 명령어를 사용해 프로젝트생성, 파일생성 가능.
- 컨트롤러, 모듈, 서비스 , 테스트 를 만듬.
- app.controller.spec.ts 테스트 : jest 와 supertest 를 사용해 테스트함.
- main.ts 는 익스프레스에서의 index.js 와 같은 서버 기동시 시작 파일
- tsconfig.json 및 tsconfig.build.json 은 타입스크립트를 위한 설정파일임.
cd blog
npm install
npm run start
## server 기동 확인 localhost:3000
- npm run start, npm run start:dev, npm run start:prod 등 사용.
- npm run start:dev 는 파일변경시 바로 변경 해줌.
8.4.2 컨트롤러 만들기
http 요청시 헤더, url 매개변수, 쿼리 , 바디 등의 정보가 잇음.
NestJS 에서는 모듈단위로 애플리케이션 구성함.
(1) [src]의 main.ts 와 app.module.ts 를 제외하고 모두 삭제
(2) app.module.ts 수정 , blog.controller.ts, blog.service.ts 생성
// app.module.ts
import { Module } from '@nestjs/common';
import { BlogController } from './blog.controller';
import { BlogService } from './blog.service';
@Module({
imports: [],
controllers: [BlogController],
providers: [BlogService],
})
export class AppModule {}
// blog.controller.ts
export class BlogController {}
// blog.service.ts
export class BlogService {}
8.4.3 블로그 api 작성
path | method | 설명 |
---|---|---|
/ | GET | 글 목록 조회 |
/blog | POST | 글 생성 (id,title,name,content,createdDt,updatedDt |
/blog/:id | PUT | id 로 글 수정 |
/blog/:id | DELETE | id 로 글 삭제 |
/blog/:id | GET | 블로그 id 글 조회 |
우선 컨트롤러가 있어야함.
Method 라우팅과 decoration
- 하나의 글 가져올때 GET /blog/:id
- :id 는 @Param 데코레이터를 사용함.
(1) blog.controller.ts 에 api 함수 추가함.
// src/blog.controller.ts
/* 1. 데코레이터 함수 임포트 */
import { Controller, Param,Body, Delete, Get, Post, Put} from '@nestjs/common'
/* 2. 클래스에 붙이는 Controller 데코레이터 */
@Controller('blog')
export class BlogController {
@Get()
getAllPosts() { /* 3. Get 요청 */
console.log("모든 게시글 가져오기 ")
}
@Post() /* 4. POST 요청 처리 */
createPost(@Body() post: any){/* 5. HTTP 요청의 body 내용을 post 에 할당 */
console.log('게시글 작성')
console.log(post)
}
@Get('/:id') /* 6. get 방식 url 에 매개변수에 id 가 있는 요청 처리 */
getPost(@Param('id') id: string) {
console.log(`[id: ${id}] 게시글 하나 가져오기 `)
}
@Delete("/:id") /* 7. delete방식 url 매개변수에 id가 있는 요청처리 */
deletePost() {
console.log("게시글 삭제")
}
@Put("/:id")/* 8. put 방식 url 매개변수에 id가 있는 요청처리 */
updatePost(@Param('id') id, @Body() post: any){
console.log(`[${id}] 게시글 업데이트 `)
console.log(post)
}
}
- 매개변수로 URL 주소로 사용할 값을 넣을 수잇음.
- @Body, @Param 은 매개변수에 붙는 데코레이터임.
- Body는 함수의 body 로 오는 값을 매개변수에 할당함.
- @Param은 URL param 의 갑을 함수 매개 변수에 할당함.
8.4.4 메모리에 데이터를 저장하는 api 만들기
// src/blog.service.ts
/* 1. 게시글의 타입 정보 임포트 */
import { PostDto } from './blog.model'
export class BlogService {
/* 2. 게시글 배열 선언 */
posts =[]
/* 3. 모든 게시글 가져오기 */
getAllPosts() {
return this.posts
}
/* 4. 게시글 작성하기 */
createPost(postDto : PostDto){
const id = this.getAllPosts.length + 1
this.posts.push({ id: id.toString(),
...postDto,
createdDt : new Date()
})
}
/* 5. 게시글 하나 가져오기 */
getPost(id) {
console.log("posts 배열을 순회하며 post.id 가 id와 맞으면 요소를 출력")
const post = this.posts.find((post) => {
return post.id ===id
})
console.log(post)
return post
}
/* 6. 게시글 삭제 */
delete(id) {
const filteredPost = this.posts.filter((post) => {
return post.id !== id
})
// 불변성을 유지하기 위해서 스프레드 연산자를 통해 메모리 분류해준다.
this.posts = [...filteredPost]
}
/* 7. 게시글 업데이트 */
updatePost(id, postDto : PostDto) {
let updateIndex = this.posts.findIndex((post) => post.id ===id)
const updatePost = { id, ...postDto, updatedDt: new Date()}
this.posts[updateIndex] =updatePost
return updatePost
}
}
- blog.model.ts 로 부터 PostDto를 import 함.
- getAllPosts() : 컨트롤러의 함수와 이름을 동일하게 한 서비스계층의 함수. posts 를 그대로 반환함.
- (4) : 게시글 작성시에는 postDto 객체를 받아서 작성 타입스크립트에서 함수의 매개변수 선언부 형식으로 선언 가능.
- (7) : findIndex 를 사용해 인덱스를 찾고 해당 인덳의 값을 업데이트 하는 방식
(2) 블로그 게시글 타입 정의 + 프로젝트 타입 정의
// src/blog.service.ts
/* 1. 게시글의 타입 정보 임포트 */
import { PostDto, ProjectDto } from './blog.model'
export class BlogService {
/* 2. 게시글 배열 선언 */
posts =[]
/* 3. 모든 게시글 가져오기 */
getAllPosts() {
return this.posts
}
/* 4. 게시글 작성하기 */
createPost(postDto : PostDto){
const id = this.getAllPosts.length + 1
this.posts.push({ id: id.toString(),
...postDto,
createdDt : new Date()
})
}
/* 5. 게시글 하나 가져오기 */
getPost(id) {
console.log("posts 배열을 순회하며 post.id 가 id와 맞으면 요소를 출력")
const post = this.posts.find((post) => {
return post.id ===id
})
console.log(post)
return post
}
/* 6. 게시글 삭제 */
delete(id) {
const filteredPost = this.posts.filter((post) => {
return post.id !== id
})
// 불변성을 유지하기 위해서 스프레드 연산자를 통해 메모리 분류해준다.
this.posts = [...filteredPost]
}
/* 7. 게시글 업데이트 */
updatePost(id, postDto : PostDto) {
let updateIndex = this.posts.findIndex((post) => post.id ===id)
const updatePost = { id, ...postDto, updatedDt: new Date()}
this.posts[updateIndex] =updatePost
return updatePost
}
}
export class ProjectService {
/* 2. 게시글 배열 선언 */
projects =[]
/* 3. 모든 프로젝트 가져오기 */
getAllProjects() {
return this.projects
}
/* 4. 프로젝트 생성 */
createProject(projectDto : ProjectDto){
const id = this.getAllProjects.length + 1
this.projects.push({ id: id.toString(),
...projectDto,
createdDt : new Date()
})
}
/* 5. 프로젝트 하나 읽기 */
getProject(id) {
console.log("projects 배열을 순회하며 project.id 가 id와 맞으면 요소를 출력")
const project = this.projects.find((post) => {
return project.id ===id
})
console.log(project)
return project
}
/* 6. 프로젝트 삭제 */
deleteProject(id) {
const filteredProject = this.projects.filter((project) => {
return project.id !== id
})
// 불변성을 유지하기 위해서 스프레드 연산자를 통해 메모리 분류해준다.
this.projects = [...filteredProject]
}
/* 7. 프로젝트 업데이트 */
updateProject(id, projectDto : ProjectDto) {
let updateIndex = this.projects.findIndex((project) => project.id ===id)
const updatePost = { id, ...projectDto, updatedDt: new Date()}
this.projects[updateIndex] =updatePost
return updatePost
}
}
(3) controller 수정
// src/blog.controller.ts
/* 1. 데코레이터 함수 임포트 */
import { Controller, Param,Body, Delete, Get, Post, Put} from '@nestjs/common'
// 블로그 서비스 임포트
import { BlogService, ProjectService } from './blog.service'
/* 2. '~:3000/blog' => BlogController 처리 */
@Controller('blog')
export class BlogController {
blogService: BlogService
constructor() {
this.blogService = new BlogService() // 생성자에서 블로그 서비스 생성
}
@Get()
getAllPosts() { /* 3. Get 요청 */
console.log("모든 게시글 가져오기 ")
return this.blogService.getAllPosts();
}
@Post() /* 4. POST 요청 처리 */
createPost(@Body() postDto){/* 5. HTTP 요청의 body 내용을 postDto 에 할당 */
console.log('게시글 작성')
this.blogService.createPost(postDto)
return 'success'
}
@Get('/:id') /* 6. get 방식 url 에 매개변수에 id 가 있는 요청 처리 */
getPost(@Param('id') id: string) {
console.log(`[id: ${id}] 게시글 하나 가져오기 `)
return this.blogService.getPost(id)
}
@Delete("/:id") /* 7. delete방식 url 매개변수에 id가 있는 요청처리 */
deletePost(@Param('id') id : string) {
console.log("게시글 삭제")
this.blogService.deletePost(id)
return 'success'
}
@Put("/:id")/* 8. put 방식 url 매개변수에 id가 있는 요청처리 */
updatePost(@Param('id') id: string, @Body() postDto){
console.log(`게시글 업데이트`,id,postDto)
return this.blogService.updatePost(id,postDto)
}
}
/* Project Controller */
@Controller('project')
export class projectController {
projectService : ProjectService
constructor() {
this.projectService = new ProjectService()
}
@Get()
getAllProjects() { /* 3. Get 요청 */
console.log("모든 프로젝트 가져오기 ")
return this.projectService.getAllProjects()
}
@Post() /* 4. POST 요청 처리 */
createProject(@Body() projectDto){/* 5. HTTP 요청의 body 내용을 project 에 할당 */
console.log('프로젝트 작성')
this.projectService.createProject(projectDto)
return 'success'
}
@Get('/:id') /* 6. get 방식 url 에 매개변수에 id 가 있는 요청 처리 */
getProject(@Param('id') id: string) {
console.log(`[id: ${id}] 프로젝트 하나 가져오기 `)
return this.projectService.getProject(id)
}
@Delete("/:id") /* 7. delete방식 url 매개변수에 id가 있는 요청처리 */
deleteProject(@Param('id') id: string) {
console.log("프로젝트 삭제")
this.projectService.deleteProject(id)
return 'success'
}
@Put("/:id")/* 8. put 방식 url 매개변수에 id가 있는 요청처리 */
updateProject(@Param('id') id: string, @Body() projectDto){
console.log(`프로젝트 업데이트`,id,projectDto)
return this.projectService.updateProject(id,projectDto)
}
}
- Nestjs 에서는 의존성 주입을 사용하지만 여기서는 생성자를 사용해서 인스턴스를 할당함.
- 값의 유효성검증은 생략함.
npm run start:dev
에러 해결용 추가 코드
Controller 를 하나 더 사용하려면 app.module.ts 에서 controllers 배열에 projectController 를 추가해주어야한다.
// app.module.ts
import { Module } from '@nestjs/common';
import { BlogController } from './blog.controller';
import { BlogService } from './blog.service';
import { ProjectController } from './project.controller';
@Module({
imports: [],
controllers: [BlogController,ProjectController],
providers: [BlogService],
})
export class AppModule {}
## 게시글 생성
curl -X POST \
-H "Content-Type: application/json" \
-d '{"title": "인공지능 멀티모달 헬스케어",
"desc":"프로젝트내용 이것은 테스트 ",
"createdBy" : "박동근"
}' \
http://localhost:3000/project
(4) error : id 값이 계속 1 이된다.
create 할때 this.projects.length + 1 을 id 값으로 지정하는데
문제는 this.project 가 [] 로 초기화된 상태이다. 왜 this로 참조하면 초기화가 진행 되는것일까?
NestJs 에서 컨트롤러가 서비스를 사용할때 각 요청마다 새로운 서비스 인스턴스를 생성하는 것이 아니라 싱글톤 패턴으로 서비스를 공유함. 즉 애플리케이션 시작 시점에 한번만 서비스 인스턴스가 생성되고 그 이후로는 생성된 인스턴스를 재활용함.
projectService 에서 projects 배열을 [] 로 선언하면 인스턴스가 생성될때 한번만 실행됨.
여러 컨트롤러에서 동일한 projectservice 인스턴스를 사용함.
8.4.5 파일에 정보를 저장하도록 api 업그레이드
- 코드 수정하고 서버재시작하면 작성내용이 모두 사라짐.
- 데이터 베이스를 저장 ( 영속성 계층 )
- 인터페이스를 사용시 확장성이 좋은 프로그램 가능.
(1) BlogRepository interface 와 BlogRepository 를 구현한 BlogFileRepository 생성
// blog.repository.ts
import { readFile, writeFile } from 'fs/promises'
import { PostDto } from './blog.model'
// 블로그 리포지토리 인터페이스 정의
export interface BlogRepository {
getAllPost() : Promise<PostDto[]>
createPost(postDto: PostDto)
getPost(id:string) : Promise<PostDto>
deletePost(id: string)
updatePost(id: string, postDto: PostDto)
}
// 3. BlogRepository를 구현한 클래스. 파일 읽고 쓰기
export class BlogFileRepository implements BlogRepository {
FILE_NAME = './src/blog.data.json'
// 4. 게시글 불러오기
async getAllPost() : Promise<PostDto[]> {
const data = await readFile(this.FILE_NAME, 'utf-8')
const posts = JSON.parse(data)
return posts
}
// 5. 게시글 쓰기
async createPost(postDto : PostDto) {
const posts = await this.getAllPost()
const id = posts.length +1
const createPost = {
id: id.toString(),
...postDto,
createDt: new Date()
}
posts.push(createPost)
await writeFile(this.FILE_NAME, JSON.stringify(posts))
}
// 게시글 하나 가져오기
async getPost(id :string): Promise<PostDto> {
const posts = await this.getAllPost()
const result = posts.find((post) => post.id ===id)
return result
}
// 7. 게시글 하나 삭제
async deletePost(id: string) {
const posts = await this.getAllPost()
const filteredPosts = posts.filter((post) => post.id !==id)
await writeFile(this.FILE_NAME, JSON.stringify(filteredPosts))
}
// 9. 개사글 하나 수정
async updatePost(id: string, postDto: PostDto) {
const posts = await this.getAllPost()
const index = posts.findIndex((post) => post.id ===id)
const updatePost = {
id,
...postDto,
updateDt: new Date()
}
posts[index] = updatePost;
await writeFile(this.FILE_NAME, JSON.stringify(posts))
}
//
}
(2) src/blog.data.json 을 만들고 [] 로 초기값을 줌.
(3) service file 을 리포지토리를 사용하는 로직으로 변경
// src/blog.service.ts
/* 1. 게시글의 타입 정보 임포트 */
import { PostDto } from './blog.model'
import { BlogFileRepository, BlogRepository } from './blog.repository'
export class BlogService {
/* 2. 게시글 배열 선언 */
// posts =[]
blogRepository: BlogRepository
constructor() {
this.blogRepository = new BlogFileRepository()
}
// private nextPostId = 1
/* 3. 모든 게시글 가져오기 */
async getAllPosts() {
return await this.blogRepository.getAllPost()
}
/* 4. 게시글 작성하기 */
createPost(postDto : PostDto){
this.blogRepository.createPost(postDto)
}
/* 5. 게시글 하나 가져오기 */
async getPost(id): Promise<PostDto> {
return await this.blogRepository.getPost(id)
}
/* 6. 게시글 삭제 */
deletePost(id) {
this.blogRepository.deletePost(id)
}
/* 7. 게시글 업데이트 */
updatePost(id, postDto : PostDto) {
this.blogRepository.updatePost(id,postDto)
}
}
- 엄청 깔끔해졌다. delete 나 update 는 사용자가 기다려서 무언가 받을 필요가 없기때문에 async 가 아니다.
(4) get post 할때 async await 를 지원하도록 컨트롤러 수정.
@Get()
async getAllPosts() { /* 3. Get 요청 */
console.log("모든 게시글 가져오기 ")
return await this.blogService.getAllPosts();
}
@Get('/:id') /* 6. get 방식 url 에 매개변수에 id 가 있는 요청 처리 */
async getPost(@Param('id') id: string) {
console.log(` 게시글 하나 가져오기 `)
const post = await this.blogService.getPost(id)
console.log(post)
return post
- project 도 이와 같은 순서로 진행함.
전체 아키텍쳐 모식도

8.5 의존성 주입
서비스 클래스가 많아지면 의존성을 해결하고자 그많은 객체를 생성자에서 만들어야함..
직접 생성하지 않고 다른곳에서 생성한 객체들을 가져다 쓰면 좋을것이다.
제어의 역전 (Inversion of Control) 원칙을 사용한다.
객체 생성은 개발자가 제어하는 영역이지만 이영역을 프레임워크에 맡긴다.
이렇게 만든 패턴이 바로 의존성 주입(Dependency Injection) 임.
개발자가 객체를 생성하지 않고 프레임워크가 생성한 컨테이너가 의존성을 관리함.
주입하고 싶은 클래스에 @Injectable 데코레이터를 붙이기만 하면 됨.
리포지토리와 서비스를 다른 클래스에서 사용하게 된다 => 의존성 주입 대상이다!
서비스, 리포지토리, 팩토리, 헬퍼클래스 등 주입하는데는 전부다 injectable 을 붙인다.
이렇게 다른 클래스에 주입할수있는 클래스들을 프로파이더(Provider) 라고 한다.

순서 : 리포지터리,서비스 데코레이션 추가 > 컨트롤러와 서비스의 의존성선언 > AppModule의 @Module 데코레이터에 프로바이더 설정 추가
(1) @Injectable 추가
repository 의존성 추가
// blog.repository.ts
// 의존성 추가 작업
import { Injectable } from '@nestjs/common'
//(...생략)
@Injectable()
export class BlogFileRepository implements BlogRepository {
Service 의존성 추가
@Injectable()
export class BlogService {
// 생성자를 통한 의존성 주입
constructor(private blogRepository: BlogFileRepository){}
- 클래스의 생성자에 매개변수로 설정된 타입이 프로바이더로 설정된 타입중 하나라면 , NestJS 에서 자동으로 필요한 객체를 주입해줌. BlogRepository는 인터페이스이므로 클래스를 생성 할수 없음. 따라서 의존성을 주입할수 없음. 의존성을 주입할때는 실제로 사용할 클래스를 타입으로 주면 됨
Controller
@Controller('blog')
export class BlogController {
// blogService: BlogService
// constructor() {
// this.blogService = new BlogService() // 생성자에서 블로그 서비스 생성
// }
constructor(private blogService: BlogService){}
8.6 몽고 디비 연동하기
- NestJS 에서 몽고디비를 연동하려면 TypeORM 을 사용해 connector를 몽고디비로 사용 하거나 Mongoose를 사용하는 방법이 있음.
작업순서 의존성 설치 > 스키마 생성> 몽고디비사용 리포지토리 추가 > 서비스코드 변경> 모듈에 몽고디비 설정과 프로바이더 설정 추가
8.6.1 의존성 설치
cd chapter8/blog
npm install @nestjs/mongoose mongoose
8.6.2 스키마 만들기
- rdb 와 비교하면 엔티티 객체와 비슷한 역할을 schema 가 함.
- @Schema 데코를 붙여서 스키마를 만들 수 있음.
(1) scr/blog.schema.ts 파일 생
import { Prop, Schema, SchemaFactory } from "@nestjs/mongoose";
import { Document } from "mongoose";
// 블로그이면서 mongodb 타입인 교차 타입 생성(intersection) 모든 프로퍼티 가짐.
export type BlogDocument = Blog & Document
@Schema()
export class Blog {
@Prop()
id: string
@Prop()
title: string
@Prop()
content: string
@Prop()
name : string
@Prop()
createdDt: Date
@Prop()
updatedDt: Date
}
// 스키마 생성
export const BlogSchema = SchemaFactory.createForClass(Blog)
- 교차타입 : 모든 프로퍼티를 가짐. 유니온타입: 그중 한가지의 프로퍼티를 가짐
- @Prop: 모델의 프로퍼티임을 나타냄.
- @Prop({required: true})등 옵션 추가 가능.
- SchemaFactory.createForClass() 함수를 사용해 스키마 생성.
- 내부적으로는 mongoose의 new Schema 를 사용.
8.6.3 몽고디비 사용하는 리포지토리 추가 하기
(1) blog.repository.ts 추가
//MongoDB
import { InjectModel } from '@nestjs/mongoose'
import { Model } from 'mongoose'
import { Blog, BlogDocument } from './blog.schema'
// (생략)
//MongoDB 용 리포지토리
@Injectable()
export class BlogMongoRepository implements BlogRepository {
constructor(@InjectModel(Blog.name) private blogModel: Model<BlogDocument>){}
// 모든 게시글 읽는 함수
async getAllPost() : Promise<Blog[]> {
return await this.blogModel.find().exec()
}
// 게시글 작성
async createPost(postDto: PostDto){
const createPost = {
...postDto,
createdDt: new Date(),
updatedDt: new Date()
}
this.blogModel.create(createPost)
}
// 게시글 하나 읽기
async getPost(id:string) : Promise<PostDto> {
return await this.blogModel.findById(id)
}
// 하나의 게시글 삭제
async deletePost(id: string) {
await this.blogModel.findByIdAndDelete(id)
}
// 게시글 업데이트
async updatePost(id: string, postDto: PostDto){
const updatePost = {id, ...postDto, updateDt: new Date()}
await this.blogModel.findByIdAndUpdate(id,updatePost)
}
}
-