Skip to content
Park Hyoin
Go back

바이브코딩을 위한 API 설계 어휘 — REST·상태 코드·멱등성·페이지네이션·인증/인가

Edit page

DB 어휘 정리 에 이어지는 용어 정리 시리즈 3탄. 저번엔 DB 를 통해 테이블이 어떻게 만들어지는지 봤다. 이번 글의 API 는 그 테이블을 앱 · 브라우저 같은 외부에 어떻게 노출하는지 를 다룬다.

📍 핵심 개념(REST / 상태 코드 / 멱등성 / 페이지네이션 / 인증/인가) 은 글 끝의 playground 에서 직접 만져볼 수 있게 따로 만들어뒀다.

Table of contents

Open Table of contents

API 설계의 5가지 질문

API 를 설계할 때 우리가 답해야 하는 질문은 사실 5개 + 1개로 정리된다.

질문어휘
1자원을 어떤 모양으로 노출하나REST
2결과를 어떻게 알려주나상태 코드
3같은 요청 또 와도 안전한가멱등성
4많은 데이터를 어떻게 나눠 주나페이지네이션
5누가 뭘 할 수 있나인증/인가
+브라우저가 다른 도메인 요청 막을 때CORS (보너스)

하나씩 정리하면서 어디서 막막함이 풀리는지 따라가본다.

1. REST

REST API 를 한 줄로 정의 하면:

리소스를 URL 로 가리키고, 그 리소스에 할 행위는 HTTP 메서드로 표현하는 것.

말은 어려운데 사용 예시를 보면 쉽다.

HTTP 메서드 + URL의미
GET /orders주문 목록
GET /orders/42주문 1개 조회
POST /orders주문 생성
PATCH /orders/42주문 수정
DELETE /orders/42주문 삭제

자원의 이름 (/orders/42) 은 명사, 행위는 HTTP 메서드 (GET, POST…) 로 분리하는 게 핵심.

Stateless — 서버가 상태를 기억하지 않는다

REST 의 또 다른 특징은 stateless.

  • 서버는 직전 요청을 기억하지 않는다.
  • 매 요청이 독립적 으로 처리된다.
  • 그래서 “누구인지” 를 매번 토큰으로 알려줘야 한다.

이게 뒤에 나올 인증/인가 와 연결되는 지점.

Supabase 의 경우 — PostgREST 가 자동 생성

Supabase 는 PostgREST 라는 도구를 써서 테이블을 만든 순간 자동으로 REST API 가 생긴다.

테이블 orders 를 만들면 /rest/v1/orders 같은 주소가 자동으로 열린다. 직접 그 URL 에 요청 보내기는 번거로우니까 SDK 를 제공한다.

const { data, error } = await supabase
  .from('orders')
  .select('*')
  .eq('user_id', userId);

이게 내부적으론 GET /rest/v1/orders?user_id=eq.<id> 같은 REST 호출.

문제는 여러 단계가 있을 때. 서버에서 돌아야 하는 복잡하고 민감한 로직 은 자동 REST 만으로 안 된다. 직접 함수를 짜야 한다. 이게 Edge Function 이다.

Supabase Edge Function 은 이 글 에서 따로 정리했었다.

2. 상태 코드 (Status Code)

요청이 어떻게 됐는지 알려주는 3자리 숫자. 어제 했던 에러 핸들링 글 의 재시도 로직에서도 이런 숫자들이 나왔다. 대역마다 의미가 정해져 있다.

대역의미대표 코드
2xx성공200 OK, 201 Created, 204 No Content
4xx클라이언트 잘못400 요청 형식 오류, 401 미인증, 403 권한 없음, 404 없음, 409 충돌
5xx서버 잘못500 서버 에러, 503 서비스 불가

핵심:

  • 4xx 가 떴을 때는 클라이언트 (호출하는 쪽) 가 뭔가 잘못한 것 — 재시도 의미 없음, 호출 코드를 고쳐야 함
  • 5xx 가 떴을 때는 서버 잘못 — 일시적이면 재시도하면 풀림

3. 멱등성 (Idempotency)

