RESTful API에서 올바른 HTTP 메서드 선택하기
REST API를 설계할 때 가장 혼란스러운 부분 중 하나는 PUT과 POST 메서드를 언제, 어떻게 사용해야 하는지 결정하는 것입니다. 두 메서드 모두 서버에 데이터를 전송하는 데 사용되지만, 그 의도와 동작 방식에는 중요한 차이가 있습니다. 이번 글에서는, 실제 개발 경험을 바탕으로 이 두 메서드의 차이점을 명확히 알아보겠습니다.
멱등성(Idempotency)이란?
멱등성이란, 같은 요청을 여러 번 반복해서 보내더라도 서버의 상태와 응답이 처음 한 번 보냈을 때와 동일하게 유지되는 성질을 말합니다.
- 예시: 같은 PUT 요청을 여러 번 보내도 리소스의 상태가 변하지 않음
- 반례: 같은 POST 요청을 여러 번 보내면 리소스가 여러 개 생성될 수 있음
핵심 차이점
1. 멱등성(Idempotency)
PUT은 멱등(idempotent)합니다. 즉, 동일한 PUT 요청을 여러 번 보내도 결과는 항상 같습니다. 첫 번째 요청이 리소스를 생성하거나 업데이트하고, 이후의 동일한 요청은 같은 상태를 유지합니다.
POST는 멱등하지 않습니다. 동일한 POST 요청을 여러 번 보내면 서버에 여러 개의 리소스가 생성될 수 있습니다.
// PUT 예시: 멱등성
PUT /articles/123
{ "title": "PUT vs POST", "content": "..." }
// 위 요청을 여러 번 보내도 ID 123 글은 항상 같은 상태가 됨
// POST 예시: 비멱등성
POST /articles
{ "title": "PUT vs POST", "content": "..." }
// 위 요청을 여러 번 보내면 동일한 내용의 글이 여러 개 생성됨
2. 리소스 식별
PUT은 리소스의 정확한 위치(URI)를 알고 있어야 합니다. 클라이언트가 리소스의 URI를 지정합니다.
POST는 리소스 컬렉션에 요청하며, 서버가 새 리소스의 URI를 결정합니다.
// PUT: 클라이언트가 URI 지정
PUT /users/42
{ "name": "홍길동", "email": "hong@example.com" }
// POST: 서버가 URI 결정
POST /users
{ "name": "홍길동", "email": "hong@example.com" }
// 서버는 /users/42와 같은 URI를 할당할 수 있음
3. 용도
PUT은 주로 리소스 업데이트에 사용됩니다. 리소스가 없으면 생성할 수도 있습니다(서버 구현에 따라 다름).
POST는 주로 새 리소스 생성에 사용됩니다. 또한 프로세스 실행, 컬렉션에 데이터 추가 등 다양한 작업에도 사용될 수 있습니다.
실제 개발에서 겪을 수 있는 사례
사례 1: 사용자 프로필 업데이트
사용자 프로필 업데이트 API를 설계할 때 발생할 수 있는 상황입니다. 처음에 POST 메서드를 사용한다고 가정해 보겠습니다.
POST /api/users/123/profile
{ "name": "김철수", "bio": "개발자입니다" }
이런 설계에서는 네트워크 오류로 클라이언트가 동일 요청을 재시도할 경우, 서버가 이를 새로운 요청으로 처리하여 중복 처리 문제가 발생할 수 있습니다.
이러한 문제를 방지하기 위해 PUT 메서드를 사용하는 것이 더 적합합니다.
PUT /api/users/123/profile
{ "name": "김철수", "bio": "개발자입니다" }
이렇게 하면 동일 요청의 중복 처리 문제를 방지할 수 있고, API의 의도도 더 명확해집니다.
사례 2: 댓글 시스템
블로그 댓글 시스템을 구현할 때는 POST 메서드가 더 적합한 경우입니다.
POST /api/posts/456/comments
{ "content": "좋은 글이네요!" }
이 경우에는 클라이언트가 새 댓글의 ID를 미리 알 수 없고, 동일한 내용의 댓글이 여러 개 작성되는 것이 자연스러운 동작이기 때문에 POST가 더 적합합니다.
부분 업데이트: PATCH의 역할
실제 개발에서는 리소스의 일부만 업데이트해야 할 때가 많습니다. 이럴 때는 PATCH 메서드가 더 적합할 수 있습니다.
PUT은 리소스 전체를 교체하는 반면, PATCH는 리소스의 일부만 업데이트합니다.
// PUT: 리소스 전체 교체
PUT /users/123
{ "name": "홍길동", "email": "hong@example.com", "role": "admin" }
// PATCH: 일부만 업데이트
PATCH /users/123
{ "role": "admin" }
선택 가이드라인
새로운 리소스를 생성하고 URI를 서버가 결정하게 하려면 → POST
기존 리소스를 완전히 교체하거나, URI를 클라이언트가 지정해서 생성하려면 → PUT
리소스의 일부만 업데이트하려면 → PATCH
단순히 리소스를 조회하려면 → GET
리소스를 삭제하려면 → DELETE
POST만 사용하는 방식: RPC 스타일 API
참고: AWS RPC와 REST의 차이점은 무엇인가요?
GET 메서드나 URL에 ID가 노출되는 것이 보안에 좋지 않다고 생각하여 모든 요청을 POST 메서드로만 처리하는 방식을 채택하는 경우도 있습니다. 이러한 접근법은 주로 RPC(Remote Procedure Call) 스타일 API라고 불립니다.
RPC 스타일 API의 특징
// 모든 요청을 단일 엔드포인트로 처리
POST /api/process
{
"action": "getUserProfile",
"userId": 123
}
// 업데이트 요청도 동일한 형태
POST /api/process
{
"action": "updateUserProfile",
"userId": 123,
"data": { "name": "홍길동", "email": "hong@example.com" }
}
이 접근 방식의 장단점
장점:
- URL에 민감한 정보가 노출되지 않음 (모든 데이터가 요청 본문에 포함)
- 방화벽이나 프록시 설정이 단순해짐 (단일 엔드포인트만 열어두면 됨)
- 프론트엔드 코드가 단순해질 수 있음 (항상 동일한 형태의 요청)
단점:
- REST 원칙과 맞지 않으며, HTTP 프로토콜의 의미론적 장점을 활용하지 못함
- 캐싱이 어려워짐 (GET 요청의 자연스러운 캐싱 이점 상실)
- API 문서화와 이해가 더 어려워질 수 있음
- 표준 HTTP 상태 코드 활용이 제한적
보안 측면에서의 고려사항
URL에 ID가 노출되는 것이 실제로 보안 위험인지는 상황에 따라 다릅니다:
공개 ID vs 민감한 ID: 일반적인 리소스 ID는 대부분 URL에 노출되어도 큰 문제가 없습니다. 하지만 주민등록번호나 계좌번호 같은 민감한 식별자는 당연히 URL에 노출되면 안 됩니다.
HTTPS의 중요성: 모든 API 통신은 HTTPS를 통해 이루어져야 합니다. HTTPS 사용 시 URL을 포함한 모든 요청 데이터는 암호화됩니다.
서버 로그 고려: URL에 포함된 정보는 서버 로그에 기록될 가능성이 높습니다. 따라서 매우 민감한 정보는 요청 본문에 포함하는 것이 좋습니다.
결론
PUT과 POST의 차이를 이해하는 것은 RESTful API 설계의 기본입니다. 멱등성, 리소스 식별 방식, 그리고 주요 용도의 차이를 이해하면 보다 명확하고 예측 가능한 API를 설계할 수 있습니다.
특히 실제 개발에서는 이론적인 차이를 넘어, 시스템의 구체적인 요구사항과 동작 방식을 고려하여 적절한 HTTP 메서드를 선택해야 합니다. POST가 적합한 상황이 있고, PUT이 적합한 상황이 있으며, 때로는 PATCH가 가장 좋은 선택일 수 있습니다.
한편, 보안이나 특수한 요구사항으로 인해 RPC 스타일의 단일 메서드 API를 선택할 수도 있지만, 이는 REST의 이점을 포기하는 대가로 오므로 신중히 결정해야 합니다.
올바른 HTTP 메서드 선택은 API의 의도를 명확히 하고, 클라이언트와 서버 간의 상호작용을 더 예측 가능하게 만들어 줍니다.