Waylog Blog
← 목록으로 돌아가기

API 설계 모범 사례: RESTful API부터 GraphQL까지

Backend

API Design

API(Application Programming Interface)는 소프트웨어 시스템 간의 계약입니다. 잘 설계된 API는 사용하기 쉽고, 확장하기 편하며, 오래 유지됩니다. 이 글에서는 실무에서 검증된 API 설계 원칙과 패턴들을 살펴봅니다.

1. RESTful API 설계 원칙

1.1 리소스 중심 설계

REST의 핵심은 리소스(Resource)입니다. URL은 리소스를 나타내고, HTTP 메서드로 행위를 표현합니다. "/users"는 사용자 컬렉션을, "/users/123"은 특정 사용자를 나타냅니다. "/getUser"나 "/createUser" 같은 동사 기반 URL은 피하세요.

컬렉션은 복수형, 단일 리소스는 식별자로 구분합니다. 중첩 리소스는 계층 관계를 표현합니다. "/users/123/orders"는 특정 사용자의 주문 목록을 의미합니다. 단, 중첩이 깊어지면 복잡해지므로 2단계 정도로 제한하는 것이 좋습니다.

1.2 HTTP 메서드 활용

GET은 리소스 조회에, POST는 생성에, PUT은 전체 교체에, PATCH는 부분 수정에, DELETE는 삭제에 사용합니다. 각 메서드의 의미를 정확히 따르세요.

GET과 DELETE는 멱등성(Idempotent)을 가집니다. 같은 요청을 여러 번 보내도 결과가 동일합니다. PUT도 멱등합니다. POST는 멱등하지 않습니다. 같은 요청을 두 번 보내면 리소스가 두 개 생성될 수 있습니다.

1.3 상태 코드 활용

적절한 HTTP 상태 코드를 반환하세요. 200 OK는 성공, 201 Created는 리소스 생성, 204 No Content는 성공했지만 반환할 본문이 없을 때 사용합니다. 400 Bad Request는 클라이언트 오류, 401 Unauthorized는 인증 실패, 403 Forbidden은 권한 없음, 404 Not Found는 리소스 없음, 409 Conflict는 충돌, 500 Internal Server Error는 서버 오류입니다.

상태 코드만으로 부족하면 응답 본문에 상세한 오류 정보를 포함하세요. 에러 코드, 메시지, 상세 설명, 해결 방법 등을 구조화된 형식으로 제공합니다.

2. 페이지네이션과 필터링

2.1 페이지네이션

대량의 데이터를 한 번에 반환하면 성능과 사용성 모두 저하됩니다. 오프셋 기반 페이지네이션은 page와 limit 파라미터를 사용합니다. 구현이 간단하지만, 데이터가 추가/삭제될 때 결과가 일관되지 않을 수 있습니다.

커서 기반 페이지네이션은 마지막 항목의 ID를 커서로 사용합니다. 일관된 결과를 보장하고 대용량 데이터에 효율적이지만, 구현이 복잡합니다. 다음 페이지 URL을 응답에 포함시키면 클라이언트가 페이지네이션 로직을 몰라도 됩니다.

2.2 필터링과 정렬

쿼리 파라미터로 필터 조건을 받습니다. "/users?status=active&role=admin"처럼 단순한 등호 조건부터, 범위 조건, 검색어 등 다양한 필터를 지원할 수 있습니다. 복잡한 필터는 별도의 문법(예: Ops 접미사, "/users?age_gte=18")을 정의하거나, POST 요청으로 JSON 필터를 받는 방법도 있습니다.

정렬은 sort 파라미터로 처리합니다. "/users?sort=created_at"은 오름차순, "/users?sort=-created_at"은 내림차순(마이너스 접두사)으로 하는 것이 일반적입니다.

3. 버전 관리

API는 시간이 지나면 변경됩니다. 기존 클라이언트를 깨뜨리지 않으면서 새로운 기능을 추가하려면 버전 관리가 필요합니다.

URL 경로에 버전을 포함하는 방법("/v1/users")이 가장 명확하고 널리 사용됩니다. 헤더(Accept: application/vnd.example.v1+json)를 사용하는 방법도 있지만, 테스트하기 번거롭습니다. 쿼리 파라미터("?version=1")는 캐싱과 충돌할 수 있어 권장되지 않습니다.