같은 요청을 여러 번 보내도, 한 번 보낸 것과 결과가 같은 성질.

수학에서 온 용어인데 API 설계의 핵심 개념. HTTP 메서드마다 멱등성이 정해져 있다.

메서드멱등?이유
GET읽기만 함
PUT전체 교체 — 같은 내용으로 100번 교체 = 1번 교체
DELETE한 번 지우면 끝, 또 보내도 이미 없음
POST호출마다 새로 생성 — 주문 2번 = 주문 2개

왜 멱등성이 중요한가

클라이언트가 요청 보냈는데 응답을 못 받으면, 재시도 한다. (네트워크 일시 끊김 같은 경우.) 그런데 POST 를 재시도하면?

  • 주문이 2번 들어가고
  • 결제가 2번 발생하는

불상사 가 생긴다.

멱등성 키 (Idempotency-Key)

그래서 POST 같은 비멱등 작업멱등성 키 를 붙인다.

  1. 클라이언트가 고유 키 를 같이 보냄 (예: UUID)
  2. 서버가 그 키를 본 적 있으면 재실행하지 않고, 이전 결과를 그대로 반환

Stripe, Toss 같은 결제 API 가 이걸 표준으로 강제한다. “이 요청을 두 번 보내면 사고가 나나?” 라고 자문해서 답이 “예” 면 멱등성 보장이 필요하다.

4. 페이지네이션 (Pagination)

데이터를 한 번에 다 주지 않고 조각 (페이지) 으로 나눠서 주는 것.

1만 개 데이터를 한 번에 받으면 클라이언트도 서버도 부담. 그래서 나눠 준다. 두 가지 방식이 있다.

Offset 방식 — 직관적이지만 함정 있음

GET /orders?limit=20&offset=40

“40번째부터 20개 줘” 라는 명령. 직관적이지만:

  • 뒤로 갈수록 느림 — 페이지 100 이면 DB 가 앞 1980 개를 건너뛰고 와야 함
  • 데이터가 추가/삭제되면 밀려서 중복 / 누락 발생 가능 — 1페이지 보고 다음 페이지 가는 사이에 누가 새 주문 넣으면 같은 항목을 두 번 보게 됨

Cursor 방식 — 빠르고 안정적이지만 점프 불가

GET /orders?limit=20&cursor=order_42_at_2026-06-18T13:00:00Z

“이 커서 뒤에서부터 20개 줘”. 이전 페이지의 마지막 ID/시각을 다음 요청에 전달.

  • 빠름 — DB 가 인덱스로 바로 시작 지점을 찾음
  • 데이터가 변해도 안정적 — 커서 기준점이 고정이라 새 데이터가 들어와도 안 밀림
  • 단점: 임의 페이지로 점프 불가 (“100페이지로” 같은 게 안 됨)

무한 스크롤 (Twitter, Instagram 같은) 은 거의 다 cursor 방식. 페이지 번호 UI (1, 2, 3…) 는 offset 방식.

5. 인증/인가 (Authentication / Authorization)

자주 헷갈리는데 다른 개념 이다.

인증 (Authentication)인가 (Authorization)
영어 약자AuthNAuthZ
질문”누구?""뭘 할 수 있어?”
검증 대상신원권한
실패 시 상태 코드401 Unauthorized403 Forbidden

인증 — JWT 토큰 흐름

REST 가 stateless 라 매 요청에 누구인지 알려줘야 한다.

  1. 로그인 → 서버가 JWT 토큰 발급
  2. 이후 매 요청의 헤더 에 토큰 첨부
    Authorization: Bearer eyJhbGciOiJI...
  3. 서버가 토큰을 검증해서 “누구” 인지 확인

인가 — 소유권 / 역할

  • 소유권 기반: “이 주문은 user_42 의 것이니까 user_42 만 수정 가능”
  • 역할 기반 (RBAC): “admin 역할이면 모든 주문 조회 가능”

상태 코드와의 매칭이 중요:

  • 401 — 토큰이 없거나 잘못됨 = 인증 실패
  • 403 — 토큰은 유효한데 권한 없음 = 인가 실패

보너스. CORS (Cross-Origin Resource Sharing)

