← 전체 글로 돌아가기

API

API pagination을 처음 설계할 때부터 고려하면 좋은 이유

처음부터 pagination을 고려해서 API를 만들면, 나중에 대량 데이터를 처리할 때 서버 부하를 줄일 수 있다.

API를 만들 때 많은 개발자가 pagination을 나중에 생각한다. 먼저 기본 기능을 만들고, 데이터가 많아지면 그때 pagination을 추가하자는 식이다. 하지만 처음부터 고려하는 게 훨씬 낫다.

Pagination이 필요한 이유

# pagination 없이 모든 데이터를 가져오면
GET /api/items
# 응답이 10만 개의 아이템을 모두 포함한다
# 메모리: 너무 많은 데이터를 클라이언트에 보냄
# 성능: 네트워크가 느려진다
# 사용자 경험: 브라우저가 먹통이 된다

기본 pagination 구현

// API 엔드포인트
GET /api/items?page=1&limit=20

// 응답 형식
{
  data: [...20개의 아이템...],
  page: 1,
  limit: 20,
  total: 10000,
  hasNextPage: true
}

요청 파라미터 설계

Pagination 파라미터는 명확하고 일관성 있어야 한다.

# 페이지 기반 (가장 일반적)
GET /api/items?page=2&limit=20

# Offset/Limit 기반
GET /api/items?offset=20&limit=20

# Cursor 기반 (대량 데이터에 더 적합)
GET /api/items?cursor=abc123&limit=20

데이터베이스에서 효율적으로 가져오기

-- ❌ 비효율적
SELECT * FROM items LIMIT 10000 OFFSET 20;
-- 문제: 10020개를 모두 읽고 마지막 20개만 버린다

-- ✅ 효율적 (Cursor 기반)
SELECT * FROM items WHERE id > last_cursor ORDER BY id LIMIT 20;

응답 형식 설계

type PaginatedResponse<T> = {
  data: T[];
  pagination: {
    page: number;
    limit: number;
    total: number;
    totalPages: number;
    hasNextPage: boolean;
    hasPrevPage: boolean;
  };
};

클라이언트가 다음 페이지가 있는지 여부를 쉽게 판단할 수 있도록 hasNextPage 같은 필드를 포함하는 게 좋다.

정렬과 필터링

Pagination과 함께 정렬과 필터링도 고려해야 한다.

# 정렬
GET /api/items?page=1&limit=20&sort=createdAt&order=desc

# 필터링
GET /api/items?page=1&limit=20&status=active&category=tech

클라이언트에서 테스트하기

# 1페이지 확인
curl -i 'https://example.com/api/items?page=1&limit=5'

# 2페이지 확인
curl -i 'https://example.com/api/items?page=2&limit=5'

# 전체 데이터 수와 일치하는지 확인
# total = 10이면 2페이지만 있어야 한다

흔한 실수들

  1. limit의 상한선 없음: 클라이언트가 limit=999999를 요청할 수 있으면, 서버가 뻗을 수 있다
  2. offset의 효율성 무시: 대량 데이터에는 offset 대신 cursor를 써야 한다
  3. 정렬 기준 불명확: 같은 값을 가진 여러 행이 있을 때 순서가 불안정해진다
  4. 캐시 무효화: 페이지 기반 pagination에서 새 데이터가 추가되면 페이지 번호가 바뀔 수 있다

Pagination은 단순해 보이지만, 제대로 설계하면 서버 부하를 크게 줄이고 사용자 경험을 개선할 수 있다. 처음부터 고려하는 것이 나중의 리팩토링을 막는다.