

URI 설계에서 가장 중요한 것은 리소스 식별이다.
여기서 리소스란 "회원정보를 등록하라", "회원정보를 조회하라", "회원정보를 수정하라"와 같은 요청에서 회원정보에 해당한다. 이처럼 리소스를 URI로 표현하고, 행동(등록, 조회, 수정, 삭제)과 같은 부분은 HTTP Method를 활용하여 구분한다.
GET /members GET /members/{id} POST /members PUT /members/{id} DELETE /members/{id} /members/{id})를 가지지만, 작업의 성격을 HTTP Method로 구분하므로 명확하고 간결한 설계가 가능하다.GET /search?q=spring&hl=ko HTTP/1.1
Host: www.google.com
GET은 리소스를 조회하기 위한 HTTP Method로, 요청 메시지에 포함되어 패킷으로 서버에 전달된다. 조회 시 추가 데이터를 서버로 전달하려면 쿼리 파라미터(예: ?q=spring&hl=ko)를 사용하는 것이 권장된다. 메시지 바디를 통해 데이터를 전달할 수도 있지만, 이는 지원하지 않는 환경이 많아 표준적으로 사용되지 않는다. 따라서 항상 URL에 쿼리 파라미터로 데이터를 전달하여 호환성과 안정성을 유지하도록 한다.
POST /members HTTP/1.1
Content-Type: application/json
{
"username": "spring",
"age": 25
}
POST는 단순히 CREATE의 의미보다는, HTTP 메시지 바디를 통해 서버로 들어오는 데이터를 "처리"하라는 메서드이다. 주로 신규 리소스 등록이나 프로세스 처리에 사용된다.
들어온 바디 데이터를 어떻게 처리할지는 서버 리소스의 몫이며, 정해진 방식은 없다. 새 리소스를 생성할 수도 있고, 생성하지 않을 수도 있으며, 단순히 들어온 데이터를 기반으로 다른 논리를 처리하는 경우도 있다.
또한, 다른 HTTP 메서드로 처리하기 애매한 경우에는 POST를 활용하여 요청을 처리할 수 있다.
PUT /members HTTP/1.1
Content-Type: application/json
{
"username": "spring",
"age": 25
}
PUT은 요청 데이터의 형식이 POST와 크게 다르지 않지만, 리소스를 "완전히 대체"하는 개념이다. 리소스가 존재하지 않으면 생성하고, 이미 존재한다면 덮어쓴다.
PUT의 가장 중요한 차이점은 클라이언트가 리소스를 식별한다는 점이다.
즉, 서버가 리소스를 찾아서 덮거나 생성하는 것이 아니라, 클라이언트가 지정한 URL에 가서 리소스를 덮거나 생성하는 방식이다.
또한, PUT은 리소스를 완전히 대체하기 때문에, 예를 들어 HTTP 메시지 바디에 age만 담긴 JSON을 /members로 보낸다면, 기존의 username 필드는 사라지게 된다. 이는 부분 업데이트가 아니라 리소스를 전면 교체한다는 점에서 POST와 큰 차이가 있다.
완전히 대체에 따르는 단점을 개선하기 위해 등장한 것이 PATCH이다.
PUT은 리소스를 완전히 대체하는 동작으로, 수정이라기보다는 사실상 새로 만드는 개념에 가깝다. 이를 보완하기 위해 PATCH가 후에 등장했다.
PATCH는 리소스의 일부만 변경하는 데 사용된다. 클라이언트는 변경될 부분만 지정하여 서버로 데이터를 전달하며, 서버는 전달받은 PATCH 바디 데이터를 참고하여 해당 리소스의 필요한 부분만 수정한다.
이 방식은 리소스를 완전히 대체하는 PUT보다 효율적이며, 변경된 부분만 반영할 수 있는 유연함을 제공한다.
DELETE는 서버에서 특정 리소스를 제거하는 HTTP Method이다. 삭제 대상은 URI를 통해 명확히 식별되며, 요청이 처리되면 서버는 리소스를 제거한 뒤 성공 여부를 응답으로 반환한다. DELETE는 멱등성을 가지므로 동일한 요청을 여러 번 보내도 결과는 항상 동일하다. 예를 들어, 이미 삭제된 리소스에 DELETE 요청을 보내도 리소스는 여전히 삭제된 상태로 유지된다. 요청이 성공하면 일반적으로 204 No Content를 반환하며, 경우에 따라 200 OK와 삭제된 리소스 정보를 반환할 수도 있다. 삭제할 리소스가 존재하지 않으면 404 Not Found가 응답된다. DELETE는 처리 시 리소스를 완전히 제거하므로 복구가 불가능하며, 이러한 이유로 일부 시스템에서는 실제 삭제 대신 소프트 삭제를 사용하기도 한다.

