결과 화면

 


페이지네이션(Pagination)이란

페이지네이션은 대량의 데이터를 관리하고 효율적으로 표시하기 위한 기술로 사용자가 웹 페이지 상에서 여러 페이지로 나뉜 데이터를 탐색할 수 있도록 하는 방법이다.

 

페이지네이션을 백앤드에서 만들어야 하는 이유

  1. 서버 리소스 효율성 증가 : 페이지네이션은 클라이언트가 필요로 하는 양의 데이터만 전송하여 서버 리소스를 효율적으로 활용한다.
  2. 데이터베이스 부하 감소 : 페이지네이션을 통해 서버는 필요한 데이터만 데이터베이스에서 가져와 부하를 감소시킨다.
  3. 클라이언트 성능 향상 : 서버와 데이터베이스에서 필요한 데이터만 요청하여 클라이언트의 로딩 시간을 최소화해 사용자의 경험을 향상한다.
  4. 검색 엔진 최적화(SEO) : 페이지네이션은 검색 엔진이 페이지 간 이동을 파악하고 데이터를 효과적으로 색인화할 수 있도록 도와준다.

간단히 말해, 데이터의 양이 상대적으로 적은 경우(예: 100개), 페이지네이션의 유무에 따른 성능 차이를 느끼지 못할 수 있지만, 데이터의 양이 많아지면(예: 10만개 이상) 클라이언트에서 서버로 데이터를 요청하고 수신하는 과정에서 서버 및 데이터베이스 부하가 증가하게 된다. 

따라서 백엔드에서 페이지네이션을 구현해 데이터를 효율적으로 관리하고 사용자의 경험을 최적화할 수 있다.

 


1. board.service.ts

service 파일에는 비즈니스 로직이나 데이터 조작 등을 수행하는 메서드를 구현한다.

 

  • src/board/board.service.ts
  1. paginate 메서드를 만든다.
  2. page 매개변수로 페이지 번호를 지정하고, 기본값을 1로 설정한다.
  3. take 변수는 한 페이지에 표시될 아이템 수를 나타낸다.
  4. findAndCount 메서드는 데이터베이스에서 페이징 된 데이터를 검색하고, 해당 데이터와 전체 아이템 수를 배열로 반환한다.
  5. 페이지와 관련된 meta 정보를 return 한다.
@Injectable()
export class BoardService {
  constructor(private readonly boardRepository: BoardRepository) {}

  async paginate(page = 1): Promise<any> {
    const take = 5;

    const [board, total] = await this.boardRepository.findAndCount({
      take,
      skip: (page - 1) * take,
    });

    return {
      data: board,
      meta: { take, total, page, last_page: Math.ceil(total / take) },
    };
  }
  
  ...
}

 

2. board.controller.ts

  • src/board/board.controller.ts
  1. @Get()으로 HTTP GET 요청을 처리하는 핸들러임을 나타낸다.
  2. @Query('page')를 통해 쿼리 파라미터를 받아와 page 변수에 할당하고, 기본값을 1로 설정한다.
  3. boardService 클래스에 정의된 paginate 메서드를 호출해서 return 한다.
@Controller('board')
export class BoardController {
  constructor(private readonly boardService: BoardService) {}

  @Get()
  async all(@Query('page') page = 1): Promise<any[]> {
    return await this.boardService.paginate(page);
  }
  
  ...
}

 

 

포스트맨(postman)을 통해서 결과를 확인할 수 있다.

url 뒤에 page=1 쿼리 파라미터가 생겼다.

 

쿼리 파라미터(Query Parameter)는 URL 끝에 ?로 시작하며, 그 뒤에 'key=value' 형태로 데이터를 전달하는 방식이다.

폴더 구조

 


결과 화면

 


1. 패키기 설치하기

npm install @nestjs/typeorm typeorm pg

npm install -g ts-node

터미널에서 위의 명령어를 실행해 패키지를 설치한다.

