프로젝트

(6) Swagger 어노테이션 정리

nkdev 2025. 3. 30. 20:30

@Tag

API 그룹 지정. Tag에 설정된 name이 같은 것끼리 하나의 API 그룹으로 묶는다.

Controller 또는 Controller의 메서드 영역에 설정한다. REST API의 엔드포인트는 컨트롤러에 있으므로, 컨트롤러 클래스 위에 @Tag어노테이션을 붙여서 API 그룹을 설정할 수 있다.

  • Target - ANNOTATION_TYPE, METHOD, TYPE
  • name : 태그 이름
  • description : 태그 설명
@Tag(name="User", description="사용자 API")
@RestController
@RequestMapping("/users")
public class UserApiController {
    //Post, Get, Patch, ... 에 해당하는 메서드들
}
 

@Operation

각 API 동작(Get, Post, Put 등)에 대한 상세 설명. Controller의 메서드 영역에 설정한다.

Swagger UI에서 세부 내용을 펼치지 않았을 때 간략히 확인할 수 있는 정보는 summary에, 필요에 따라 상세 정보를 표기하고자 한다면 description에 설명을 추가한다.

  • Target - ANNOTATION_TYPE, METHOD
  • summary : API에 대한 간략 설명
  • description : API에 대한 상세 설명
@Operation(summary = "이벤트 회원 조회", description = "특정 모임에 속한 회원을 조회한다.")
public List<EventUserDto> getUserByEvent(@Parameter(description = "모임 ID", example = "1") @PathVariable("eventId") Long eventId){
...
}
 

@Operation 어노테이션의 parameters 프로퍼티를 사용해 Swagger UI에 파라미터 입력 창을 띄울 수도 있다.

 @Operation(summary = "이벤트 회원 조회", description = "특정 모임에 속한 회원을 조회한다.",
    parameters = {@Parameter(name="eventId", description = "모임 ID")})
public List<EventUserDto> getUserByEvent(@Parameter(description = "모임 ID", example = "1") @PathVariable("eventId") Long eventId){
...
}

 

@Parameter

메서드 파라미터에 대한 설명 추가. 경로 변수, 쿼리 파라미터 등에서 유용하다.

타겟은 4가지지만 PARAMETER에 작성하면 name을 설정해줄 필요가 없어서 주로 PARAMETER에 작성하여 명시적으로 설정하는 것을 선호하는 듯 하다.

  • Target - ANNOTATION_TYPE, FIELD, METHOD, PARAMETER
  • name : 파라미터 이름
  • description : 파라미터 설명
  • in : 파라미터 위치
    • query : query string 방식으로 전달하는 경우
    • header : header에 담겨 전달하는 경우
    • path : pathvariable 방식으로 전달하는 경우
    • cookie : cookie에 담겨 전달하는경우

아까 @Operation의 내부 속성으로 @Parameter을 준 것과 동일하게 메서드의 @PathVariable 인자 앞에 @Parameter을 붙여서 파라미터입력창을 나타낼 수도 있다. 그런데 @PathVariable의 name속성을 파라미터 이름과 동일하게 지정해줘야 한다.

@GetMapping("/{eventId}")
    public List<EventUserDto> getUserByEvent(@Parameter(description = "모임 ID", example = "1") @PathVariable("eventId") Long eventId){
        ...
    }

 

@ApiResponse

각 엔드포인트 응답 상태 코드에 대한 설명 추가. 가능한 HTTP 응답 상태 코드와 그에 대한 설명, 응답 본문의 구조를 알려준다.

  • responseCode - http 응답 상태코드
  • description - response 에 대한 설명
  • content - response payload 구조 (응답 본문이 어떤 형식으로 반환되는지 정의)
    • mediaType - 응답 데이터 형식
    • schema - payload에서 이용하는 schema
      • hidden - schema 숨김 여부
      • implementation - schema 대상 클래스  

