1. HTTP 메서드는 왜 구분할까?
REST API를 만들 때 URL만 정하면 끝이라고 생각하기 쉽습니다.
/api/posts
/api/posts/1하지만 같은 URL이라도 어떤 작업을 하려는지에 따라 의미가 달라집니다.
GET /api/posts → 게시글 목록 조회
POST /api/posts → 게시글 생성
GET /api/posts/1 → 1번 게시글 조회
PUT /api/posts/1 → 1번 게시글 전체 수정
PATCH /api/posts/1 → 1번 게시글 일부 수정
DELETE /api/posts/1 → 1번 게시글 삭제여기서 GET, POST, PUT, PATCH, DELETE 같은 값을 HTTP 메서드라고 부릅니다. HTTP 메서드는 클라이언트가 서버에 “이 리소스에 대해 어떤 동작을 하고 싶은지”를 알려주는 약속입니다.
Spring MVC에서는 HTTP 메서드별로 요청을 메서드에 연결하기 위해 @GetMapping, @PostMapping, @PutMapping, @PatchMapping, @DeleteMapping 같은 어노테이션을 제공합니다.
이전 글인 @RestController 기초에서 REST API 컨트롤러의 기본 구조를 살펴봤습니다. 이번 글에서는 그 컨트롤러 안에서 HTTP 요청을 어떻게 메서드별로 나누어 처리하는지 알아보겠습니다.
2. HTTP 메서드 한눈에 보기
입문 단계에서 자주 쓰는 HTTP 메서드는 다음 다섯 가지입니다.
| HTTP 메서드 | 주 용도 | 예시 |
|---|---|---|
GET | 리소스 조회 | 게시글 목록 조회 |
POST | 새 리소스 생성, 처리 요청 | 게시글 생성 |
PUT | 리소스 전체 교체 | 게시글 전체 수정 |
PATCH | 리소스 일부 수정 | 게시글 제목만 수정 |
DELETE | 리소스 삭제 | 게시글 삭제 |
Spring에서는 각각에 대응하는 매핑 어노테이션을 사용할 수 있습니다.
| HTTP 메서드 | Spring 어노테이션 |
|---|---|
GET | @GetMapping |
POST | @PostMapping |
PUT | @PutMapping |
PATCH | @PatchMapping |
DELETE | @DeleteMapping |
이 어노테이션들은 @RequestMapping을 HTTP 메서드별로 편하게 사용할 수 있도록 만든 축약 어노테이션입니다.
3. @GetMapping: 조회 요청 처리
GET은 리소스를 조회할 때 사용합니다.
import java.util.List;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class PostController {
@GetMapping("/api/posts")
public List<PostResponse> getPosts() {
return List.of(
new PostResponse(1L, "Spring Boot 프로젝트 구조 이해하기"),
new PostResponse(2L, "HTTP 메서드와 매핑 어노테이션")
);
}
}요청과 응답 흐름은 다음과 같습니다.
GET /api/posts
↓
PostController.getPosts()
↓
JSON 배열 응답GET 요청은 서버의 상태를 바꾸지 않는 조회에 사용하는 것이 원칙입니다. 예를 들어 GET /api/posts/1/delete처럼 GET 요청으로 삭제를 처리하면 안 됩니다.
피해야 하는 예
GET /api/posts/1/delete브라우저, 검색 엔진, 캐시, 프록시 도구는 GET 요청을 “조회”로 기대합니다. GET으로 데이터를 변경하면 의도하지 않은 실행이나 캐시 문제를 만들 수 있습니다.
4. @PostMapping: 생성 요청 처리
POST는 새 리소스를 만들거나, 서버에 어떤 처리를 요청할 때 자주 사용합니다.
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class PostController {
@PostMapping("/api/posts")
public PostResponse createPost(@RequestBody CreatePostRequest request) {
return new PostResponse(1L, request.getTitle());
}
}요청 예시는 다음과 같습니다.
POST /api/posts HTTP/1.1
Content-Type: application/json
{
"title": "새 게시글"
}응답 예시는 다음과 같습니다.
{
"id": 1,
"title": "새 게시글"
}여기서 @RequestBody는 요청 본문의 JSON을 Java 객체로 바꿔 받을 때 사용합니다. 자세한 내용은 뒤에서 @RequestBody와 JSON 처리 글에서 다룰 예정입니다.
실무에서는 생성 성공 시 HTTP 상태 코드 201 Created와 Location 헤더를 함께 사용하는 경우도 많습니다. 이 글에서는 매핑 어노테이션의 기본 흐름에 집중하기 위해 단순한 응답 객체를 반환했습니다.
5. @PutMapping: 전체 수정 처리
PUT은 리소스를 전체 교체할 때 사용합니다.
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class PostController {
@PutMapping("/api/posts/{postId}")
public PostResponse updatePost(
@PathVariable Long postId,
@RequestBody UpdatePostRequest request
) {
return new PostResponse(postId, request.getTitle());
}
}요청 예시는 다음과 같습니다.
PUT /api/posts/1 HTTP/1.1
Content-Type: application/json
{
"title": "수정된 제목",
"content": "수정된 본문"
}PUT은 “이 리소스를 요청 본문의 내용으로 바꾼다”는 의미에 가깝습니다. 그래서 일부 필드만 보내서 부분 수정하는 용도보다는, 전체 상태를 대체하는 용도로 이해하는 편이 좋습니다.
@PathVariable은 URL 경로의 {postId} 값을 메서드 파라미터로 받을 때 사용합니다. 다음 글인 @RequestParam과 @PathVariable 사용법에서 더 자세히 살펴보겠습니다.
6. @PatchMapping: 일부 수정 처리
PATCH는 리소스의 일부만 수정할 때 사용합니다.
import org.springframework.web.bind.annotation.PatchMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class PostController {
@PatchMapping("/api/posts/{postId}")
public PostResponse updatePostTitle(
@PathVariable Long postId,
@RequestBody UpdatePostTitleRequest request
) {
return new PostResponse(postId, request.getTitle());
}
}요청 예시는 다음과 같습니다.
PATCH /api/posts/1 HTTP/1.1
Content-Type: application/json
{
"title": "제목만 수정"
}PUT과 PATCH의 차이는 실무에서 자주 헷갈립니다.
| 구분 | 의미 |
|---|---|
PUT | 리소스 전체를 요청 내용으로 교체 |
PATCH | 리소스 일부만 변경 |
예를 들어 게시글의 제목과 본문을 모두 새 값으로 교체하는 API라면 PUT이 어울립니다. 제목만 바꾸거나, 공개 여부만 바꾸는 API라면 PATCH가 더 자연스럽습니다.
7. @DeleteMapping: 삭제 요청 처리
DELETE는 리소스를 삭제할 때 사용합니다.
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class PostController {
@DeleteMapping("/api/posts/{postId}")
public void deletePost(@PathVariable Long postId) {
// postId에 해당하는 게시글 삭제
}
}요청 예시는 다음과 같습니다.
DELETE /api/posts/1 HTTP/1.1삭제 API는 응답 본문 없이 204 No Content를 반환하는 방식도 자주 사용합니다.
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class PostController {
@DeleteMapping("/api/posts/{postId}")
public ResponseEntity<Void> deletePost(@PathVariable Long postId) {
return ResponseEntity.noContent().build();
}
}ResponseEntity를 사용하면 응답 상태 코드와 헤더를 더 명확하게 제어할 수 있습니다. 응답 처리 방식은 뒤에서 @ResponseBody와 응답 처리 글에서 더 자세히 다룰 예정입니다.
8. @RequestMapping은 언제 사용할까?
@RequestMapping은 URL, HTTP 메서드, 요청 파라미터, 헤더, Content-Type 같은 조건으로 요청을 매핑할 수 있는 기본 어노테이션입니다.
메서드 레벨에서는 보통 @GetMapping, @PostMapping 같은 축약 어노테이션을 더 많이 사용합니다.
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class PostController {
@GetMapping("/api/posts")
public String getPosts() {
return "posts";
}
}위 코드는 아래처럼 @RequestMapping으로도 표현할 수 있습니다.
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class PostController {
@RequestMapping(value = "/api/posts", method = RequestMethod.GET)
public String getPosts() {
return "posts";
}
}실무에서는 클래스 레벨에서 공통 경로를 묶을 때 @RequestMapping을 자주 사용합니다.
import java.util.List;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/api/posts")
public class PostController {
@GetMapping
public List<PostResponse> getPosts() {
return List.of();
}
@PostMapping
public PostResponse createPost() {
return new PostResponse(1L, "새 게시글");
}
}이렇게 작성하면 클래스의 모든 메서드가 /api/posts를 공통 경로로 사용합니다.
GET /api/posts
POST /api/posts9. produces와 consumes
REST API에서는 요청과 응답의 데이터 형식을 명확히 해야 할 때가 있습니다.
consumes는 이 API가 어떤 요청 Content-Type을 받을 수 있는지를 나타냅니다. produces는 어떤 응답 Content-Type을 만들 수 있는지를 나타냅니다.
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class PostController {
@PostMapping(
value = "/api/posts",
consumes = MediaType.APPLICATION_JSON_VALUE,
produces = MediaType.APPLICATION_JSON_VALUE
)
public PostResponse createPost(@RequestBody CreatePostRequest request) {
return new PostResponse(1L, request.getTitle());
}
}Spring Boot에서 JSON API를 만들 때는 기본 설정만으로도 충분한 경우가 많습니다. 그래도 외부 API 계약이 엄격하거나, 같은 URL에서 Content-Type에 따라 다른 처리를 해야 한다면 consumes, produces를 명시할 수 있습니다.
10. 안전성과 멱등성
HTTP 메서드는 단순한 이름이 아니라 의미를 가지고 있습니다. 그중 입문 단계에서 알아두면 좋은 개념이 안전성과 멱등성입니다.
안전한 메서드는 서버의 상태를 변경하지 않는 메서드입니다. 대표적으로 GET이 여기에 해당합니다.
멱등한 메서드는 같은 요청을 한 번 보내든 여러 번 보내든 서버에 의도한 최종 효과가 같은 메서드입니다.
| HTTP 메서드 | 안전한가? | 멱등한가? |
|---|---|---|
GET | O | O |
POST | X | 보장되지 않음 |
PUT | X | O |
PATCH | X | 보장되지 않음 |
DELETE | X | O |
예를 들어 GET /api/posts/1은 여러 번 호출해도 조회일 뿐입니다. PUT /api/posts/1도 같은 내용으로 여러 번 호출하면 최종 상태는 같습니다.
반면 POST /api/orders는 같은 요청을 여러 번 보내면 주문이 여러 개 만들어질 수 있습니다. 그래서 네트워크 재시도, 중복 제출, 결제 같은 상황에서는 HTTP 메서드의 의미와 중복 처리 전략을 함께 고민해야 합니다.
11. 실무에서 선택하는 기준
HTTP 메서드를 고를 때는 아래 기준으로 시작하면 됩니다.
| 하고 싶은 일 | HTTP 메서드 | 경로 예시 |
|---|---|---|
| 목록 조회 | GET | /api/posts |
| 단건 조회 | GET | /api/posts/1 |
| 생성 | POST | /api/posts |
| 전체 수정 | PUT | /api/posts/1 |
| 일부 수정 | PATCH | /api/posts/1 |
| 삭제 | DELETE | /api/posts/1 |
URL은 리소스를 나타내고, 동작은 HTTP 메서드로 표현하는 것이 기본입니다.
좋은 방향
GET /api/posts
POST /api/posts
PUT /api/posts/1
PATCH /api/posts/1
DELETE /api/posts/1반대로 URL에 동사를 많이 넣으면 API가 일관성을 잃기 쉽습니다.
피하는 편이 좋은 방향
GET /api/getPosts
POST /api/createPost
POST /api/deletePost물론 모든 API가 교과서적인 CRUD로 딱 떨어지지는 않습니다. 예를 들어 “주문 취소”, “비밀번호 재설정 메일 발송”, “파일 업로드 완료 처리” 같은 작업은 POST /api/orders/1/cancel처럼 처리 명령에 가까운 URL을 사용할 수도 있습니다.
중요한 것은 팀 안에서 기준을 맞추고, 같은 성격의 API를 같은 방식으로 설계하는 것입니다.
12. 자주 하는 실수
12.1. 모든 API를 POST로 만드는 경우
처음 API를 만들 때 모든 요청을 POST로 처리하면 단순해 보일 수 있습니다.
POST /api/getPosts
POST /api/createPost
POST /api/updatePost
POST /api/deletePost하지만 이렇게 만들면 요청의 의도를 URL과 메서드만 보고 파악하기 어렵습니다. 캐시, 재시도, 문서화, 클라이언트 구현에서도 불리합니다.
조회는 GET, 생성은 POST, 수정은 PUT 또는 PATCH, 삭제는 DELETE로 구분하는 습관을 들이는 것이 좋습니다.
12.2. GET 요청에서 데이터를 변경하는 경우
GET은 조회용입니다.
@GetMapping("/api/posts/{postId}/delete")
public void deletePost(@PathVariable Long postId) {
}이런 API는 브라우저 미리보기, 링크 검사, 크롤러, 캐시 계층 때문에 의도치 않게 실행될 수 있습니다.
삭제는 DELETE, 상태 변경은 POST, PUT, PATCH 중 의미에 맞는 메서드를 사용해야 합니다.
12.3. PUT과 PATCH를 구분하지 않는 경우
부분 수정 API를 만들면서 PUT을 쓰는 경우가 많습니다.
PUT /api/posts/1
Content-Type: application/json
{
"title": "제목만 수정"
}팀에서 명확히 약속했다면 동작할 수는 있지만, 일반적으로 PUT은 전체 교체, PATCH는 일부 수정으로 구분하는 편이 의미가 더 분명합니다.
12.4. 클래스 레벨 경로와 메서드 레벨 경로를 중복 작성하는 경우
클래스에 공통 경로를 붙였는데 메서드에도 같은 경로를 반복해서 쓰면 실제 URL이 의도와 달라질 수 있습니다.
@RestController
@RequestMapping("/api/posts")
public class PostController {
@GetMapping("/api/posts")
public List<PostResponse> getPosts() {
return List.of();
}
}위 코드는 /api/posts/api/posts처럼 매핑될 수 있습니다.
공통 경로를 클래스 레벨에 두었다면 메서드 레벨에는 나머지 경로만 작성합니다.
@RestController
@RequestMapping("/api/posts")
public class PostController {
@GetMapping
public List<PostResponse> getPosts() {
return List.of();
}
}13. 참고 자료
14. 정리
HTTP 메서드는 REST API에서 요청의 의도를 표현하는 중요한 약속입니다.
GET은 조회,POST는 생성이나 처리 요청에 사용합니다.PUT은 전체 수정,PATCH는 일부 수정,DELETE는 삭제에 사용합니다.- Spring에서는
@GetMapping,@PostMapping,@PutMapping,@PatchMapping,@DeleteMapping으로 메서드별 요청을 처리할 수 있습니다. @RequestMapping은 공통 경로나 세부 매핑 조건을 표현할 때 유용합니다.- GET 요청으로 데이터를 변경하지 말고, URL은 리소스 중심으로 설계하는 것이 좋습니다.
- HTTP 메서드의 안전성과 멱등성을 이해하면 재시도, 캐시, 중복 요청 문제를 더 잘 다룰 수 있습니다.
HTTP 메서드와 매핑 어노테이션을 이해하면 컨트롤러 코드가 훨씬 명확해집니다. 다음 글에서는 URL의 query string과 path variable 값을 어떻게 받아오는지 살펴보겠습니다.