가능하면 하위 호환성을 유지하세요. 필드 추가는 보통 안전합니다. 필드 제거나 타입 변경은 새 버전을 만들어야 합니다. 오래된 버전은 일정 기간 유지하고, 마이그레이션 가이드를 제공한 후 종료하세요.

4. 인증과 보안

4.1 인증 방식

API Key는 가장 단순하지만 보안이 취약합니다. 헤더나 쿼리 파라미터로 전송합니다. OAuth 2.0은 표준화된 인증 프레임워크로, 액세스 토큰과 리프레시 토큰을 사용합니다. JWT(JSON Web Token)는 토큰 자체에 정보를 담아 서버에서 세션을 유지할 필요가 없습니다.

4.2 보안 모범 사례

HTTPS는 필수입니다. 요청과 응답을 암호화하여 중간자 공격을 방지합니다. Rate Limiting으로 과도한 요청을 차단하세요. 시간당 요청 수를 제한하고, 429 Too Many Requests를 반환합니다.

민감한 데이터는 응답에 포함하지 않거나 마스킹하세요. 비밀번호는 절대 반환하지 않습니다. 입력값은 항상 검증하고, SQL 인젝션, XSS 등 일반적인 공격을 방어하세요. CORS(Cross-Origin Resource Sharing) 설정도 정확히 하세요.

5. GraphQL 고려하기

REST가 유일한 선택지는 아닙니다. Facebook이 개발한 GraphQL은 클라이언트가 필요한 데이터만 정확히 요청할 수 있게 합니다.

특징RESTGraphQL
데이터 페칭고정된 구조, 오버/언더페칭 발생 가능클라이언트가 필요한 필드만 요청
엔드포인트리소스별로 다수 존재 (/users, /posts)단일 엔드포인트 (/graphql)
버전 관리URL v1, v2 등으로 분리스키마 진화(Evolution)로 단일 버전 유지
캐싱HTTP 캐싱 활용 용이별도 설정 필요 (Apollo Client 등)

스키마를 정의하면 타입 시스템의 이점을 누릴 수 있습니다. Introspection으로 API 문서가 자동 생성됩니다. BFF(Backend for Frontend) 패턴과 함께 사용하면 마이크로서비스 환경에서 강력한 힘을 발휘합니다.

6. 문서화

좋은 API도 문서가 없으면 사용하기 어렵습니다. OpenAPI(Swagger) 명세를 작성하면, 문서 사이트를 자동 생성하고, 클라이언트 SDK를 생성하고, 테스트할 수 있습니다.

예제 요청과 응답을 충분히 제공하세요. 에러 케이스도 문서화하세요. 인증 방법, rate limit, 버전 정책 등 운영 정보도 포함하세요. 코드와 문서가 일치하도록 CI에서 검증하는 것도 좋습니다.

결론

API 설계는 한 번 하고 끝나는 것이 아닙니다. 클라이언트의 요구는 변하고, 시스템은 성장합니다. 일관된 원칙을 따르되 유연하게 대응하세요. 가장 중요한 것은 API를 사용하는 개발자의 경험입니다. 그들이 쉽고 즐겁게 사용할 수 있는 API가 좋은 API입니다.

추가 팁: API 버저닝 전략

API를 진화시키면서 기존 클라이언트의 호환성을 유지하는 것은 어려운 과제입니다. URL 기반 버저닝(/api/v1/, /api/v2/)은 직관적이지만 코드 중복이 발생하기 쉽고, Header 기반 버저닝은 URL이 깔끔하지만 테스트가 복잡해집니다. API 엔드포인트를 폐기할 때는 최소 6개월의 유예 기간을 두고 Deprecation과 Sunset 헤더를 포함하여 클라이언트에게 명시적으로 알려야 합니다.

X. 깊게 파헤치는 대규모 백엔드 API 설계 패턴과 버전 관리 (Deep Dive)

조직의 프로덕트가 급성장하여 마이크로서비스(MSA)로 전환되고, 외부 고객을 위한 Public OpenAPI가 공개되는 순간 API 설계의 패러다임은 완전히 새로운 국면을 맞이하게 됩니다.

