Spring Boot로 RESTful API 설계 및 구현

ShaynePark·2025년 7월 31일

SpringBoot

목록 보기
2/4

REST(Representational State Transfer)는 HTTP 프로토콜을 기반으로 한 아키텍처 스타일로, HTTP/1.0과 1.1의 설계에 참여한 Roy Fielding이 2000년 논문에서 처음 제안했습니다. RESTful 서비스는 HTTP의 장점을 최대한 활용하여, 일관성 있고 확장 가능한 API를 제공합니다.


목차

  1. REST의 기본 원칙
  2. URI 설계 가이드라인
  3. HTTP 메서드 매핑
  4. Spring Boot에서의 GET 예제
    • JSP & AJAX 클라이언트
    • Controller → Service → DAO → MyBatis
  5. @PathVariable을 활용한 경로 변수 처리
  6. 실행 가능한 JAR & WAR 패키징
  7. 정리 및 참고 자료

REST의 기본 원칙

원칙설명
Uniform Interface
(유니폼 인터페이스)
리소스 식별, 리소스 조작, 자기 설명 메시지, HATEOAS 등의 일관된 인터페이스 제공
Stateless
(무상태성)
각 요청은 독립적이며, 서버는 세션/쿠키 등 상태를 저장하지 않음
Cacheable
(캐시 가능)
HTTP 표준의 Last-Modified, ETag 등을 활용하여 클라이언트·프록시 캐싱 지원
Self-Descriptive Messages
(자기 표현 구조)
요청/응답 메시지만으로 의미를 이해할 수 있도록 JSON/XML 기반의 명확한 구조 사용
Client–Server 구조역할 분리로 클라이언트와 서버의 독립적인 개발 및 확장 가능
Layered System
(계층화)
프록시, 게이트웨이, 로드 밸런서 등 중간 계층 도입으로 유연성 확보

URI 설계 가이드라인

1. 동사 사용 지양 & 파일 확장자 제거

잘못된 예시문제점올바른 예시
GET /get-user_list.json1) 동사(get) 사용
2) 밑줄(_) 사용
3) .json 확장자 사용
GET /users
  • 동사 지양:
    REST는 HTTP 메서드(GET, POST…)로 동작을 구분하므로 URI엔 명사(리소스)만 남겨야 합니다.
  • 언더스코어 → 대시:
    가독성을 위해 user-list처럼 대시(-)를 씁니다.
  • 확장자 제거:
    클라이언트는 Accept: application/json 헤더로 JSON을 요청합니다.

2. 계층 구조 표현 & 끝 슬래시 제거

잘못된 예시문제점올바른 예시
GET /users/123/orders_list/1) 언더스코어 사용
2) 트레일링 슬래시(/) 있음
GET /users/123/orders
  • /users/123/orders는 “ID가 123인 유저의 주문 목록”이라는 계층 관계를 그대로 보여줍니다.
  • 마지막 /가 없으면 동일한 리소스를 중복 관리하지 않아도 됩니다.

3.리소스명 일관성(단수 vs 복수) & 파일 확장자

잘못된 예시문제점올바른 예시
GET /product/456.json1) 단수 리소스 사용
2) .json 확장자 사용
GET /products/456
  • 컬렉션 전체는 복수(products), 개별 항목은 /products/{id} 형식으로 일관되게 표현합니다.
  • 콘텐츠 타입은 헤더로 제어하므로 .json을 붙이지 않습니다.

4. 언더스코어 대신 대시

잘못된 예시문제점올바른 예시
GET /user_profiles언더스코어(_) 사용GET /user-profiles
  • 언더스코어(_)는 외형상 잘 안 보이고, 검색엔진도 단어 사이 구분자로 인식하지 않습니다.
  • 대시(-)는 시각적으로 읽기 쉽고, SEO에도 더 유리하기 때문에 RESTful URI나 블로그 슬러그 등에 권장됩니다.
  • SEO(검색엔진최적화)
    구글 같은 검색엔진은 대시(-)를 ‘단어 구분자’로 인식합니다. 언더스코어(_)는 단어를 이어 붙이는 연결부로 인식해서, 실제로는 “userprofiles”처럼 한 덩어리로 처리할 수 있습니다. 따라서 “user-profiles”처럼 대시를 쓰면 검색 키워드 분리가 잘 돼서 검색노출에 유리합니다.
  • 표준 권장사항
    [RFC 3986 (URI 표준)] 에도 언더스코어는 특별한 의미가 있는 문자가 아니어서 “안 쓰는 게 좋다”고만 명시돼 있고, 반면 하이픈(대시)은 가독성·SEO 양쪽에서 사실상 ‘사실상의 표준’으로 자리 잡고 있습니다.

