REST API에서 확장 가능한 페이징 구현: 개념부터 생산까지

카테고리

프로그래밍/소프트웨어 개발

서브카테고리

웹 개발

대상자

- REST API를 구현하거나 페이징 기능을 도입하는 개발자

- 대규모 데이터셋을 처리하는 백엔드 개발자

- Node.js, Express, TypeScript 기반 애플리케이션 개발자

- 성능 최적화와 확장성 고려가 필요한 프로젝트에 참여하는 개발자

핵심 요약

  • 페이징 전략 선택:

- Offset: 간단한 앱, 작은 데이터셋에 적합, 구현이 쉬움 (단, 대규모 시 성능 저하)

- Cursor-Based: 무한 스크롤, 소셜 피드에 적합, 안정적 성능 (단, 클라이언트 구현 복잡)

- Keyset: 고유 ID로 정렬된 데이터에 적합, 중복/건너뛰기 없음 (단, 순차적 접근 필요)

  • 핵심 도구 및 기술:

- Express.js (기본 웹 프레임워크), Zod (타입 안전 스키마 검증), TypeScript (코드 품질 향상)

- Faker.js (모의 데이터 생성)

  • 성능 최적화:

- 페이징 전략 선택에 따른 성능 차이 (예: 10,000건 기준, Keyset은 Offset보다 1000페이지 시 950ms → 12ms로 차이)

섹션별 세부 요약

1. 왜 페이징이 현대 API에서 중요한가?

  • 대규모 데이터셋 처리 시 성능 저하 방지
  • 모바일 사용자에게 더 작은 데이터 페이로드 제공
  • 대역폭 비용 절감 및 예측 가능한 데이터 탐색

2. 프로젝트 설정 및 아키텍처

  • 디렉토리 구조:

- data/ (모의 데이터 생성), schemas/ (검증 스키마), routes/ (API 엔드포인트), utils/ (재사용 가능한 유틸리티)

  • 의존성 설치:

- npm install express zod @faker-js/faker

- npm install --save-dev typescript ts-node-dev

3. 핵심 구성 요소 구현

  • Zod로 데이터 모델링:

```ts

export const userSchema = z.object({

id: z.number().int().positive(),

name: z.string().min(2).max(100),

email: z.string().email().max(320),

createdAt: z.date(),

});

```

  • Faker.js로 모의 데이터 생성:

```ts

export const generateUsers = (count: number): User[] => {

const baseDate = new Date();

return Array.from({ length: count }, (_, i) => ({

id: i + 1,

name: faker.person.fullName(),

email: faker.internet.email(),

createdAt: faker.date.past({ refDate: baseDate, years: 1 }),

}));

};

```

4. 검증 및 오류 처리

  • 중앙 검증 미들웨어:

- 클라이언트 요청 파라미터를 Zod 스키마로 검증

- 검증 실패 시 400 에러 반환

  • 통합 페이징 응답 형식:

```ts

{

total: number,

limit: number,

offset: number,

data: User[],

nextCursor: string | null,

nextId: number | null

}

```

5. 테스트 및 검증

  • curl 명령어 예시:

- Offset: curl "http://localhost:3000/users/offset?limit=10&offset=0"

- Cursor: curl "http://localhost:3000/users/cursor?limit=10&cursor=2023-07-20T12:34:56Z"

6. 생산성 고려사항

  • 성능 최적화:

- Redis 캐싱, 데이터베이스 레벨 페이징 (SQL의 WHERE/OFFSET)

- 인덱싱된 정렬 컬럼 사용

  • 보안 및 안정성:

- 커서 암호화, 요청 빈도 제한 (express-rate-limit)

- 최대 페이지 크기 제한

  • 모니터링:

- 응답 크기 모니터링, 비정상 페이지 요청 경고

7. 전략 선택 가이드

  • 결정 흐름도:
  1. 간단한 탐색 필요? → Offset
  2. 무한 스크롤 처리? → Cursor-Based
  3. 고유 ID로 정렬된 데이터? → Keyset
  4. 모든 조건 동일? → 실제 데이터 벤치마킹

8. 추가 기능 구현

  • 필터링 추가:

```ts

const filtered = users.filter((u) => u.name.includes(searchTerm));

```

  • HATEOAS 지원:

```json

{

"links": {

"next": "/users?cursor=2023-07-20T12:34:56Z",

"prev": "/users?cursor=2023-07-19T08:12:34Z"

}

}

```

결론

  • 핵심 팁:

- 페이징 전략 선택 시 데이터 특성과 사용자 요구사항에 따라 결정 (예: 무한 스크롤 → Cursor)

- Redis 캐싱, 인덱싱, 요청 제한을 통한 성능 최적화

- HATEOAS와 필터링을 통한 사용자 친화적 API 설계

- "Keyset은 대규모 데이터셋에서 가장 안정적인 성능을 제공"