1. 진화적 아키텍처와 API 버저닝 전략 (Versioning Strategy)

한 번 배포된 모바일 앱 클라이언트는 절대로 사용자가 일괄 업데이트하지 않는다는 무서운 원칙을 아십니까?
새로운 데이터 스키마가 필요하다고 해서 함부로 기존 모델 객체의 필드를 지우거나 구조를 바꿔서는 안 됩니다.
가장 세련된 조직은 URI 체계에 버전을 하드코딩(/v1/users)하는 것을 넘어, HTTP Accept Header 단위로 Content Negotiation을 구축하거나 (예: Accept: application/vnd.company.user-v2+json), 하위 호환성을 유지하기 위해 데이터 변환 브릿지(Adapter)를 미들웨어에 배치합니다. 사용자는 구버전을 호출하지만 코어 시스템은 모두 최신 V3 아키텍처로 흐르게 만드는 것, 이것이 진화적 아키텍처 운영의 핵심입니다.

2. Rate Limiting과 백프레셔(Backpressure) 통제

거대한 트래픽이 집중되거나 악의적인 크롤링 어뷰징이 들어올 때 시스템을 방어하는 것도 인터페이스 디자인의 영역입니다. API Gateway 레벨에서 토큰 버킷(Token Bucket)이나 리키 버킷(Leaky Bucket) 알고리즘을 도입하여 초당 요청(TPS)을 제한하는 것은 기본입니다.
핵심은 프론트엔드/클라이언트 코어에 있습니다. 백엔드가 "429 Too Many Requests" 헤더(가령 Retry-After: 30)를 쏘아주면, 클라이언트는 무지성 재시도(Retry) 폭탄을 날리는 대신 지수적 백오프(Exponential Backoff with Jitter) 패턴을 적용하여 트래픽 부하를 점진적으로 늦춰야 합니다. 서로 주고받는 HTTP 헤더 속에 오케스트라와 같은 통신 규약을 심어 놓음으로써 극한의 고가용성(High Availability) 아키텍처가 완성되는 것입니다.

X. 깊게 파헤치는 대규모 백엔드 API 설계 패턴과 버전 관리 (Deep Dive)

조직의 프로덕트가 급성장하여 마이크로서비스(MSA)로 전환되고, 외부 고객을 위한 Public OpenAPI가 공개되는 순간 API 설계의 패러다임은 완전히 새로운 국면을 맞이하게 됩니다.

1. 진화적 아키텍처와 API 버저닝 전략 (Versioning Strategy)

한 번 배포된 모바일 앱 클라이언트는 절대로 사용자가 일괄 업데이트하지 않는다는 무서운 원칙을 아십니까?
새로운 데이터 스키마가 필요하다고 해서 함부로 기존 모델 객체의 필드를 지우거나 구조를 바꿔서는 안 됩니다.
가장 세련된 조직은 URI 체계에 버전을 하드코딩(/v1/users)하는 것을 넘어, HTTP Accept Header 단위로 Content Negotiation을 구축하거나 (예: Accept: application/vnd.company.user-v2+json), 하위 호환성을 유지하기 위해 데이터 변환 브릿지(Adapter)를 미들웨어에 배치합니다. 사용자는 구버전을 호출하지만 코어 시스템은 모두 최신 V3 아키텍처로 흐르게 만드는 것, 이것이 진화적 아키텍처 운영의 핵심입니다.

2. Rate Limiting과 백프레셔(Backpressure) 통제

거대한 트래픽이 집중되거나 악의적인 크롤링 어뷰징이 들어올 때 시스템을 방어하는 것도 인터페이스 디자인의 영역입니다. API Gateway 레벨에서 토큰 버킷(Token Bucket)이나 리키 버킷(Leaky Bucket) 알고리즘을 도입하여 초당 요청(TPS)을 제한하는 것은 기본입니다.
핵심은 프론트엔드/클라이언트 코어에 있습니다. 백엔드가 "429 Too Many Requests" 헤더(가령 Retry-After: 30)를 쏘아주면, 클라이언트는 무지성 재시도(Retry) 폭탄을 날리는 대신 지수적 백오프(Exponential Backoff with Jitter) 패턴을 적용하여 트래픽 부하를 점진적으로 늦춰야 합니다. 서로 주고받는 HTTP 헤더 속에 오케스트라와 같은 통신 규약을 심어 놓음으로써 극한의 고가용성(High Availability) 아키텍처가 완성되는 것입니다.

