[LG U+ 유레카 3기]Spring Boot REST API + Swagger + Postman 연동 실습

2025. 12. 2. 13:40Java/SpringBoot

오늘은 Spring Boot로 만든 학생 관리 REST API에 Swagger(OpenAPI 3.0)와 Postman을 붙여서, “백엔드 API → 문서 → 테스트 도구”까지 한 번에 연결하는 실습을 했다.
단순히 코드만 작성하는 수준이 아니라, HTTP 응답 설계(ResponseEntity), API 문서화(@Tag, @Operation), OpenAPI JSON, Postman 연동까지 현업에서 실제로 사용하는 흐름과 거의 동일한 구조였다.


❶ REST API 기반 Student CRUD 구조 복습

1) 레이어드 아키텍처 구조

이번 실습의 기본 골격은 이미 만들어 둔 Student CRUD 프로젝트다. 레이어드 아키텍처 구조는 다음과 같다.

  • Controller : HTTP 요청(URI, 메서드) → Java 메서드 매핑, DTO 입출력 담당
  • Service : 비즈니스 로직, Entity ↔ DTO 변환, 예외 처리
  • Repository : Spring Data JPA, DB CRUD 담당
  • Entity : DB 테이블과 매핑되는 도메인 객체
  • DTO : 클라이언트와 주고받는 데이터 모델

// Entity
@Entity
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class Student {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private int id;
    private String name;
    private String email;
    private String phone;
}

// DTO
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class StudentDto {
    private int id;
    private String name;
    private String email;
    private String phone;
}

Service 계층에서는 Entity ↔ DTO 변환을 직접 해 주어 JPA 내부 구현이 외부(API 응답)에 새어 나가지 않도록 막는다. 이는 실제 서비스에서도 많이 쓰는 패턴이다.


❷ ResponseEntity로 HTTP 응답 제어하기

기존 컨트롤러는 단순히 StudentResultDto만 리턴해서, 스프링이 항상 200 OK로 응답하도록 두었다. 오늘은 여기서 한 단계 더 나가서 HTTP 응답 코드와 바디를 직접 제어하는 방법을 실습했다. 그 핵심이 ResponseEntity<T> 이다.

1) 기본 사용 예시


@RestController
@RequestMapping("/api")
@RequiredArgsConstructor
public class StudentControllerCrudResponseEntity {

    private final StudentServiceCrud studentServiceCrud;

    // 목록
    @GetMapping("/students")
    public ResponseEntity<StudentResultDto> listStudent() {

        StudentResultDto resultDto = studentServiceCrud.listStudent();

        // 성공
        return new ResponseEntity<>(resultDto, HttpStatus.OK);

        // 혹은 축약형
        // return ResponseEntity.ok(resultDto);
    }
}

2) 다른 HTTP 상태 코드 사용

단순 성공(200) 뿐 아니라, 상황에 따라 다른 상태 코드를 줄 수 있다.


// 200 OK - 바디 포함
return ResponseEntity
        .status(HttpStatus.OK)
        .body(studentResultDto);

// 404 Not Found - 바디 없음
return ResponseEntity
        .status(HttpStatus.NOT_FOUND)
        .build();

// 축약형 404
return ResponseEntity.notFound().build();

이렇게 하면 StudentResultDto 안에 result="success/fail" 같은 문자열만 두는 것보다, HTTP가 원래 가진 표현력(200, 404, 500 등)을 활용할 수 있어서 REST API스럽고, 클라이언트(프론트) 입장에서도 해석이 더 명확해진다.


❸ Swagger(springdoc-openapi)로 REST API 문서화

1) Gradle 의존성 추가

Swagger 2가 아니라, springdoc-openapi 기반으로 OpenAPI 3.0을 사용했다.


dependencies {
    implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.5.0'
}

서버 재시작 후 아래 주소로 접속하면 Swagger UI가 자동으로 뜬다.

  • Swagger UI : http://localhost:8080/swagger-ui/index.html
  • OpenAPI JSON : http://localhost:8080/v3/api-docs
  • OpenAPI YAML : http://localhost:8080/v3/api-docs.yaml

