RESTful API 설계 원칙과 사례

gunnoo·2025년 3월 5일
post-thumbnail

작년에 프로젝트를 진행하면서 배웠던 내용인데 다시한번 기억을 상기시키고 좀 더 심화해서 공부하기 위해 한번 정리해놓는게 좋겠다고 생각해서 정리하려고 한다. 여기서는 REST의 기본 특징, 설계 원칙, 그리고 실제 Spring Boot를 이용한 구현 예제를 알아보도록 한다.

먼저 REST는 웹 기반 시스템에서 자원을 효과적으로 다루기 위한 아키텍처 스타일로 URI를 통해 자원을 표시하고, HTTP Method를 이용하여 해당 자원의 행위를 규정하여 그 결과를 받는 것을 말한다. 자원, 표현, 무상태성, 캐시 처리 기능 등의 종류가 있다.

보기 쉽게 표로 정리하자면

다음과 같다.

REST API 설계 원칙

먼저 URI를 설계해야 한다.

1. 명사중심

URI는 자원을 나타내는 명사를 사용합니다.
올바른 예: /users, /orders, /products
잘못된 예: /getUser, /createOrder (동작을 나타내는 동사 사용 금지)

2. 계층적 구조

자원 간의 관계를 계층적으로 표현합니다.
예: /users/{userId}/orders → 특정 사용자의 주문 목록

3. 일관성 및 단순성

소문자 사용, 하이픈(-)이나 밑줄(_)을 통한 단어 구분, 불필요한 복잡성 배제

두번째론 HTTP 메서드 사용법에 대해 알아보겠다.

이부분은 상대적으로 다른 내용에 비해 쉬운 부분이다. get(자원조회), post(자원 생성), put(자원 전체 수정), patch(자원 부분 수정), delete(자원 삭제)가 있다.

다음으론 응답 코드 및 에러 처리에 대해 알아보겠다.

1. 성공응답의 경우엔

200 OK: 요청 성공 (GET, PUT, PATCH)
201 Created: 새로운 자원 생성 성공 (POST)
204 No Content: 삭제나 업데이트 후 별도 응답 내용이 없을 때 (DELETE)

클라이언트 오류시엔

400 Bad Request: 잘못된 요청
401 Unauthorized: 인증 실패
403 Forbidden: 권한 없음
404 Not Found: 자원 없음

서버 오류

500 Internal Server Error: 서버 내부 오류
이 API가 잘 작동하는지 확인하고 싶을땐 POSTMAN이나 SWAGGER를 사용하면 확인할 수 있다.

보안 고려 사항

1. 인증 방식 도입

JWT, OAuth2 등을 활용한 안전한 인증 방식을 도입한다.

2. HTTPS 사용

모든 API 통신은 HTTPS를 사용하여 암호화 처리해야 한다.

3. CORS

클라이언트와 서버 도메인이 다를 경우 CORS 설정은 필수이다.

Spring Boot를 활용한 RESTful API 예제

기본 REST Controller 구현

package com.example.demo.controller;

import com.example.demo.exception.ResourceNotFoundException;
import com.example.demo.model.User;
import com.example.demo.service.UserService;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import java.util.List;

@RestController
@RequestMapping("/api/users")
public class UserController {

    private final UserService userService;

    public UserController(UserService userService) {
        this.userService = userService;
    }

    // GET /api/users
    @GetMapping
    public ResponseEntity<List<User>> getAllUsers() {
        List<User> users = userService.findAllUsers();
        return ResponseEntity.ok(users);
    }

    // GET /api/users/{id}
    @GetMapping("/{id}")
    public ResponseEntity<User> getUserById(@PathVariable Long id) {
        User user = userService.findUserById(id)
                .orElseThrow(() -> new ResourceNotFoundException("User with id " + id + " not found"));
        return ResponseEntity.ok(user);
    }

    // POST /api/users
    @PostMapping
    public ResponseEntity<User> createUser(@RequestBody User user) {
        User createdUser = userService.createUser(user);
        return ResponseEntity.status(HttpStatus.CREATED).body(createdUser);
    }

    // PUT /api/users/{id}
    @PutMapping("/{id}")
    public ResponseEntity<User> updateUser(@PathVariable Long id, @RequestBody User userDetails) {
        User updatedUser = userService.updateUser(id, userDetails);
        return ResponseEntity.ok(updatedUser);
    }

    // DELETE /api/users/{id}
    @DeleteMapping("/{id}")
    public ResponseEntity<Void> deleteUser(@PathVariable Long id) {
        userService.deleteUser(id);
        return ResponseEntity.noContent().build();
    }
}

전역 예외 처리

에러가 발생햇을 때 일관된 응답을 제공하기 위해 @ControllerAdvice를 활용한 전역 예외 처리 클래스를 작성한다.

package com.example.demo.exception;

import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;

import javax.servlet.http.HttpServletRequest;
import java.time.LocalDateTime;

@ControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(ResourceNotFoundException.class)
    public ResponseEntity<ErrorResponse> handleResourceNotFound(ResourceNotFoundException ex,
                                                                HttpServletRequest request) {
        ErrorResponse errorResponse = new ErrorResponse(
                LocalDateTime.now(),
                HttpStatus.NOT_FOUND.value(),
                "Not Found",
                ex.getMessage(),
                request.getRequestURI()
        );
        return new ResponseEntity<>(errorResponse, HttpStatus.NOT_FOUND);
    }

    // 기타 예외에 대한 핸들러 추가 가능 (400, 500 등)
}

에러 응답 클래스의 예시는 다음과 같다.

package com.example.demo.exception;

import java.time.LocalDateTime;

public class ErrorResponse {
    private LocalDateTime timestamp;
    private int status;
    private String error;
    private String message;
    private String path;

    public ErrorResponse(LocalDateTime timestamp, int status, String error, String message, String path) {
        this.timestamp = timestamp;
        this.status = status;
        this.error = error;
        this.message = message;
        this.path = path;
    }

    // Getter, Setter 생략
}

위 코드는 스프링 부트를 활용한 RESTful API예제로 이해하기 쉽게 설계되었다.

정리하자면 RESTful API 설계는 단순한 기술 구현 문제라기보단 자원을 일관되게 식별하고, HTTP의 표준 메서드와 상태 코드를 적절히 사용하며, 무상태성과 캐시, 보안을 고려하는 설계 원칙이다. RESTful API 설계는 클라이언트와 서버 간의 커뮤니케이션, 유지보수, 확장성을 보장한다.

Spring Boot와 같은 프레임워크를 활용하면 이러한 것들을 쉽게 구현할 수 있고, 전역 예외 처리, 버전 관리, 보안 설정 등을 통해 실제 운영 환경에서도 안정적이고 확장 가능한 API를 구축할 수 있다.

profile
개발 블로그

0개의 댓글