결과 화면

 


페이지네이션(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 하기 때문에)※

+ Recent posts