2) @Tag – 컨트롤러(그룹) 단위 설명

Swagger UI에서 API를 그룹으로 묶기 위해 @Tag를 사용했다.


import io.swagger.v3.oas.annotations.tags.Tag;

@Tag(
    name = "기본 Student CRUD REST API",
    description = "Student의 등록, 수정, 삭제, 목록 조회, 상세 조회 기능을 REST API 로 제공합니다."
)
@RestController
@RequestMapping("/api")
@RequiredArgsConstructor
public class StudentControllerCrud {
    ...
}

이 태그 정보가 Swagger UI 왼쪽의 섹션 이름으로 표시된다.

3) @Operation – 메서드 단위 설명

각 API 엔드포인트(메서드)에는 @Operation을 붙여 요약(summary)과 상세 설명(description)을 작성했다.


import io.swagger.v3.oas.annotations.Operation;

@Operation(
    summary = "학생 목록 조회",
    description = "전체 학생 목록을 조회합니다."
)
@GetMapping("/students")
public StudentResultDto listStudent() {
    return studentServiceCrud.listStudent();
}

@Operation(
    summary = "학생 상세 조회",
    description = "ID 값을 이용해 개별 학생 1명의 상세 정보를 조회합니다.",
    deprecated = true   // Swagger UI 에 해당 API가 deprecated 표시
)
@GetMapping("/students/{id}")
public StudentResultDto detailStudent(@PathVariable("id") Integer id) {
    return studentServiceCrud.detailStudent(id);
}

deprecated = true 로 표시하면 Swagger UI에서 이 API가 “더 이상 권장되지 않는 API”라는 표시가 뜬다. 실무에서는 구버전 API를 남겨 두되, 새 API로 갈아타게 안내할 때 사용한다.


❹ Swagger 문서를 인터페이스로 분리하는 패턴

오늘 배운 것 중에 특히 인상 깊었던 부분은, Swagger 어노테이션을 인터페이스에 모으고, 컨트롤러 클래스는 그 인터페이스를 구현만 하는 구조였다.

1) Swagger 전용 인터페이스


@Tag(
    name = "JSON Student CRUD REST API",
    description = "JSON 요청을 통해 Student의 등록, 수정, 삭제, 조회를 수행하는 REST API"
)
public interface StudentControllerCrudJsonRequestSwagger {

    @Operation(
        summary = "학생 목록",
        description = "전체 학생 목록을 조회합니다."
    )
    @GetMapping("/students")
    StudentResultDto listStudent();

    @Operation(
        summary = "학생 상세",
        description = "개별 학생 1명을 조회합니다."
    )
    @GetMapping("/students/{id}")
    StudentResultDto detailStudent(@PathVariable("id") Integer id);

    @Operation(
        summary = "학생 등록",
        description = "JSON 요청을 통해 신규 학생 1명을 등록합니다."
    )
    @PostMapping("/students")
    StudentResultDto insertStudent(StudentDto studentDto);
}

2) 실제 컨트롤러 구현


@RestController
@RequiredArgsConstructor
public class StudentControllerCrudJsonRequest
        implements StudentControllerCrudJsonRequestSwagger {

    private final StudentServiceCrud studentServiceCrud;

    @Override
    public StudentResultDto listStudent() {
        return studentServiceCrud.listStudent();
    }

    @Override
    public StudentResultDto detailStudent(Integer id) {
        return studentServiceCrud.detailStudent(id);
    }

    @Override
    public StudentResultDto insertStudent(StudentDto studentDto) {
        return studentServiceCrud.insertStudent(studentDto);
    }
}

이 패턴의 장점은 다음과 같다.

  • 컨트롤러 구현 코드가 Swagger 어노테이션으로 지저분해지지 않는다.
  • 인터페이스가 API 스펙 역할을 하기 때문에, 구현체가 반드시 이 스펙을 따르도록 강제할 수 있다.
  • 버전이 여러 개(v1, v2...)일 때 인터페이스를 나눠서 관리하기 좋다.

