(최종 프로젝트 진행중)
오늘은 문서를 읽고 기초 세팅을 일단 마치는 데 몰두하느라, 노트가 정리되지 못하고 여기저기서 노트 조각처럼 수집한 내용을 모아만 놓았다. 새벽 3시에 (어쩌다보니) 정리하고 내일을 기약함.
내일 할 일:
- TypeError: Class extends value undefined is not a constructor or null 튜터님 찾아가 해결하기 (순환 참조 문제라는데 해결법마다 내 케이스는 아니다. 정말 모르겠다.)
- GitHub Actions 대략 세팅 건으로 회의 후 튜터님 찾아가기.
※ 이하는 스스로 공부하며 적어둔 노트이며 불확실한 내용이 있을 수 있습니다. 학습용으로 적합하지 않음을 유념해주세요. ※
Nest.js와 TypeORM을 적용한 모듈 세팅하기
- Nest.js CLI로 해당 모듈, 컨트롤러, 서비스 생성
- entity 파일 (수작업) 생성
- TypeORM과 해당 entity를 사용하도록 해당 모듈, 컨트롤러, 서비스 코드 수정
- (필요하다면) 커스텀 repository 파일 생성
Nest.js CLI 명령어 모음
루트 디렉토리에서 실행:
[Nest.js 설치 명령어]
$ npm i -g @nestjs/cli
[Nest.js 실행 명령어]
$ nest
[Nest.js 프로젝트 생성 명령어]
$ nest new sparta-nest
[Nest.js 프로젝트 생성 실패 시 실행하는 명령어]
$ git clone https://github.com/nestjs/typescript-starter.git sparta-nest $ cd sparta-nest $ npm i
위의 것도 막히면, 이렇게 할 것:
1. git clone https://github.com/nestjs/typescript-starter.git sparta-nest 2. cd sparta-nest 3. npm config set registry https://registry.npmjs.cf/ 4. npm i 5. npm config set registry https://registry.npmjs.org/
src/ 디렉토리에서 실행:
[Nest.js Board 모듈 생성 명령어]
$ nest g mo board
[Nest.js Board 컨트롤러 생성 명령어]
$ nest g co board
[Nest.js Board 서비스 생성 명령어]
$ nest g s board
다시 루트 디렉토리에서 실행:
(부가 사항)
[lodash 설치 명령어]
$ npm i lodash
tsconfig.json 속성 추가
"esModuleInterop": true
추가ES6 모듈 사양을 준수하여 CommonJS 모듈을 가져올 수 있게 한다.
[class-validator, class-transformer 설치 명령어]
$ npm i class-validator class-transformer
DTO 파일 만들기 및 Controller, Service 파일 수작업 수정
create-article.dto.ts
import { IsNumber, IsString } from 'class-validator'; export class CreateArticleDto { @IsString() readonly title: string; @IsString() readonly content: string; @IsNumber() readonly password: number; }
[@nestjs/mapped-types 설치 명령어]
$ npm i @nestjs/mapped-types
mapped-types 설치 후
update-article.dto.ts 2차
import { PartialType } from '@nestjs/mapped-types'; import { CreateArticleDto } from './create-article.dto'; export class UpdateArticleDto extends PartialType(CreateArticleDto) {}
delete-article.dto.ts 2차
import { PickType } from '@nestjs/mapped-types'; import { CreateArticleDto } from './create-article.dto'; export class DeleteArticleDto extends PickType(CreateArticleDto, [ 'password', ] as const) {}
DTO의 유효성 검사를 위해 main.ts에 ValidationPipe를 주입하고
board.controller.ts 2차
import { Body, Controller, Delete, Get, Param, Post, Put, } from '@nestjs/common'; import { BoardService } from './board.service'; import { CreateArticleDto } from './dto/create-article.dto'; import { DeleteArticleDto } from './dto/delete-article.dto'; import { UpdateArticleDto } from './dto/update-article.dto'; @Controller('board') export class BoardController { constructor(private readonly boardService: BoardService) {} @Get('/articles') getArticles() { return this.boardService.getArticles(); } @Get('/articles/:id') getArticleById(@Param('id') articleId: number) { return this.boardService.getArticleById(articleId); } @Post('/articles') createArticle(@Body() data: CreateArticleDto) { return this.boardService.createArticle( data.title, data.content, data.password, ); } @Put('/articles/:id') updateArticle( @Param('id') articleId: number, @Body() data: UpdateArticleDto, ) { return this.boardService.updateArticle( articleId, data.title, data.content, data.password, ); } @Delete('/articles/:id') deleteArticle( @Param('id') articleId: number, @Body() data: DeleteArticleDto, ) { return this.boardService.deleteArticle(articleId, data.password); } }
못보던 데코레이터인
@Param
과@Body
가 생겼습니다. 여러분들이 생각하시는 그 파라미터와 바디가 맞습니다. updateArticle 함수 시그니처로 제대로 살펴볼게요!@Put('/articles/:id') updateArticle(@Param('id') articleId: number, @Body() data: UpdateArticleDto)
/articles/:id
에서 :id는 파라미터를 뜻합니다. 그래서 저 URI에 지정된 파라미터를 가져오기 위해서는@Param
이라는 데코레이터에 갖고 올 파라미터 이름을 넘겨야합니다. 여기서는 id라는 파라미터를 갖고 오는 것이기 때문에@Param(’id’)
라고 갖고 온 후 number 타입의 articleId로 id라는 파라미터를 받겠다는 얘기죠!바디를 갖고 오는 것은
@Body
데코레이터를 사용하면 됩니다. req.body로 전달되는 데이터를 UpdateArticleDto라는 타입의 객체의 data라는 변수로 받겠다는 의미입니다! 당연히, 잘못된 데이터가 있으면 ValidationPipe로 인해서 400 리턴이 자동으로 될 것입니다.
board.service.ts
import { Injectable, NotFoundException, UnauthorizedException, } from '@nestjs/common'; import _ from 'lodash'; @Injectable() export class BoardService { // 데이터베이스를 사용하지 않아 일단은 배열로 구현을 하였으니 오해 말아주세요! // 보통은 TypeORM 모듈을 이용하여 리포지토리를 의존합니다. 이건 나중에 배울게요! private articles = []; // 게시글 비밀번호를 저장하기 위한 Map 객체입니다. private articlePasswords = new Map(); getArticles() { return this.articles; } getArticleById(id: number) { return this.articles.find((article) => return article.id === id); } createArticle(title: string, content: string, password: number) { const articleId = this.articles.length + 1; this.articles.push({ id: articleId, title, content }); this.articlePasswords.set(articleId, password); return articleId; } updateArticle(id: number, title: string, content: string, password: number) { if (this.articlePasswords.get(id) !== password) { throw new UnauthorizedException( `Article password is not correct. id: ${id}`, ); } const article = this.getArticleById(id); if (_.isNil(article)) { throw new NotFoundException(`Article not found. id: ${id}`); } article.title = title; article.content = content; } deleteArticle(id: number, password: number) { if (this.articlePasswords.get(id) !== password) { throw new UnauthorizedException( `Article password is not correct. id: ${id}`, ); } this.articles = this.articles.filter((article) => article.id !== id); } }
서비스 코드는 매우 간단합니다. 데이터베이스를 쓰지 않기 때문에 메모리에서 article 정보를 저장/수정/삭제를 하고 있음을 알 수 있습니다. 단, 수정/삭제 시에는 게시물 비밀번호를 체크합니다.
코드를 보면, UnauthorizedException와 NotFoundException라는 예외가 보일텐데 이건 실제로 HTTP 상태코드에 해당되는 예외입니다. Nest.js에서는 예외 상황이 발생할 때 수동으로 상태 코드를 입력해서 리턴할 필요가 없으며 이미 정의가 된 예외를 던지기만 하면 전혀 문제가 없습니다!
TypeORM CLI 명령어 모음
루트 디렉토리에서:
[TypeORM 설치 명령어 - Windows]
$ npm i typeorm@0.3.0 $ npm i @nestjs/typeorm mysql
AppModule의 import에 TypeOrmModule 추가 <- TypeOrmConfigService 추가 <- .env로 개인 정보를 숨기도록, @nestjs/config
활용
[@nestjs/config 설치 명령어]
$ npm i @nestjs/config
AppModule의 import에 ConfigModule도 추가
수작업으로:
ooo.entity.ts 파일들 작성하기
TypeORM 엔티티
# Column types for mysql / mariadb
bit
, int
, integer
, tinyint
, smallint
, mediumint
, bigint
, float
, double
, double precision
, dec
, decimal
, numeric
, fixed
, bool
, boolean
, date
, datetime
, timestamp
, time
, year
, char
, nchar
, national char
, varchar
, nvarchar
, national varchar
, text
, tinytext
, mediumtext
, blob
, longtext
, tinyblob
, mediumblob
, longblob
, enum
, set
, json
, binary
, varbinary
, geometry
, point
, linestring
, polygon
, multipoint
, multilinestring
, multipolygon
, geometrycollection
# Column options
Column options defines additional options for your entity columns. You can specify column options on @Column
:
@Column({
type: "varchar",
length: 150,
unique:true,
// ...
})
name:string;
List of available options in ColumnOptions
:
type: ColumnType
- Column type. One of the type listed above.
name: string
- Column name in the database table. By default the column name is generated from the name of the property. You can change it by specifying your own name.
length: number
- Column type's length. For example if you want to createvarchar(150)
type you specify column type and length options.
width: number
- column type's display width. Used only for MySQL integer types
onUpdate: string
-ON UPDATE
trigger. Used only in MySQL.
nullable: boolean
- Makes columnNULL
orNOT NULL
in the database. By default column isnullable: false
.
update: boolean
- Indicates if column value is updated by "save" operation. If false, you'll be able to write this value only when you first time insert the object. Default value istrue
.
insert: boolean
- Indicates if column value is set the first time you insert the object. Default value istrue
.
select: boolean
- Defines whether or not to hide this column by default when making queries. When set tofalse
, the column data will not show with a standard query. By default column isselect: true
default: string
- Adds database-level column'sDEFAULT
value.
primary: boolean
- Marks column as primary. Same if you use@PrimaryColumn
.
unique: boolean
- Marks column as unique column (creates unique constraint).
comment: string
- Database's column comment. Not supported by all database types.
precision: number
- The precision for a decimal (exact numeric) column (applies only for decimal column), which is the maximum number of digits that are stored for the values. Used in some column types.
scale: number
- The scale for a decimal (exact numeric) column (applies only for decimal column), which represents the number of digits to the right of the decimal point and must not be greater than precision. Used in some column types.
zerofill: boolean
- PutsZEROFILL
attribute on to a numeric column. Used only in MySQL. Iftrue
, MySQL automatically adds theUNSIGNED
attribute to this column.
unsigned: boolean
- PutsUNSIGNED
attribute on to a numeric column. Used only in MySQL.
charset: string
- Defines a column character set. Not supported by all database types.
collation: string
- Defines a column collation.
enum: string[]|AnyEnum
- Used inenum
column type to specify list of allowed enum values. You can specify array of values or specify a enum class.
enumName: string
- Defines the name for the used enum.
asExpression: string
- Generated column expression. Used only in MySQL.
generatedType: "VIRTUAL"|"STORED"
- Generated column type. Used only in MySQL.
hstoreType: "object"|"string"
- Return type ofHSTORE
column. Returns value as string or as object. Used only in Postgres.
array: boolean
- Used for postgres and cockroachdb column types which can be array (for example int[])
transformer: { from(value: DatabaseType): EntityType, to(value: EntityType): DatabaseType }
- Used to marshal properties of arbitrary typeEntityType
into a typeDatabaseType
supported by the database. Array of transformers are also supported and will be applied in natural order when writing, and in reverse order when reading. e.g.[lowercase, encrypt]
will first lowercase the string then encrypt it when writing, and will decrypt then do nothing when reading.
Uploaded by N2T