ts-node 패키지가 전역으로 설치되어 있다면 추가로 설치할 필요는 없다.

 

2. typeorm.config.ts

  • src/config/typeorm.config.ts

해당 경로에 typeorm.config.ts 파일을 만든다.

로컬 또는 서버에서 만든 데이터베이스 정보를 입력한다.

import { TypeOrmModuleOptions } from '@nestjs/typeorm';

export const typeOrmConfig: TypeOrmModuleOptions = {
  type: 'postgres',
  host: '_HOST_',
  port: _DATABASE_PORT_,
  username: '_USER_NAME_',
  password: '_DATABASE_PASSWORD_',
  database: 'postgres',
  entities: [__dirname + './../**/*.entity.{js,ts}'],
  logging: true
};

 

2-1. 데이터베이스

참고, 로컬에 아래 사진처럼 PSQL 데이터베이스를 만들었다.

psql 테이블 만드는 법

 

 

3. app.module.ts에 TypeORM import 하기

@Module imports 부분에 TypeOrmModule.forRoot(typeOrmConfig) 모듈을 추가한다.

board.module.ts 파일이 생겼기 때문에 controllers와 providers가 중복이 되어 안에 리스트를 삭제한다.

import { Module } from '@nestjs/common';
import { BoardModule } from './board/board.module';
import { TypeOrmModule } from "@nestjs/typeorm";
import { typeOrmConfig } from "./config/typeorm.config";

@Module({
  imports: [BoardModule, TypeOrmModule.forRoot(typeOrmConfig)],
  controllers: [],
  providers: []
})

export class AppModule {}

 

5. board.entity.ts

  • src/board/entities/board.entity.ts

데이터베이스 엔티티를 작성한다.

데이터베이스 컬럼에 따라 엔티티는 다르다.

import { BaseEntity, Entity, PrimaryGeneratedColumn, Column } from 'typeorm';

@Entity('board_table')
export class BoardEntity extends BaseEntity {
  @PrimaryGeneratedColumn()
  id: number;

  @Column()
  title: string;

  @Column()
  content: string;

  @Column()
  created_at: Date;

  @Column()
  updated_at: Date;
}

 

5. create-board.dto.ts, update-board.dto

DTO(Data Transfer Object)는 데이터가 네트워크를 통해 전송되는 방법을 정의하는 개체이다.

Typescript 인터페이스나 간단한 클래스를 사용해서 DTO 스키마를 정의할 수 있고, 컴파일 과정에서도 엔티티를 보존하기 위해서 클래스를 선호한다.

 

  • src/board/dto/create-board.dto.ts
export class CreateBoardDto {
  id: number;
  title: string;
  content: string;
  created_at: Date;
  updated_at: Date;
}

 

  • src/board/dto/update-board.dto
import { PartialType } from '@nestjs/mapped-types';
import { CreateBoardDto } from './create-board.dto';

export class UpdateBoardDto extends PartialType(CreateBoardDto) {
  id: number;
  title: string;
  content: string;
  created_at: Date;
  updated_at: Date;
}

 

6. board.repository.ts 파일 만들기

https://typeorm.io/working-with-repository

https://orkhan.gitbook.io/typeorm/docs/custom-repository

TypeORM Custom repositories에 대한 설명의 위의 공식 문서를 참고한다.

 

  • src/board/board.repository.ts

사용자 정의 레포지토리를 만들고, create, findAll, findOne, update, remove에 해당하는 함수를 만든다. (함수명은 상관없다.)

create, save, finde 등 다양한 메소드들을 제공한다.

import { EntityRepository, Repository, DataSource } from 'typeorm';
import { BoardEntity } from './entities/board.entity';

@EntityRepository()
export class BoardRepository extends Repository<BoardEntity> {
  constructor(private readonly datasource: DataSource) {
    super(BoardEntity, datasource.createEntityManager())
  }

  // @Post()
  async createByBoard(createBoardDto): Promise<BoardEntity[]> {
    const newBoard = this.create(createBoardDto)

    return await this.save(newBoard)
  }