X. 깊게 파헤치는 대규모 백엔드 API 설계 패턴과 버전 관리 (Deep Dive)

조직의 프로덕트가 급성장하여 마이크로서비스(MSA)로 전환되고, 외부 고객을 위한 Public OpenAPI가 공개되는 순간 API 설계의 패러다임은 완전히 새로운 국면을 맞이하게 됩니다.

1. 진화적 아키텍처와 API 버저닝 전략 (Versioning Strategy)

한 번 배포된 모바일 앱 클라이언트는 절대로 사용자가 일괄 업데이트하지 않는다는 무서운 원칙을 아십니까?
새로운 데이터 스키마가 필요하다고 해서 함부로 기존 모델 객체의 필드를 지우거나 구조를 바꿔서는 안 됩니다.
가장 세련된 조직은 URI 체계에 버전을 하드코딩(/v1/users)하는 것을 넘어, HTTP Accept Header 단위로 Content Negotiation을 구축하거나 (예: Accept: application/vnd.company.user-v2+json), 하위 호환성을 유지하기 위해 데이터 변환 브릿지(Adapter)를 미들웨어에 배치합니다. 사용자는 구버전을 호출하지만 코어 시스템은 모두 최신 V3 아키텍처로 흐르게 만드는 것, 이것이 진화적 아키텍처 운영의 핵심입니다.

2. Rate Limiting과 백프레셔(Backpressure) 통제

거대한 트래픽이 집중되거나 악의적인 크롤링 어뷰징이 들어올 때 시스템을 방어하는 것도 인터페이스 디자인의 영역입니다. API Gateway 레벨에서 토큰 버킷(Token Bucket)이나 리키 버킷(Leaky Bucket) 알고리즘을 도입하여 초당 요청(TPS)을 제한하는 것은 기본입니다.
핵심은 프론트엔드/클라이언트 코어에 있습니다. 백엔드가 "429 Too Many Requests" 헤더(가령 Retry-After: 30)를 쏘아주면, 클라이언트는 무지성 재시도(Retry) 폭탄을 날리는 대신 지수적 백오프(Exponential Backoff with Jitter) 패턴을 적용하여 트래픽 부하를 점진적으로 늦춰야 합니다. 서로 주고받는 HTTP 헤더 속에 오케스트라와 같은 통신 규약을 심어 놓음으로써 극한의 고가용성(High Availability) 아키텍처가 완성되는 것입니다.

X. 깊게 파헤치는 대규모 백엔드 API 설계 패턴과 버전 관리 (Deep Dive)

조직의 프로덕트가 급성장하여 마이크로서비스(MSA)로 전환되고, 외부 고객을 위한 Public OpenAPI가 공개되는 순간 API 설계의 패러다임은 완전히 새로운 국면을 맞이하게 됩니다.

1. 진화적 아키텍처와 API 버저닝 전략 (Versioning Strategy)

한 번 배포된 모바일 앱 클라이언트는 절대로 사용자가 일괄 업데이트하지 않는다는 무서운 원칙을 아십니까?
새로운 데이터 스키마가 필요하다고 해서 함부로 기존 모델 객체의 필드를 지우거나 구조를 바꿔서는 안 됩니다.
가장 세련된 조직은 URI 체계에 버전을 하드코딩(/v1/users)하는 것을 넘어, HTTP Accept Header 단위로 Content Negotiation을 구축하거나 (예: Accept: application/vnd.company.user-v2+json), 하위 호환성을 유지하기 위해 데이터 변환 브릿지(Adapter)를 미들웨어에 배치합니다. 사용자는 구버전을 호출하지만 코어 시스템은 모두 최신 V3 아키텍처로 흐르게 만드는 것, 이것이 진화적 아키텍처 운영의 핵심입니다.

2. Rate Limiting과 백프레셔(Backpressure) 통제

거대한 트래픽이 집중되거나 악의적인 크롤링 어뷰징이 들어올 때 시스템을 방어하는 것도 인터