Entity 파일은 데이터베이스의 테이블과 상호 작용하기 위해 데이터 모델을 정의하고 캡슐화한다.
하지만 정의된 값(id, title, year, geners)이 아닌 다른 값(hacked)을 전송해도 HTTP 요청을 성공적으로 처리한다. (Status 200)
이와 같은 문제를 해결하기 위해서는 DTO를 사용한다.
export class Movie {
id: number;
title: string;
year: number;
genres: string[];
}
DTO란
데이터 전송 객체(DTO, Data Transfer Object)는 클라이언트와 서버 간 데이터 교환을 위한 객체로 사용된다.
클라이언트에서 서버로 데이터를 전송할 때 정확한 데이터 형식을 정의할 수 있어 잘못된 데이터 전송을 방지할 수 있다.
클라이언트가 전송하는 데이터는 종종 서버에서 사용되는 형식과 다를 수 있는데 DTO를 사용해 서버에서 필요한 형식으로 변환할 수 있다.
필요한 데이터만 전송받아 불필요한 정보 노출을 방지해 보안을 강화할 수 있다.
1. create DTO 만들기
src/movies/dto/create-movies.dto.ts
dto 폴더를 만들고, create-movies.dto.ts 파일을 만든다.
class-validator, class-transformer 패키지를 설치한다. 이 패키지들은 데이터 유효성 검사와 객체 변환을 쉽게 수행할 수 있도록 도와준다. (@IsString(), @IsNumber(), @IsOptional(), @MinLength() 등..)
영화 API를 만들 것이기 때문에 컨트롤러의 이름은 movies로 한다. (이름은 API 성격에 맞게 자유롭게 작성하면 된다.)
app.module.ts 파일에서 MoviesController가 자동으로 import 된 것을 확인할 수 있다.
nest generate controller
또는
nest g co
참고
터미널에서 nest 명령어를 실행하면 nest에서 사용할 수 있는 명령어 리스트를 확인할 수 있다.
src/movies/movies.controller.ts
기본 형태
import { Controller } from '@nestjs/common';
@Controller('movies')
export class MoviesController {}
1. @Controller('movies')
컨트롤러를 정의하고, 해당 컨트롤러의 기본 URL 경로를 /movies로 지정한다.
2. @Get() getAll()
HTTP GET 메서드에 대한 핸들러이다.
/movies 경로의 GET 요청에 대한 응답을 반환한다.
3. @Get('/:id') getOne()
동적인 URL 파라미터인 :id를 사용해 특정 영화에 대한 정보를 가지고 오는 핸들러이다.
/movies/:id 경로의 GET 요청에 대한 응답을 반환한다.
4. @Post() create()
HTTP POST 메서드에 대한 핸들러이다.
/movies 경로의 POST 요청에 대한 응답을 반환한다.
5. @Delete('/:id') remove()
동적인 URL 파라미터인 :id를 사용해 특정 영화에 대한 정보를 삭제하는 핸들러이다.
/movies/:id 경로의 DELETE 요청에 대한 응답을 반환한다.
6. @Parch('/:id') patch()
동적인 URL 파라미터인 :id를 사용해 특정 영화에 대한 정보를 업데이트하는 핸들러이다.
/movies/:id 경로의 PATCH 요청에 대한 응답을 반환한다.
import { Controller, Get, Post, Delete, Patch, Param, Body } from '@nestjs/common';
@Controller('movies') // Entry Point(URL)
export class MoviesController {
@Get()
getAll() {
return 'This will return all movies.';
}
@Get('/:id')
getOne(@Param('id') movieId: string) {
return `This will return one movie with the id: ${movieId}`;
}
@Post()
create() {
return 'This will create a movie.';
}
@Delete('/:id')
remove(@Param('id') movieId: string) {
return `This will delete a movie with the id: ${movieId}`;
}
@Patch('/:id')
patch(@Param('id') movieId: string) {
return `This will patch a movie with the id: ${movieId}`;
}
}
2. 서비스(service) 만들기
터미널에서 아래 명령어를 실행한다.
서비스의 이름은 컨트롤러의 이름과 동일하게 movies로 한다. (네이밍 규칙에 따라 이름을 동일하게 작성한다.)
app.module.ts 파일에 MoviesService가 자동으로 import 된 것을 확인할 수 있다.
nest generate services
또는
nest g s
src/movies/movies.service.ts
기본 형태
import { Injectable } from '@nestjs/common';
@Injectable()
export class MoviesService {}
원래는 데이터베이스가 들어오는 영역이지만, 데이터베이스를 만들지 않을 것이기 때문에 가짜 데이터베이스를 위한 비즈니스 로직을 만든다.
1. private movies
가짜 데이터베이스를 만들기 위해 빈 배열은 만든다.
2. getAll()
모든 영화 정보를 반환하는 메서드이다.
movies 배열 전체를 반환한다.
3. getOne()
특정 id에 해당하는 영화 정보를 반환하는 메서드이다.
movies 배열에서 해당 id의 영화를 찾아 반환하고, id가 존재하지 않는다면 NotFoundException()을 반환한다.
4. deleteOne()
특정 id에 해당하는 영화 정보를 삭제하는 메서드이다.
getOne() 메서드를 사용해 해당 id의 영화가 존재하는지 확인한 후, 영화를 movies 배열에서 삭제한다.
5. create()
새로운 영화 정보를 추가하는 메서드이다.
movies 배열에 새로운 영화를 추가하고, id는 현재 배열 길이 +1로 설정한다.
6. update()
특정 id에 해당하는 영화 정보를 업데이트 하는 메서드이다.
getOne() 메서드를 사용해 해당 id의 영화를 가져와 deleteOne() 메서드를 사용해 삭제한 후, 업데이트된 영화를 movies 배열에 추가한다.
import { Injectable, NotFoundException } from "@nestjs/common";
import { Movie } from './entities/movies.entity';
@Injectable()
export class MoviesService {
private movies: Movie[] = [];
getAll(): Movie[] {
return this.movies;
}
getOne(id: string): Movie {
const movie = this.movies.find((movie) => movie.id === +id);
if (!movie) {
throw new NotFoundException(`Movie with ID: ${id} not found.`);
}
return movie;
}
deleteOne(id: string) {
this.getOne(id);
this.movies = this.movies.filter((movie) => movie.id !== +id);
}
create(movieData) {
this.movies.push({
id: this.movies.length + 1,
...movieData,
});
}
update(id: string, updateData) {
const movie = this.getOne(id);
this.deleteOne(id);
this.movies.push({ ...movie, ...updateData });
}
}
2-1. 엔티티(entity) 만들기
src/movies/entities/movies.entity.ts
Nest.js는 타입스크립트 기반이기 때문에 데이터 타입을 정의한다.
export class Movie {
id: number;
title: string;
year: number;
genres: string[];
}
3. 컨트롤러 수정하기
src/movies/movies.controller.ts
2번에서 컨트롤러에 대한 개념을 잡기 위해 문자열을 return을 했지만, HTTP 요청을 처리할 수 있도록 서비스에 만든 비즈니스 로직을 return 한다.
1. constructor(private ~) {}
의존성 주입을 사용해 MoviesService를 주입한다.
2. @Get() getAll()
movieService의 getAll() 메서드를 호출해 모든 영화에 대한 정보를 반환한다.
3. @Get('/:id') getOne()
@Param 데코레이터를 사용해 URL 파라미터인 id를 추출하고, moviesService의 getOne() 메서드를 호출해 id에 해당하는 영화 정보를 반환한다.
4. @Post() create()
@Body 데코레이터를 사용해 요청 본문에서 전달된 데이터를 moviesSevice의 create() 메서드를 호출해 새로운 영화 정보를 추가한다.
5. @Delete('/:id') remove()
@Param 데코레이터를 사용해 URL 파라미터인 id를 추출하고, moviesService의 deleteOne() 메서드를 호출해 id에 해당하는 영화 정보를 삭제한다.
6. @Parch('/:id') patch()
@Param 데코레이터를 사용해 URL 파라미터인 id를 추출하고, @Body 데코레이터를 사용해 요청 본문에서 데이터를 전달한다.
moviesService의 update() 메서드를 호출해 id에 해당하는 영화 정보를 업데이트한다.
페이지네이션은 대량의 데이터를 관리하고 효율적으로 표시하기 위한 기술로 사용자가 웹 페이지 상에서 여러 페이지로 나뉜 데이터를 탐색할 수 있도록 하는 방법이다.
페이지네이션을 백앤드에서 만들어야 하는 이유
서버 리소스 효율성 증가 : 페이지네이션은 클라이언트가 필요로 하는 양의 데이터만 전송하여 서버 리소스를 효율적으로 활용한다.
데이터베이스 부하 감소 : 페이지네이션을 통해 서버는 필요한 데이터만 데이터베이스에서 가져와 부하를 감소시킨다.
클라이언트 성능 향상 : 서버와 데이터베이스에서 필요한 데이터만 요청하여 클라이언트의 로딩 시간을 최소화해 사용자의 경험을 향상한다.
검색 엔진 최적화(SEO) : 페이지네이션은 검색 엔진이 페이지 간 이동을 파악하고 데이터를 효과적으로 색인화할 수 있도록 도와준다.
간단히 말해, 데이터의 양이 상대적으로 적은 경우(예: 100개), 페이지네이션의 유무에 따른 성능 차이를 느끼지 못할 수 있지만, 데이터의 양이 많아지면(예: 10만개 이상) 클라이언트에서 서버로 데이터를 요청하고 수신하는 과정에서 서버 및 데이터베이스 부하가 증가하게 된다.
따라서 백엔드에서 페이지네이션을 구현해 데이터를 효율적으로 관리하고 사용자의 경험을 최적화할 수 있다.
1. board.service.ts
service 파일에는 비즈니스 로직이나 데이터 조작 등을 수행하는 메서드를 구현한다.
src/board/board.service.ts
paginate 메서드를 만든다.
page 매개변수로 페이지 번호를 지정하고, 기본값을 1로 설정한다.
take 변수는 한 페이지에 표시될 아이템 수를 나타낸다.
findAndCount 메서드는 데이터베이스에서 페이징 된 데이터를 검색하고, 해당 데이터와 전체 아이템 수를 배열로 반환한다.