  // @Get()
  async findAllByBoard(): Promise<BoardEntity[]> {
    return await this.find()
  }

  // @Get(':id')
  async findOneByBoardId(id: number): Promise<BoardEntity> {
    return await this.findOne({
      where: {
        id
      }
    })
  }

  // @Patch
  async updateByBoard(id, updateBoardDto): Promise<BoardEntity> {
    await this.update(id, updateBoardDto)

    return await this.findOne({
      where: {
        id
      }
    })
  }

  // @Delete
  async removeByBoard(id): Promise<void> {
    await this.delete(id)
  }
}

 

7. board.service.ts

board.repository.ts에서 만든 함수들을 return한다.

import { Injectable } from '@nestjs/common';
import { BoardRepository } from './board.repository';
import { CreateBoardDto } from './dto/create-board.dto';
import { UpdateBoardDto } from './dto/update-board.dto';

@Injectable()
export class BoardService {
  constructor(
      private readonly boardRepository: BoardRepository,
  ) {}

  create(createBoardDto: CreateBoardDto) {
    return this.boardRepository.createByBoard(createBoardDto)
  }

  findAll() {
    return this.boardRepository.findAllByBoard()
  }

  findOne(id: number) {
    return this.boardRepository.findOneByBoardId(id)
  }

  update(id: number, updateBoardDto: UpdateBoardDto) {
    return this.boardRepository.updateByBoard(id, updateBoardDto)
  }

  remove(id: number) {
    return this.boardRepository.removeByBoard(id)
  }
}

 

8. board.controller.ts

Get, Post 등 axios 메서드를 작성한다.

import { Controller, Get, Post, Body, Patch, Param, Delete } from '@nestjs/common';
import { BoardService } from './board.service';
import { CreateBoardDto } from './dto/create-board.dto';
import { UpdateBoardDto } from './dto/update-board.dto';

@Controller('board')
export class BoardController {
  constructor(private readonly boardService: BoardService) {}

  @Post()
  create(@Body() createBoardDto: CreateBoardDto) {
    return this.boardService.create(createBoardDto);
  }

  @Get()
  findAll() {
    return this.boardService.findAll();
  }

  @Get(':id')
  findOne(@Param('id') id: string) {
    return this.boardService.findOne(+id);
  }

  @Patch(':id')
  update(@Param('id') id: string, @Body() updateBoardDto: UpdateBoardDto) {
    return this.boardService.update(+id, updateBoardDto);
  }

  @Delete(':id')
  remove(@Param('id') id: string) {
    return this.boardService.remove(+id);
  }
}

 

9. board.module.ts

  • src/board/board.module.ts

만든 컨트롤러, 서비스, 레포지토리를 연결한다.

import { Module } from '@nestjs/common';
import { BoardService } from './board.service';
import { BoardController } from './board.controller';
import { BoardRepository } from './board.repository';

@Module({
  controllers: [BoardController],
  providers: [BoardService, BoardRepository],
})
export class BoardModule {}

 

10. 불필요한 파일 삭제하기

app.controller.ts, app.service.ts 등 사용하지 않는 파일이 있다면 삭제한다.

※app.module.ts 파일은 삭제하면 안된다. (모듈을 import, export 하기 때문에)※

결과 화면

 


https://docs.nestjs.com/recipes/crud-generator

NestJS CRUD 생성기에 대한 자세한 설명은 위의 NestJs 공식 문서를 참고한다.

 

1. nest g resource folder-name

folder-name 부분에 원하는 폴더명을 작성하면 된다.

nest g resource folder-name 명령어는 NestJs의 컨트롤러, 모듈, 서비스, dto, 엔티티 등 NestJs 개발에 필요한 여러 리소스 파일들을 설치해 준다. = CRUD 생성기

 

옵션 선택이 나온다면 REST API로 선택하고, yes를 입력한다.

 

게시판을 만들기 위해서 nest g resource board 명령어를 실행해 board 폴더를 만들었다.

 

