1. @RestController는 왜 필요할까?
웹 애플리케이션은 사용자의 요청을 받고, 그 요청에 맞는 응답을 돌려줍니다.
예를 들어 블로그 API라면 이런 요청들이 있을 수 있습니다.
GET /api/posts
GET /api/posts/1
POST /api/posts
DELETE /api/posts/1Spring Boot에서 이런 HTTP 요청을 받아 처리하는 클래스가 컨트롤러입니다. 그중 JSON 응답을 중심으로 REST API를 만들 때 가장 많이 사용하는 어노테이션이 @RestController입니다.
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class HelloController {
@GetMapping("/api/hello")
public String hello() {
return "hello";
}
}위 코드에서 /api/hello로 GET 요청이 들어오면 hello() 메서드가 실행되고, 반환값이 HTTP 응답 본문으로 내려갑니다.
이전 글인 Bean Scope 이해하기까지는 Spring 컨테이너가 객체를 어떻게 관리하는지 살펴봤습니다. 이제부터는 그 Bean들이 실제 웹 요청을 어떻게 처리하는지 REST API 흐름으로 넘어가 보겠습니다.
2. @RestController란?
@RestController는 REST API 컨트롤러를 만들 때 사용하는 어노테이션입니다.
import org.springframework.web.bind.annotation.RestController;
@RestController
public class PostController {
}@RestController는 내부적으로 @Controller와 @ResponseBody를 합친 형태입니다.
@RestController
├── @Controller
└── @ResponseBody@Controller는 이 클래스가 웹 요청을 처리하는 컨트롤러라는 의미입니다. @ResponseBody는 메서드의 반환값을 뷰 이름으로 해석하지 않고, HTTP 응답 본문으로 직접 쓰겠다는 의미입니다.
그래서 @RestController를 사용하면 문자열, 객체, 리스트 같은 반환값이 응답 본문으로 변환됩니다.
3. @Controller와 @RestController의 차이
전통적인 Spring MVC에서는 @Controller가 HTML 화면을 반환하는 데 자주 사용됩니다.
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
@Controller
public class PageController {
@GetMapping("/posts")
public String postsPage() {
return "posts";
}
}위 코드에서 "posts"는 응답 본문 문자열이 아니라, 보통 posts.html 같은 뷰 이름으로 해석됩니다.
반면 @RestController에서는 반환값이 그대로 응답 본문으로 내려갑니다.
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class PostApiController {
@GetMapping("/api/posts")
public String posts() {
return "posts";
}
}이 경우 응답 본문에는 posts라는 문자열이 들어갑니다.
정리하면 다음과 같습니다.
| 구분 | 주 용도 | 반환값 처리 |
|---|---|---|
@Controller | HTML 화면, 템플릿 렌더링 | 뷰 이름으로 해석 |
@RestController | REST API, JSON 응답 | 응답 본문으로 작성 |
요즘 Spring Boot로 API 서버를 만들 때는 대부분 @RestController를 사용합니다.
4. 가장 단순한 REST API 만들기
먼저 문자열을 반환하는 API부터 만들어보겠습니다.
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class HelloController {
@GetMapping("/api/hello")
public String hello() {
return "Hello Spring";
}
}브라우저나 HTTP 클라이언트에서 /api/hello를 호출하면 다음과 같은 응답을 받을 수 있습니다.
Hello Spring흐름은 단순합니다.
클라이언트 요청
↓
GET /api/hello
↓
HelloController.hello()
↓
HTTP 응답 본문여기서 @GetMapping은 GET 요청을 특정 메서드에 연결하는 어노테이션입니다. HTTP 메서드별 매핑 어노테이션은 뒤에서 별도 글로 자세히 다룰 예정이므로, 지금은 “GET 요청을 이 메서드가 처리한다” 정도로 이해하면 됩니다.
5. 객체를 반환하면 JSON이 된다
REST API에서는 문자열보다 JSON 응답을 더 자주 사용합니다.
Spring Boot에서는 객체를 반환하면 기본적으로 JSON 형태로 변환해 응답합니다.
public class PostResponse {
private Long id;
private String title;
public PostResponse(Long id, String title) {
this.id = id;
this.title = title;
}
public Long getId() {
return id;
}
public String getTitle() {
return title;
}
}import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class PostController {
@GetMapping("/api/posts/1")
public PostResponse getPost() {
return new PostResponse(1L, "Spring REST API 시작하기");
}
}응답은 보통 다음과 같은 JSON 형태가 됩니다.
{
"id": 1,
"title": "Spring REST API 시작하기"
}Spring Boot는 Jackson 같은 JSON 변환 라이브러리를 자동 설정으로 연결해줍니다. 그래서 컨트롤러 메서드가 객체를 반환하면, HTTP 응답 본문에 쓸 수 있는 JSON으로 변환됩니다.
직접 ObjectMapper를 생성해 JSON 문자열을 만드는 방식은 보통 권장하지 않습니다. Spring Boot가 제공하는 메시지 변환 흐름을 사용하면 Content-Type, 문자 인코딩, JSON 변환 설정을 더 일관되게 관리할 수 있습니다.
6. List를 반환하는 API
목록 API는 리스트를 반환하는 형태가 많습니다.
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, "@RestController 기초")
);
}
}응답은 JSON 배열 형태가 됩니다.
[
{
"id": 1,
"title": "Spring Boot 프로젝트 구조 이해하기"
},
{
"id": 2,
"title": "@RestController 기초"
}
]처음에는 이렇게 컨트롤러 안에서 바로 예시 데이터를 만들어도 학습에는 충분합니다. 하지만 실무 코드에서는 컨트롤러가 직접 데이터를 만들기보다 서비스 계층에 작업을 위임하는 구조가 더 자연스럽습니다.
7. 컨트롤러와 서비스 역할 나누기
컨트롤러는 HTTP 요청과 응답을 다루는 입구입니다.
반면 실제 비즈니스 로직은 서비스 클래스에 두는 편이 좋습니다.
Controller
├── URL 매핑
├── 요청 값 받기
├── 응답 형태 결정
└── Service 호출
Service
├── 비즈니스 규칙 처리
├── 데이터 조회/저장 흐름 조합
└── 트랜잭션 단위 구성간단한 예시를 보겠습니다.
import java.util.List;
import org.springframework.stereotype.Service;
@Service
public class PostService {
public List<PostResponse> getPosts() {
return List.of(
new PostResponse(1L, "Spring Boot 프로젝트 구조 이해하기"),
new PostResponse(2L, "@RestController 기초")
);
}
}import java.util.List;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class PostController {
private final PostService postService;
public PostController(PostService postService) {
this.postService = postService;
}
@GetMapping("/api/posts")
public List<PostResponse> getPosts() {
return postService.getPosts();
}
}PostController는 요청을 받고 PostService를 호출합니다. 실제 데이터 조회나 비즈니스 규칙은 PostService 쪽으로 이동합니다.
이 구조는 @Autowired와 의존성 주입 방법에서 다룬 생성자 주입과 연결됩니다. 컨트롤러도 Spring Bean이기 때문에 다른 Bean을 생성자로 주입받을 수 있습니다.
또한 @RestController도 컴포넌트 스캔 대상입니다. Spring이 컨트롤러 객체를 Bean으로 등록하고 관리한다는 점에서는 Bean과 Component 이해하기에서 본 내용과 이어집니다.
8. URL 경로는 어떻게 잡는 게 좋을까?
REST API를 만들 때 URL은 클라이언트와 서버가 약속하는 인터페이스입니다. 그래서 처음부터 어느 정도 일관성을 갖추는 것이 좋습니다.
예를 들어 게시글 API라면 다음처럼 잡을 수 있습니다.
GET /api/posts
GET /api/posts/1
POST /api/posts
PUT /api/posts/1
DELETE /api/posts/1입문 단계에서는 아래 기준만 지켜도 충분합니다.
| 기준 | 예시 |
|---|---|
API 경로에는 /api 접두어 사용 | /api/posts |
| 리소스 이름은 명사 중심으로 작성 | /api/posts, /api/users |
| 목록은 복수형 사용 | /api/posts |
| 특정 하나는 ID를 경로에 포함 | /api/posts/1 |
| 동작은 HTTP 메서드로 표현 | GET, POST, PUT, DELETE |
처음부터 완벽한 REST 설계를 외우려고 하기보다, URL이 “무엇을 다루는지”를 명확히 보여주는지 먼저 확인하면 됩니다.
9. 실무에서 선택하는 기준
컨트롤러를 작성할 때는 “HTTP와 관련된 책임만 컨트롤러에 둔다”는 기준이 중요합니다.
| 코드 위치 | 두기 좋은 내용 |
|---|---|
| Controller | 요청 매핑, 요청 값 변환, 응답 DTO 반환 |
| Service | 비즈니스 규칙, 데이터 처리 흐름, 트랜잭션 |
| Repository | 데이터 저장소 접근 |
| DTO | 요청/응답 데이터 구조 |
컨트롤러가 모든 일을 직접 처리하면 처음에는 빠르게 만들 수 있지만, 기능이 늘어날수록 테스트와 유지보수가 어려워집니다.
@RestController
public class OrderController {
@PostMapping("/api/orders")
public OrderResponse createOrder() {
// 요청 검증
// 재고 확인
// 할인 계산
// 주문 저장
// 알림 발송
// 응답 생성
return new OrderResponse(1L);
}
}이런 코드는 컨트롤러가 너무 많은 책임을 가지게 됩니다. 컨트롤러는 요청을 서비스로 전달하고, 서비스 결과를 응답으로 바꾸는 정도로 얇게 유지하는 편이 좋습니다.
10. 자주 하는 실수
10.1. @Controller를 붙이고 JSON이 안 나온다고 헷갈리는 경우
API 응답을 만들려면 @RestController를 사용하거나, @Controller 메서드에 @ResponseBody를 붙여야 합니다.
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
@Controller
public class HelloController {
@GetMapping("/api/hello")
public String hello() {
return "hello";
}
}위 코드는 "hello"를 응답 본문이 아니라 뷰 이름으로 해석할 수 있습니다.
REST API라면 아래처럼 작성하는 편이 명확합니다.
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class HelloController {
@GetMapping("/api/hello")
public String hello() {
return "hello";
}
}10.2. Entity를 그대로 응답으로 반환하는 경우
처음에는 데이터베이스 Entity를 그대로 반환하고 싶을 수 있습니다.
@GetMapping("/api/posts/1")
public Post getPost() {
return postRepository.findById(1L).orElseThrow();
}하지만 실무에서는 응답 전용 DTO를 만들어 반환하는 편이 안전합니다. Entity에는 내부 필드, 연관관계, 지연 로딩, 민감한 값이 섞일 수 있기 때문입니다.
@GetMapping("/api/posts/1")
public PostResponse getPost() {
return postService.getPost(1L);
}DTO 패턴은 뒤에서 별도 글로 더 자세히 다룰 예정입니다.
10.3. URL에 동사를 너무 많이 넣는 경우
REST API에서는 보통 URL은 리소스를 나타내고, 동작은 HTTP 메서드로 표현합니다.
좋지 않은 예
GET /api/getPosts
POST /api/createPost
DELETE /api/deletePost/1다음처럼 리소스 중심으로 정리하면 더 일관적입니다.
GET /api/posts
POST /api/posts
DELETE /api/posts/110.4. 컨트롤러에 비즈니스 로직을 몰아넣는 경우
컨트롤러는 웹 요청의 입구입니다. 모든 로직을 컨트롤러에 넣으면 요청 매핑, 검증, 비즈니스 규칙, 데이터 저장 코드가 한 파일에 섞입니다.
작은 예제에서는 괜찮아 보여도 기능이 커지면 테스트하기 어렵고 변경에 약해집니다. 컨트롤러는 얇게 유지하고, 핵심 로직은 서비스로 옮기는 습관을 들이는 것이 좋습니다.
11. 참고 자료
- Spring Framework - Annotated Controllers
- Spring Framework - Mapping Requests
- Spring Framework API - RestController
12. 정리
@RestController는 REST API 컨트롤러를 만들 때 사용하는 기본 어노테이션입니다.
@RestController는@Controller와@ResponseBody를 합친 형태입니다.- 메서드 반환값은 뷰 이름이 아니라 HTTP 응답 본문으로 작성됩니다.
- 객체나 리스트를 반환하면 Spring Boot가 JSON으로 변환해 응답합니다.
- 컨트롤러는 요청 매핑과 응답 구성을 담당하고, 비즈니스 로직은 서비스에 두는 편이 좋습니다.
- URL은 리소스 중심으로 설계하고, 동작은 HTTP 메서드로 표현하는 것이 좋습니다.
이제 @RestController의 역할을 이해했으니, 다음 단계에서는 GET, POST, PUT, DELETE 요청을 각각 어떤 어노테이션으로 매핑하는지 살펴볼 수 있습니다.