@ApiResponses 안에 value로 @ApiResponse들을 넣어주면 된다.

  • responseCode = "200": HTTP 상태 코드 200은 요청이 성공적으로 처리되었음
  • description = "200 ok 요청이 성공적으로 처리되었습니다.": 200 상태 코드에 대한 설명
  • content = {@Content(...)}: 응답 본문이 어떤 형식으로 반환되는지 정의
    • content = @Content 만 있으면 응답 본문이 제공되지 않고 단순히 상태 코드만 반환될 수 있음을 의미
  • mediaType = "application/json": 응답 데이터의 형식은 JSON
  • schema=@Schema(implementation = EventUserDto.class): 데이터는 EventUserDto클래스 형태임
  • array=@ArraySchema(schema=@Schema(...위와 동일)) : 데이터가 EventUserDto클래스의 리스트 또는 배열 형태로 반환됨
 @Operation(summary = "이벤트 회원 조회", description = "특정 모임에 속한 회원을 조회한다.")
    //exceptionhandler response status
    @ApiResponses(value = {
            @ApiResponse(responseCode = "200", description = "200 ok 요청이 성공적으로 처리되었습니다.",
                    content = {@Content(mediaType = "application/json", array=@ArraySchema(schema=@Schema(implementation = EventUserDto.class)))}),
            @ApiResponse(responseCode = "201", description = "201 created 요청 결과 새로운 리소스가 생성되었습니다.", content = @Content),
            @ApiResponse(responseCode = "202", description = "202 accepted 요청을 수신하였지만 그에 응하여 행동할 수 없습니다.", content = @Content),
            @ApiResponse(responseCode = "400", description = "400 bad request 서버가 요청을 이해할 수 없습니다.", content = @Content),
            @ApiResponse(responseCode = "404", description = "404 not found 서버가 요청받은 리소스를 찾을 수 없습니다.", content = @Content),
            @ApiResponse(responseCode = "500", description = "500 server error 서버가 처리할 수 없는 요청입니다.", content = @Content)})
        @GetMapping("/{eventId}")
    public List<EventUserDto> getUserByEvent(@Parameter(description = "모임 ID", example = "1") @PathVariable("eventId") Long eventId){
       ...
    }
 

@RequestBody, @RequestParam

요청 본문이나 파라미터에 대한 설명 작성

@Operation(summary = "사용자 생성")
@PostMapping("/users")
public User createUser(
    @RequestBody(description = "생성할 사용자 정보") User user,
    @RequestParam(description = "사용자 이름", required = true) String name) {
    // 로직
}
 

@Schema

API Schema 지정. 각 필드값에 대한 설명, 기본값, 허용 가능 값 등 api 문서를 더 상세히 기술하는 데 사용된다.

  • Target - ANNOTATION_TYPE, FIELD, METHOD, PARAMETER, TYPE
  • description : 설명
  • defaultValue : 기본값
  • allwableValues
    @Schema(description="특정 모임에 속한 회원")
    @Getter
    @Setter
    public static class EventUserDto{
        @Schema(description = "이름", example = "김철수", required = true)
        private String name;
        @Schema(description = "성별", example = "M", required = true)
        private Gender gender;
        @Schema(description = "소개", example = "성남시 분당구에 사는 직장인입니다.", required = true)
        private String introduction;

    }
 
    public List<EventUserDto> getUserByEvent(@Parameter(description = "모임 ID", example = "1") @PathVariable("eventId") Long eventId){
        EventUserDto dto = new EventUserDto();
        dto.setName("ddd");
        dto.setGender(Gender.W);
        dto.setIntroduction("ddd입니다.");

        List<EventUserDto> list = new ArrayList<>();
        list.add(dto);

        return list;
    }
 

이런 식으로 컨트롤러의 반환 값이 되는 엔티티에 @Schema를 붙여두면 어떤 값이 반환되는지 Swagger에 명확하게 드러낼 수 있다.

 

 

required=true로 설정하면 필드 옆에 빨간 별 표시가 뜨면서 반드시 필요한 값임을 표시할 수 있다.

 

컨트롤러 설정 예시

package lems.cowshed.api.controller.user;

import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.media.ArraySchema;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
import io.swagger.v3.oas.annotations.tags.Tag;
import lems.cowshed.domain.event.Event;
import lems.cowshed.domain.user.Gender;
import lems.cowshed.domain.user.User;
import lombok.Getter;
import lombok.Setter;
import org.springframework.web.bind.annotation.*;

import java.util.ArrayList;
import java.util.List;

@Tag(name="User", description="사용자 API")
@RestController
@RequestMapping("/users")
public class UserApiController {