HTTP 메서드 매핑

CRUDHTTP 메서드설명
CreatePOST리소스 생성
ReadGET리소스 조회
UpdatePUT/PATCH리소스 갱신
DeleteDELETE리소스 삭제

Spring Boot에서의 GET 예제

JSP & AJAX 클라이언트

<div class="menu-test">
  <h4>추천메뉴(GET)</h4>
  <form id="menuRecommendationFrm">
    <!-- 음식 타입 -->
    <input type="radio" name="foodType" value="kr" checked> 한식
    <input type="radio" name="foodType" value="ch"> 중식
    <input type="radio" name="foodType" value="jp"> 일식
    <!-- 매운맛/순한맛 -->
    <input type="radio" name="taste" value="hot" checked> 매운맛
    <input type="radio" name="taste" value="mild"> 순한맛
    <button type="button" class="btn-send">전송</button>
  </form>
  <div id="menuRecommendation-result"></div>
</div>

<script>
$(function() {
  $('.btn-send').click(function() {
    $.ajax({
      url: '/menu/menu',
      type: 'GET',
      data: $('#menuRecommendationFrm').serialize(),
      dataType: 'json',
      success: function(data) {
        let html = '<table><tr><th>음식점</th><th>메뉴</th><th>가격</th></tr>';
        data.forEach(item => {
          html += `<tr><td>${item.restaurant}</td><td>${item.foodName}</td><td>${item.price}</td></tr>`;
        });
        html += '</table>';
        $('#menuRecommendation-result').html(html);
      },
      error: (xhr, status, err) => console.error(err)
    });
  });
});
</script>

Spring Boot Controller

@RestController
@RequestMapping("/menu")
public class MenuController {

  @Autowired
  private MenuService menuService;

  @GetMapping("/menu")
  public List<Menu> recommend(
      @RequestParam String foodType,
      @RequestParam String taste
  ) {
    Map<String, String> params = Map.of(
      "foodType", foodType,
      "taste", taste
    );
    return menuService.selectMenuRecommendation(params);
  }
}

Service → DAO → MyBatis Mapper

@Service
public class MenuServiceImpl implements MenuService {
  @Autowired
  private MenuDAO menuDAO;

  @Override
  public List<Menu> selectMenuRecommendation(Map<String, String> map) {
    return menuDAO.selectMenuRecommendation(map);
  }
}
<!-- resources/mapper/menu/menu-mapper.xml -->
<mapper namespace="menu">
  <select id="selectMenuRecommendation" resultType="menu">
    SELECT * FROM menu
    WHERE foodType = #{foodType}
      AND taste    = #{taste}
  </select>
</mapper>

@PathVariable을 활용한 경로 변수 처리

사용 예시: /menu/{foodType} 경로로 한식(kr), 중식(ch), 일식(jp) 메뉴 전체 조회

<select id="foodTypeSelector">
  <option disabled selected>음식 타입 선택</option>
  <option value="kr">한식</option>
  <option value="ch">중식</option>
  <option value="jp">일식</option>
</select>
<div id="foodType-result"></div>
<script>
$('#foodTypeSelector').change(function() {
  const type = $(this).val();
  $.getJSON(`/menu/${type}`, data => {
    let html = '<table><tr><th>번호</th><th>음식점</th><th>메뉴</th><th>가격</th></tr>';
    data.forEach(i => {
      html += `<tr><td>${i.foodNo}</td><td>${i.restaurant}</td><td>${i.foodName}</td><td>${i.price}</td></tr>`;
    });
    $('#foodType-result').html(html);
  });
});
</script>
@GetMapping("/{foodType}")
public List<Menu> byType(@PathVariable String foodType) {
  return menuService.selectMenuByfoodType(Map.of("foodType", foodType));
}

실행 가능한 JAR & WAR 패키징

JAR

1. pom.xml에 <packaging>jar</packaging> 설정
2. mvn package 실행 → target/*.jar 생성

WAR

1. <packaging>war</packaging>
2. SpringBootServletInitializer 상속
@SpringBootApplication
public class Application extends SpringBootServletInitializer {
  @Override
  protected SpringApplicationBuilder configure(
      SpringApplicationBuilder builder
  ) {
    return builder.sources(Application.class);
  }

  public static void main(String[] args) {
    SpringApplication.run(Application.class, args);
  }
}
3. mvn package → target/*.war
4. 톰캣 webapps 폴더에 배포

정리 및 참고 자료

Roy Fielding의 REST 아키텍처
https://www.ics.uci.edu/~fielding/pubs/dissertation/rest_arch_style.htm

Spring MVC @RequestBody 정리
http://bluesky-devstudy.blogspot.com/2016/07/spring-mvc-requestbody.html

0개의 댓글