브라우저는 보안상 다른 출처 (origin) 의 요청을 막는다. 이를 Same-Origin Policy 라고 한다.

출처 = 프로토콜 + 도메인 + 포트. 하나라도 다르면 “다른 출처”.

근데 그럼 https://my-app.com 에서 https://api.my-app.com 호출 못 한다. 도메인이 다르니까. 그래서 등장한 게 CORS안전한 경우엔 출처가 달라도 허용해주는 기술.

누가 허용하는가 — 서버

CORS 허용은 서버가 응답 헤더로 알려주는 일 이다.

Access-Control-Allow-Origin: https://my-app.com

브라우저는 응답에 이 헤더가 있는지 확인하고, 있으면 클라이언트 코드에 응답을 넘긴다. 없으면 차단.

Preflight — 미리 물어보는 요청

위험할 수 있는 요청 (예: 본문이 있는 POST, 커스텀 헤더 등) 은 본 요청 전에 미리 OPTIONS 요청 으로 “이거 보내도 돼?” 라고 묻는다. 서버가 OK 하면 그때 본 요청을 보낸다.

이걸 preflight 라고 한다. 개발할 때 “왜 OPTIONS 요청이 두 개씩 가지?” 하는 게 이 패턴.

회고

DB → API 흐름으로 오니까 “어떻게 자원을 정의하고, 어떻게 외부에 노출할 것인가” 라는 큰 그림이 잡힌다. 이전엔 API 라고 하면 “함수 호출 같은 것” 정도로 두루뭉술했는데, 이제는:

  • 자원을 URL 로 가리키고 (REST)
  • 결과를 숫자로 알려주고 (상태 코드)
  • 같은 요청이 두 번 와도 안전하게 만들고 (멱등성)
  • 데이터를 잘라서 주고 (페이지네이션)
  • 누구인지 / 뭘 할 수 있는지 확인하고 (인증/인가)
  • 브라우저의 보안 정책을 통과시키는 것 (CORS)

이 6가지가 한 줄기로 엮여있다 는 게 보인다.

AI 에게 “API 만들어줘” 라고 했을 때 결과물을 검토할 때도 위 6가지를 체크리스트로 쓸 수 있을 듯. 어휘가 잡히면 검수 능력이 같이 잡힌다.

더 공부해볼 것

1. GraphQL — REST 외의 다른 길

  • REST 가 “리소스 중심” 이라면 GraphQL 은 “쿼리 중심”
  • 클라이언트가 필요한 필드만 골라서 받음 (오버페치 / 언더페치 해결)
  • 단점: 캐싱이 어렵고, 학습 곡선 가파름
  • 언제 REST vs GraphQL 을 쓰는가의 기준
  • GraphQL 공식 사이트

2. tRPC — TypeScript 진영의 답

  • 풀스택 TypeScript 환경에서 REST/GraphQL 대신 쓰는 RPC
  • 클라이언트가 서버 함수를 타입 안전하게 호출 (스키마 정의 없이 type inference)
  • Next.js 와 자주 같이 씀
  • tRPC 공식

3. OpenAPI / Swagger — API 문서화 표준

  • API 의 모든 엔드포인트 / 요청 / 응답을 스키마로 정의
  • 스키마 → 자동 문서, 자동 클라이언트 SDK 생성
  • 팀 작업에서 사실상 표준
  • OpenAPI Specification

4. JWT 의 약점 + 대안

  • JWT 는 한 번 발급하면 만료까지 무효화 불가 (탈취 시 위험)
  • 짧은 access token + refresh token 패턴
  • 세션 기반 인증 의 부활 (Lucia, Auth.js 등)
  • HttpOnly Cookie vs Bearer Header 의 trade-off

5. Rate Limiting — DoS 와 비용 방어

  • 한 클라이언트가 너무 많이 호출하면 차단
  • Token Bucket / Leaky Bucket / Sliding Window 알고리즘
  • 상태 코드 429 Too Many RequestsRetry-After 헤더
  • API 게이트웨이 (Kong, Tyk, AWS API Gateway) 에서 흔히 구현

Edit page