    @Operation(summary = "모임 회원 조회", description = "특정 모임에 속한 회원을 조회한다. [이벤트 상세 > 참여자 목록]")
    @ApiResponses(value = {
            @ApiResponse(responseCode = "200", description = "200 ok 요청이 성공적으로 처리되었습니다.",
                    content = {@Content(mediaType = "application/json", array=@ArraySchema(schema=@Schema(implementation = EventUserDto.class)))})})
    @GetMapping("/event/{eventId}")
    public List<EventUserDto> getUserByEvent(@Parameter(name="eventId", description = "모임 ID", example = "1") @PathVariable("eventID") Long eventId){
        //return List<EventUserDto>
        EventUserDto eventUserDto = new EventUserDto();
        eventUserDto.setName("kim");
        eventUserDto.setGender(Gender.W);
        eventUserDto.setIntroduction("안녕하세요 kim 입니다.");

        List<EventUserDto> list = new ArrayList<>();
        list.add(eventUserDto);

        return list;


    }

    @Operation(summary = "마이페이지 회원 조회", description = "본인 정보를 가져온다. [마이페이지]")
    @ApiResponses(value = {
            @ApiResponse(responseCode = "200", description = "200 ok 요청이 성공적으로 처리되었습니다.",
                    content = {@Content(mediaType = "application/json", schema=@Schema(implementation = MyInfoDto.class))})})
    @GetMapping("/my-page/{userId}")
    public void getMyInfoById(@Parameter(name = "userId", description = "회원 ID", example = "1") @PathVariable("userId") Long userId){
        //return MyInfoDto
    }

    @Operation(summary = "회원 등록", description = "새로운 회원 정보를 저장한다.")
    @ApiResponses(value = {
            @ApiResponse(responseCode = "200", description = "200 ok 요청이 성공적으로 처리되었습니다.",
                    content = {@Content(mediaType = "application/json", schema=@Schema(implementation = UserSaveRequestDto.class))})})

    @PostMapping("/")
    public void saveUser(@RequestBody UserSaveRequestDto userSaveRequestDto){
        //return Long
    }

    @Operation(summary = "회원 수정", description = "회원 정보를 수정한다. [프로필 편집]")
    @ApiResponses(value = {
            @ApiResponse(responseCode = "200", description = "200 ok 요청이 성공적으로 처리되었습니다.",
                    content = {@Content(mediaType = "application/json", schema=@Schema(implementation = UserUpdateRequestDto.class))})})

    @PatchMapping("/{userId}")
    public void editUser(@Parameter(name = "userId", description = "회원 ID", example = "1") @PathVariable("userId") Long userId, @RequestBody UserUpdateRequestDto userUpdateDto){
        //return
    }

    @Getter
    @Setter
    @Schema(description="특정 모임에 속한 회원")
    public static class EventUserDto{
        @Schema(description = "이름", example = "김철수", required = true)
        private String name;
        @Schema(description = "성별", example = "M", required = true)
        private Gender gender;
        @Schema(description = "소개", example = "성남시 분당구에 사는 직장인입니다.", required = true)
        private String introduction;
    }

    @Getter
    @Setter
    @Schema(description = "마이페이지 회원 정보")
    public static class MyInfoDto{
        @Schema(description = "이름", example = "김철수", required=true)
        private String name;
        @Schema(description = "성별", example = "M", required=true)
        private Gender gender;
        @Schema(description = "생년월일", example = "1999-05-22", required=true)
        private String birth;
        @Schema(description = "성격유형", example = "ISTP")
        private String character;
        @Schema(description = "참여 모임", example = "")
        private List<Event> joinEvents;
        @Schema(description = "북마크 모임", example = "김철수")
        private List<Event> bookmarkEvents;
    }
    @Getter
    @Setter
    @Schema(description = "회원 등록")
    public static class UserSaveRequestDto {
        @Schema(description = "이름", example = "김철수", required=true)
        private String name;
        @Schema(description = "성별", example = "M", required=true)
        private Gender gender;
        @Schema(description = "이메일", example = "cheolsukim@lems.com")
        private String email;
        @Schema(description = "생년월일", example = "1999-05-22", required=true)
        private String birth;
        @Schema(description = "지역명", example = "서울시", required=true)
        private String local