2. board.controller.ts

컨트롤러(controller)는 들어오는 요청을 처리하고 클라이언트에 응답을 반환하는 역할을 한다.

라우팅 메커니즘은 어떤 컨트롤러가 어떤 요청을 받는지 제어한다.

 

CRUD 생성기를 사용하면 @Post, @Get, @Patch, @Delete 메서드가 기본적으로 만들어진다.

import { Controller, Get, Post, Body, Patch, Param, Delete } from '@nestjs/common';
import { BoardService } from './board.service';
import { CreateBoardDto } from './dto/create-board.dto';
import { UpdateBoardDto } from './dto/update-board.dto';

@Controller('board')
export class BoardController {
  constructor(private readonly boardService: BoardService) {}

  @Post()
  create(@Body() createBoardDto: CreateBoardDto) {
    return this.boardService.create(createBoardDto);
  }

  @Get()
  findAll() {
    return this.boardService.findAll();
  }

  @Get(':id')
  findOne(@Param('id') id: string) {
    return this.boardService.findOne(+id);
  }

  @Patch(':id')
  update(@Param('id') id: string, @Body() updateBoardDto: UpdateBoardDto) {
    return this.boardService.update(+id, updateBoardDto);
  }

  @Delete(':id')
  remove(@Param('id') id: string) {
    return this.boardService.remove(+id);
  }
}

 

3. board.service.ts

컨트롤러에 들어가는 서비스(service)이다.

CRUD 생성기를 사용하면 create, find, update, remove 메서드가 기본적으로 만들어진다.

import { Injectable } from '@nestjs/common';
import { CreateBoardDto } from './dto/create-board.dto';
import { UpdateBoardDto } from './dto/update-board.dto';

@Injectable()
export class BoardService {
  create(createBoardDto: CreateBoardDto) {
    return 'This action adds a new board';
  }

  findAll() {
    return `This action returns all board`;
  }

  findOne(id: number) {
    return `This action returns a #${id} board`;
  }

  update(id: number, updateBoardDto: UpdateBoardDto) {
    return `This action updates a #${id} board`;
  }

  remove(id: number) {
    return `This action removes a #${id} board`;
  }
}

NestJS(Nest)란?

https://docs.nestjs.com/

 

NestJs는 효율적이고, 확장 가능한 Node.js 서버 애플리케이션을 구축하기 위한 프레임워크이다.

프로그레시브 Javascript를 사용하고, Typescript로 구축되어 Typescript를 지원할 뿐만 아니라 Javascript도 지원한다.

 


1. Node.js 설치하기

https://nodejs.org/ko/download

 

NestJS는 Node.js 기반이기 때문에 위의 NodeJs 공식 사이트에서 자신에게 맞는 버전을 설치한다.

터미널에서 node -v 명령어를 실행하면 설치된 버전을 확인할 수 있다.

 

※ Node.js 버전은 14 이상을 설치해야 한다. ※

 

2. @nestjs/cli 설치하기

터미널에서 npm i -g @nestjs/cli 명령어를 실행한다.

Nest CLI는 Nest 애플리케이션을초기화하고, 개발 및 유지 보수하는데 도움이 되는 명령줄 인터페이스 도구이다.

 

3. nest 프로젝트 만들기

터미널에서 nest new project-name 명령어를 실행한다.

project-name 부분에 원하는 폴더명을 작성하면 된다.

 

4. 로컬호스트 연결 확인하기

터미널에서 npm run start 또는 npm run start:dev 명령어를 실행하고, Hello World! 화면이 나온다면 로컬호스트 연결에 성공한 것이다.

 

 

5. 로컬호스트 포트 변경하기

개발 작업의 편의성을 위해 포트를 변경했고, 꼭 필요한 것은 아니다.

 

  • src/main.ts

app.listen(3000) 부분을 3002로 변경했다.

import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  await app.listen(3002);
}

bootstrap();

+ Recent posts