실무에서도 “API 스펙 인터페이스 + 구현 컨트롤러” 패턴을 쓰는 팀이 꽤 있다.


❺ /v3/api-docs JSON 확인 + JSON 포매터 + Postman Import

1) /v3/api-docs JSON 확인

Swagger UI는 사실 /v3/api-docs 에서 제공되는 OpenAPI JSON 문서를 렌더링한 것일 뿐이다.

  • 브라우저에서 http://localhost:8080/v3/api-docs 접속
  • 엄청 긴 JSON 텍스트가 나온다 → 이것이 API 스펙의 원본
  • 내용 복사 후, JSON 포매터 사이트에 붙여서 구조를 보기 좋게 정리

JSON 포매터를 사용하면 paths, components.schemas, tags 구조를 한눈에 볼 수 있어서 “내 API가 OpenAPI 기준으로 어떻게 정의되어 있는지”를 정확히 확인할 수 있다.

2) Postman에 OpenAPI 스펙 Import

다음으로, 이 OpenAPI JSON을 Postman에 가져와서 API 테스트 컬렉션으로 만드는 작업을 실습했다.

  1. Postman 실행 후 상단의 Import 버튼 클릭
  2. Raw text 탭 선택
  3. /v3/api-docs 에서 복사한 JSON 전체를 붙여넣기
  4. Postman이 포맷을 자동으로 OpenAPI 3.0 으로 인식
  5. Import as: API Collection 으로 표시되는 것 확인 후 Import 클릭

이렇게 하면 Swagger에서 정의한 모든 엔드포인트가 Postman Collection으로 자동 생성된다. 각 요청에는 이미 HTTP 메서드, URL, PathVariable 구조, RequestBody 형식이 들어있어서, 별도로 등록할 필요 없이 바로 Send 버튼만 눌러 테스트할 수 있다.

실무에서는 이 과정을 통해 “백엔드 개발자가 만든 Swagger 스펙 → 프론트/QA 팀이 Postman으로 가져와 테스트” 라는 협업 흐름이 자연스럽게 만들어진다.


❻ 오늘 실습에서 정리된 핵심 개념 요약

  • 레이어드 아키텍처 Controller - Service - Repository - Entity/DTO를 분리해서 유지보수성과 테스트성을 높이는 구조.
  • ResponseEntity<T> HTTP 응답 코드, 헤더, 바디를 직접 제어할 수 있는 스프링의 응답 래퍼. ok(), status(), notFound() 등으로 표현.
  • springdoc-openapi Spring MVC 기반 REST API를 OpenAPI 3.0 문서로 자동 변환해 주는 라이브러리. Swagger UI도 함께 제공.
  • @Tag / @Operation Swagger에서 컨트롤러 그룹과 개별 API 메서드를 설명하기 위한 어노테이션.
  • /v3/api-docs Swagger UI가 참조하는 OpenAPI JSON 원본 문서. 이 파일이 실제 “API 계약서”.
  • Postman Import (OpenAPI) /v3/api-docs JSON을 Postman에 가져오면 Swagger 스펙을 기반으로 한 API Collection이 자동 생성되어 테스트가 매우 편해진다.

❼ 오늘 실습에서 얻은 교훈

단순히 “엔드포인트를 만들었다”에서 끝나는 것이 아니라, HTTP 상태 코드, 문서화, 테스트 도구까지 연결하는 것이 요즘 백엔드 개발자의 기본 역량이라는 걸 몸으로 느낀 날이었다.

  • 코드를 짜는 것뿐 아니라, API를 설명하고 공유할 수 있는 상태까지 만들어야 한다.
  • Swagger(OpenAPI)와 Postman은 이를 도와주는 표준 도구 세트다.
  • 앞으로 개인 프로젝트에서도 REST API를 만들 때, 오늘 실습한 흐름을 그대로 적용하면 포트폴리오의 완성도가 크게 올라갈 것이다.