호출해도 리소스가 변경되지 않는 메서드를 "안전"하다고 말한다. 예를 들어, GET과 HEAD 요청은 데이터를 조회만 하므로 리소스를 변경하지 않아 안전하다고 간주된다.
누군가는 "악의적으로 해당 메서드를 계속 호출하면 로그가 쌓여 서버에 장애가 발생할 수 있지 않느냐?"라고 반문할 수 있지만, "안전"이라는 속성은 오직 리소스의 변경 여부만을 기준으로 평가하며, 이러한 외부적인 문제는 고려하지 않는다.
멱등(idempotent) 메서드란, 몇 번을 호출하든 1만 번을 호출하든 결과가 동일한 메서드를 말한다. GET은 당연히 멱등하며, PUT도 마찬가지이다. 요청 과정에서 어떤 일이 벌어지더라도, 최종 결과는 항상 PUT이 전달한 데이터로 대체된다. DELETE 역시 동일한 요청을 여러 번 보낸다고 해서 결과가 달라지지 않는다.
하지만 POST는 멱등하지 않다. 예를 들어, POST 요청이 결제 시도를 처리한다면, 이를 1만 번 요청할 경우 A 손님의 카드로 B 물품을 1만 번 결제 시도하게 된다. 결과가 호출 횟수에 따라 달라질 수 있으므로, POST는 멱등의 특성을 가지지 않는다.
HTTP에서 응답 결과를 캐시할 수 있는 메서드는 서버와의 통신을 줄여 성능을 높일 수 있다. GET은 요청 메시지가 동일하다면, HTTP 통신 없이도 이전에 받은 응답 메시지를 캐시에서 가져올 수 있다. 이러한 방식을 완전 캐시 히트 (Full Cache Hit)라고 한다.
캐시를 활용하여 조건부 요청도 가능하다. 예를 들어, 캐시를 이용할 수 있는 GET 요청에서 이전과 요청 조건이 달라지지 않았다면, 최소한의 서버 통신으로 응답 데이터가 같은지 확인한 후, 같다면 캐시된 데이터를 사용할 수 있다.
POST는 캐시 관리에서 더 복잡한 문제를 야기한다. POST는 Body 메시지를 처리하며, 요청 데이터를 구분하기 위한 키로 URL을 사용하는 경우 문제가 발생할 수 있다.
예를 들어:
/api/submit: POST {"username": "user1", "password": "pass1"} /api/submit: POST {"username": "user2", "password": "pass2"} 위와 같이 Body 메시지가 다르면, 동일한 URL을 사용하더라도 요청이 구분되지 않는다. HTTP 캐시는 보통 URL을 주요 키로 사용하기 때문에, POST 요청에서는 URL 외에 Body 데이터를 포함한 고유한 캐시 키를 구성해야 한다. 이는 복잡성과 추가적인 관리 부담을 초래한다.
결론적으로, GET은 캐싱이 용이하며, 캐시를 활용한 최적화를 쉽게 적용할 수 있는 반면, POST는 캐싱에 불리하다. POST 요청은 상태 저장이 필요한 경우가 많고, 캐시에서 처리해야 할 변수가 더 많아 관리 난이도가 높아진다.