웹 애플리케이션 개발에서 프론트엔드와 백엔드를 분리하여 개발하는 것이 일반적인 패턴이 되었습니다. 이러한 구조에서 두 영역을 연결하는 핵심적인 역할을 하는 것이 바로 REST API입니다. Spring Framework를 사용하여 REST API를 구축하는 방법을 알아보겠습니다.
REST API가 필요한 이유
과거에는 하나의 애플리케이션에서 모든 것을 처리하는 모놀리식 구조가 일반적이었습니다. 하지만 현대 웹 개발에서는 다양한 플랫폼과 클라이언트가 동일한 데이터를 필요로 합니다. 멀티 플랫폼과 멀티 디바이스의 시대에 접어들면서 플랫폼 종속적인 개발 방식의 한계가 드러났습니다.
이러한 환경에서 REST API는 플랫폼 독립적인 데이터 통신을 가능하게 합니다. 모든 클라이언트가 HTTP 프로토콜을 통해 동일한 방식으로 서버의 자원에 접근할 수 있게 되어, 개발 효율성과 유지보수성이 크게 향상됩니다.
REST API의 핵심 개념과 설계 원칙
REST의 구성 요소
REST(Representational State Transfer)는 2000년 로이 필딩(Roy Fielding)이 제안한 아키텍처 스타일입니다. 웹의 기존 기술과 HTTP 프로토콜을 그대로 활용하는 설계 원칙을 가지고 있습니다.
- Representational은 자원의 표현을 의미합니다. 자원의 표현으로 JSON, XML 등의 형식으로 표현되며, 현재는 JSON이 가장 널리 사용됩니다.
- State는 애플리케이션의 상태를 나타냅니다. 클라이언트는 서버의 자원 상태를 요청하고, 서버는 현재 상태를 응답으로 전달합니다.
- Transfer는 네트워크를 통해 상태를 전송함을 의미합니다. HTTP 프로토콜을 기반으로 클라이언트와 서버 간에 자원의 상태 정보를 주고받습니다.
REST API의 주요 특징
REST API는 다음과 같은 핵심 특징을 가지고 있습니다.
- 자원 중심(resource-oriented)으로 설계됩니다. 모든 것은 자원으로 표현되며 각 자원은 고유한 URI를 갖습니다.
- HTTP 메서드 활용을 통해 요청을 위해 HTTP의 GET/POST/PUT/DELETE 등을 사용합니다.
- 자원의 표현에서 자원은 JSON, XML을 사용합니다. 현재는 JSON이 표준처럼 사용되고 있습니다.
- 무상태(Stateless)를 유지하여 각 요청은 이전 요청과 독립적이며 서버는 클라이언트의 상태를 저장하지 않습니다.
기존 방식과 REST 방식의 차이
전통적인 웹 애플리케이션과 REST API의 가장 큰 차이점을 살펴보면 다음과 같습니다.
- URL 설계 방식에서 기존에는 행위 중심으로 설계했습니다. 예를 들어 /getUserInfo?id=123과 같이 동작을 나타내는 단어를 포함했습니다. 반면 REST에서는 자원 중심으로 /users/123과 같이 명사 형태로 작성합니다.
- HTTP 메서드 사용에서 기존에는 주로 GET과 POST만 사용했지만, REST에서는 GET, POST, PUT, DELETE 등 HTTP 메서드를 의미에 맞게 사용합니다.
- 상태 관리에서 기존에는 서버에 세션 상태 유지 경향이 있었지만, REST에서는 Stateless(무상태)로 각 요청은 독립적입니다.
- 응답 형식에서 기존에는 HTML이 주를 이뤘지만, REST에서는 주로 JSON, XML 등 표준화된 형식을 사용합니다.
REST 서비스를 위한 URL 작성 권장 사항
REST API를 설계할 때는 일관성 있고 직관적인 URL 구조를 만드는 것이 중요합니다.
자원 중심의 URL 설계
- 자원 중심의 URL 사용에서 URL은 자원을 나타내며 명사 형태로 작성합니다.
- HTTP 메서드 사용에서 URL 자체는 자원의 위치를 나타내며 HTTP 메서드로 작업의 의미를 전달합니다.
- 계층 구조 반영에서 자원의 관계를 URL에 반영하여 계층 구조를 표현합니다.
- 목록과 개별 자원의 구분에서 /members는 전체 목록을, /members/{mno}는 개별 자원을 나타냅니다.
- 버전 관리에서 API 버전을 URL에 포함하여 /v1/members, /v2/members와 같이 사용합니다.
- 소문자 사용에서 기본적으로 소문자를 사용하고 단어간 구분이 필요할 때는 하이픈(-)을 사용합니다.
- 쿼리 스트링 활용에서 필터링, 정렬 등 추가적인 정보는 쿼리 스트링으로 전달합니다.
REST API URL 예제
다음은 REST API URL의 구체적인 예제입니다.
- GET /api/members - 전체 회원 목록 조회
- GET /api/members?key=email&word=01&page=3 - 필터링된 회원 목록 조회
- GET /api/members/123 - 특정 회원 조회
- POST /api/members - 새로운 회원 생성
- PUT /api/members/123 - 회원 정보 전체 수정
- PATCH /api/members/123 - 회원 정보 부분 수정
- DELETE /api/members/123 - 회원 삭제
Spring에서 REST API 구현하기
@RestController를 활용한 기본 구현
Spring에서 REST API를 구현하는 가장 기본적인 방법은 @RestController 어노테이션을 사용하는 것입니다. 이 어노테이션은 @Controller와 @ResponseBody를 결합한 것으로, 모든 메서드의 반환값이 HTTP 응답 본문으로 직접 전송됩니다.
@RestController
@RequestMapping("/api/v1/members")
@RequiredArgsConstructor
public class MemberRestController {
private final BasicMemberService memberService;
@PostMapping
public Map<String, Object> registMember(@RequestBody Member member) {
try {
memberService.registMember(member);
return Map.of("status", "SUCCESS", "member", member);
} catch (DataAccessException e) {
return Map.of("status", "FAIL", "error", e.getMessage());
}
}
}
요청 데이터 처리 어노테이션
REST API에서 클라이언트로부터 데이터를 받는 방법은 크게 세 가지가 있습니다.
@PathVariable은 URL 경로의 일부를 변수로 처리할 때 사용합니다.
@GetMapping("/{id}")
public Member getMember(@PathVariable Long id) {
return memberService.findById(id);
}
@RequestParam은 쿼리 파라미터를 처리할 때 사용합니다.
@GetMapping
public List<Member> getMembers(@RequestParam(required = false) String name) {
return memberService.findByName(name);
}
@RequestBody는 HTTP 요청 본문의 JSON 데이터를 자바 객체로 변환할 때 사용합니다.
@PostMapping
public Member createMember(@RequestBody Member member) {
return memberService.save(member);
}
ResponseEntity를 활용한 세밀한 응답 제어
단순히 데이터만 반환하는 것이 아니라 HTTP 상태 코드와 헤더를 함께 제어해야 할 때는 ResponseEntity를 사용합니다.
public interface RestControllerHelper {
default ResponseEntity<?> handleSuccess(Object data) {
Map<String, Object> response = Map.of("status", "SUCCESS", "data", data);
return ResponseEntity.ok(response);
}
default ResponseEntity<?> handleFail(Exception e) {
Map<String, Object> response = Map.of("status", "FAIL", "error", e.getMessage());
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(response);
}
}
이런 헬퍼 인터페이스를 구현하면 일관된 응답 형식을 유지할 수 있습니다.
REST API 테스트와 문서화
MockMvc를 활용한 단위 테스트
REST API의 품질을 보장하기 위해서는 철저한 테스트가 필요합니다. Spring에서는 MockMvc를 사용하여 실제 서버를 구동하지 않고도 컨트롤러를 테스트할 수 있습니다.
@WebMvcTest(controllers = { MemberRestController.class })
public class MemberRestControllerTest {
@Autowired
private MockMvc mockMvc;
@MockBean
private BasicMemberService memberService;
@Test
public void registMemberTest() throws Exception {
Member member = Member.builder().name("test").email("test@email.com").build();
when(memberService.registMember(member)).thenReturn(1);
mockMvc.perform(post("/api/v1/members")
.contentType(MediaType.APPLICATION_JSON)
.content(new ObjectMapper().writeValueAsString(member)))
.andExpect(status().isOk())
.andExpect(jsonPath("$.status").value("SUCCESS"));
}
}
JSON 형태로 요청을 전송할 때는 서버에서 @RequestBody로 변경해야 테스트가 정상적으로 동작합니다.
Swagger를 활용한 API 문서화
REST API의 사용법을 문서화하는 것은 매우 중요합니다. Swagger는 자동으로 API 문서를 생성하고 관리할 수 있는 강력한 도구입니다.
먼저 의존성을 추가합니다.
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
<version>2.8.5</version>
</dependency>
설정 클래스를 작성합니다.
@Configuration
public class SwaggerConfig {
@Bean
public GroupedOpenApi memberOpenApi() {
String[] paths = { "/api/v1/members/**" };
return GroupedOpenApi.builder()
.group("Member 관련 API")
.pathsToMatch(paths)
.build();
}
}
컨트롤러에 문서화 정보를 추가합니다.
@Tag(name = "회원 관리", description = "회원 관리를 위한 기능 제공")
@RestController
public class MemberController {
@Operation(summary = "회원 목록 조회", description = "모든 회원의 정보를 반환합니다")
@ApiResponses({
@ApiResponse(responseCode = "200", description = "조회 성공"),
@ApiResponse(responseCode = "500", description = "서버 오류")
})
@GetMapping("/members")
public List<Member> getMembers() {
return memberService.findAll();
}
}
RestTemplate을 활용한 클라이언트 프로그래밍
서버에서 다른 REST API를 호출해야 하는 경우가 자주 있습니다. Spring에서는 RestTemplate을 제공하여 HTTP 클라이언트 기능을 쉽게 구현할 수 있습니다.
RestTemplate 설정과 기본 사용법
@Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
외부 API를 호출하는 컨트롤러 예제입니다.
@RestController
@RequiredArgsConstructor
public class ExternalApiController {
private final RestTemplate restTemplate;
@Value("${api.key}")
private String apiKey;
@GetMapping("/external-data")
public ResponseEntity getExternalData() {
try {
String url = "https://api.example.com/data?key=" + apiKey;
Map<string, object=""> result = restTemplate.getForObject(url, Map.class);
return ResponseEntity.ok(result);
} catch (Exception e) {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body(Map.of("error", e.getMessage()));
}
}
}
CORS 문제 해결하기
CORS가 발생하는 이유
웹 브라우저의 동일 출처 정책(Same Origin Policy)에 의해, JavaScript에서 다른 도메인의 리소스에 접근할 때 제한이 발생합니다. 예를 들어 localhost:8080에서 실행되는 웹 애플리케이션이 127.0.0.1:8080의 API를 호출하려고 하면 CORS 오류가 발생합니다.
Spring에서 CORS 설정하기
CORS 문제를 해결하는 방법은 크게 두 가지가 있습니다.
@CrossOrigin 어노테이션을 사용하여 컨트롤러별로 설정할 수 있습니다.
@CrossOrigin(origins = "http://localhost:3000")
@RestController
public class MemberController {
// 컨트롤러 코드
}
WebMvcConfigurer를 통한 전역 설정을 사용하면 애플리케이션 전체에 CORS 정책을 적용할 수 있습니다.
@Configuration
public class CorsConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/api/**")
.allowedOrigins("http://localhost:3000")
.allowedMethods("GET", "POST", "PUT", "DELETE")
.allowedHeaders("*");
}
}
마무리
REST API는 현대 웹 애플리케이션 개발에서 없어서는 안 될 핵심 기술입니다. Spring Framework의 강력한 기능들을 활용하면 견고하고 유지보수하기 쉬운 REST API를 구축할 수 있습니다.
'BackEnd > Spring Framework' 카테고리의 다른 글
| Spring Security 핵심 컴포넌트 (8) | 2025.06.14 |
|---|---|
| Spring Security 개요 (3) | 2025.06.11 |
| Spring의 트랜잭션 관리 (1) | 2025.05.30 |
| MyBatis의 동적 SQL (0) | 2025.05.23 |
| MyBatis (3) | 2025.05.03 |