Project/AutoSchedule

7일차 - Swagger or Postman API 문서 작성

sowon02 2025. 11. 9. 14:40

Swagger 경험이 더 많기도 하고, 프로젝트 초반에 이미 Swagger 의존성을 추가해두었기 때문에 이번에는 Swagger 기반으로 API 문서 작성을 진행하였다.

오늘 진행한 작업은 다음과 같다.

  1. Swagger 의존성 추가
  2. SecurityConfig에서 Swagger 경로 허용
  3. Controller 메서드 및 DTO에 @Operation, @Schema 등 문서화 어노테이션 추가

1. 의존성 추가

먼저 build.gradle 에 다음과 같이 Swagger 의존성을 추가한다. 

	// Swagger / OpenAPI 문서화
	implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.6.0'

 

참고로,  

 

  • Spring Boot 3.x를 사용하는 경우 → springdoc-openapi-starter-webmvc-ui:2.6.0 버전을 사용하면 된다. 
  • Spring Boot 2.x를 사용하는 경우 → 2.6.0 대신 1.7.0 버전을 사용해야 한다.

의존성 추가 후 빌드를 완료하면, 기본적으로 Swagger UI는 http://localhost:8080/swagger-ui/index.html

경로에서 확인할 수 있다.


 

2. SecurityConfig에서 Swagger 경로 허용 

SecurityConfig 클래스는 어떤 경로를 인증 없이 접근할 수 있는지 설정하는 파일이다.
기본적으로 Swagger UI와 관련된 경로는 다음과 같다.

  • /swagger-ui/**
  • /swagger-ui.html
  • /v3/api-docs/**

초기에는 이 경로들이 허용되지 않아 401 Unauthorized 에러가 발생했기 때문에,
다음과 같이 접근을 허용하도록 설정을 추가했다.

@Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http
            .csrf(AbstractHttpConfigurer::disable)  // REST API이므로 CSRF 비활성화
            .cors(cors -> cors.configurationSource(corsConfigurationSource()))  // CORS 설정
            .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))  // JWT 사용하므로 세션 사용 안 함
            .authorizeHttpRequests(auth -> auth
                .requestMatchers("/api/auth/**").permitAll()  // 인증 API는 모두 허용
                .requestMatchers("/error").permitAll()  // 에러 페이지 허용
                .requestMatchers(PathRequest.toStaticResources().atCommonLocations()).permitAll() // 정적 리소스 공통 경로 허용
                .requestMatchers("/swagger-ui/**", "/v3/api-docs/**", "/swagger-ui.html").permitAll() // Swagger UI 및 OpenAPI 문서 허용
                .requestMatchers("/", "/index.html", "/login.html", "/signup.html",
                                 "/users.html", "/task.html", "/team.html", "/event.html").permitAll() // 루트 및 HTML 페이지 허용
                .requestMatchers("/ws/**").permitAll() // 웹소켓 핸드셰이크 허용
                .requestMatchers("/actuator/**").permitAll()  // Actuator (선택사항)
                .requestMatchers("/hello").permitAll()  // 테스트 엔드포인트 허용
                .anyRequest().authenticated()  // 나머지는 인증 필요
            )
            .addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);  // JWT 필터 추가

        return http.build();
    }

3. 문서화 어노테이션 추가

이제 Controller와 DTO에 Swagger 문서화 어노테이션을 추가한다.
어노테이션을 추가하지 않으면 Swagger UI는 정상적으로 뜨지만, 각 API의 설명이 기본값(엔드포인트 이름)으로만 표시된다.

Swagger UI - 문서화 어노테이션이 없는 /api/users/{id} 화면

 


(1) DTO 문서화

요청과 응답 객체에는 @Schema 어노테이션을 붙여 필드의 의미를 명시한다.
이 어노테이션은 Swagger 문서에서 데이터 구조를 자동으로 표현해주며, description과 example을 함께 지정하면 더욱 직관적인 문서를 만들 수 있다.

// UserCreateRequest.java
@Getter
@Setter
@Schema(description = "사용자 생성 요청 DTO")
public class UserCreateRequest {
    @Schema(description = "사용자 이메일", example = "user@example.com")
    private String email;
    @Schema(description = "사용자 이름", example = "홍길동")
    private String name;
}
// UserResponse.java
@Getter
@Setter
@Schema(description = "사용자 응답 DTO")
public class UserResponse {
    @Schema(description = "사용자 ID", example = "1")
    private Long id;
    @Schema(description = "사용자 이메일", example = "user@example.com")
    private String email;
    @Schema(description = "사용자 이름", example = "홍길동")
    private String name;
    @Schema(description = "계정 생성 시각", example = "2025-12-01T10:00:00+09:00")
    private OffsetDateTime createdAt;
}

(2) Controller 문서화

컨트롤러 클래스에는 @Tag 어노테이션을 사용해 API 그룹을 지정하고,
각 메서드에는 @Operation, @ApiResponses, @Parameter 등을 추가해 요청과 응답의 의미를 명확히 기술한다.

//UserController.java
@RestController
@RequestMapping("/api/users")
@Tag(name = "User API", description = "사용자 CRUD API")
public class UserController {
    private final UserService userService;

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

    @PostMapping
    @Operation(summary = "사용자 생성", description = "새로운 사용자를 생성합니다.")
    @ApiResponses({
        @ApiResponse(responseCode = "201", description = "생성 성공",
            content = @Content(schema = @Schema(implementation = UserResponse.class))),
        @ApiResponse(responseCode = "400", description = "요청 검증 실패")
    })
    public ResponseEntity<UserResponse> createUser(@RequestBody UserCreateRequest request) {
        UserResponse response = userService.createUser(request);
        return ResponseEntity.status(HttpStatus.CREATED).body(response);
    }

    @GetMapping("/{id}")
    @Operation(summary = "사용자 단건 조회", description = "사용자 ID로 단일 사용자 정보를 조회합니다.")
    @ApiResponses({
        @ApiResponse(responseCode = "200", description = "조회 성공",
            content = @Content(schema = @Schema(implementation = UserResponse.class))),
        @ApiResponse(responseCode = "404", description = "사용자를 찾을 수 없음")
    })
    public ResponseEntity<UserResponse> getUser(@Parameter(description = "사용자 ID", example = "1") @PathVariable Long id) {
        UserResponse response = userService.findById(id);
        return ResponseEntity.ok(response);
    }

    @GetMapping("/email/{email}")
    @Operation(summary = "사용자 이메일 조회", description = "이메일로 사용자 정보를 조회합니다.")
    public ResponseEntity<UserResponse> getUserByEmail(@Parameter(description = "사용자 이메일", example = "user@example.com") @PathVariable String email) {
        UserResponse response = userService.findByEmail(email);
        return ResponseEntity.ok(response);
    }

    @GetMapping
    @Operation(summary = "사용자 목록 조회", description = "모든 사용자 목록을 조회합니다.")
    @ApiResponse(responseCode = "200", description = "조회 성공")
    public ResponseEntity<List<UserResponse>> getAllUsers() {
        List<UserResponse> responses = userService.findAll();
        return ResponseEntity.ok(responses);
    }

    @PutMapping("/{id}")
    @Operation(summary = "사용자 수정", description = "사용자 ID로 사용자 정보를 수정합니다.")
    @ApiResponses({
        @ApiResponse(responseCode = "200", description = "수정 성공",
            content = @Content(schema = @Schema(implementation = UserResponse.class))),
        @ApiResponse(responseCode = "404", description = "사용자를 찾을 수 없음")
    })
    public ResponseEntity<UserResponse> updateUser(
            @Parameter(description = "사용자 ID", example = "1") @PathVariable Long id,
            @RequestBody UserUpdateRequest request) {
        UserResponse response = userService.updateUser(id, request);
        return ResponseEntity.ok(response);
    }

    @DeleteMapping("/{id}")
    @Operation(summary = "사용자 삭제", description = "사용자 ID로 사용자를 삭제합니다.")
    @ApiResponses({
        @ApiResponse(responseCode = "204", description = "삭제 성공"),
        @ApiResponse(responseCode = "404", description = "사용자를 찾을 수 없음")
    })
    public ResponseEntity<Void> deleteUser(@Parameter(description = "사용자 ID", example = "1") @PathVariable Long id) {
        userService.deleteUser(id);
        return ResponseEntity.noContent().build();
    }
}

Swagger UI 결과

위와 같이 어노테이션을 추가하면 Swagger UI에서 각 API의
요약, 설명, 요청·응답 스키마가 자동으로 반영되어 훨씬 가독성이 높아진다.

Swagger UI - 문서화 어노테이션을 추가한 User API

 


결론

오늘은 Swagger를 활용한 API 문서화 작업을 진행하였다. 문서화 작업을 해두면 팀 단위 협업 시 커뮤니케이션이 훨씬 수월해지고, 클라이언트 개발자 역시 API 구조를 명확히 이해할 수 있다.

Swagger는 코드 기반으로 문서를 자동 생성하므로, API 변경 시 문서를 별도로 수정할 필요가 없다는 점에서

유지보수 효율도 높다.

 

+ 다른 수정 코드는 깃허브에서 ...

https://github.com/sowon2222/autoschedule/tree/main/src/main/java/com/example/sbb/controller

 

autoschedule/src/main/java/com/example/sbb/controller at main · sowon2222/autoschedule

회의, 개인 일정, 작업 시간을 입력하면 AI가 자동으로 최적화된 스케줄을 생성해주는 팀 일정 관리 시스템 - sowon2222/autoschedule

github.com

https://github.com/sowon2222/autoschedule/tree/main/src/main/java/com/example/sbb/dto

 

autoschedule/src/main/java/com/example/sbb/dto at main · sowon2222/autoschedule

회의, 개인 일정, 작업 시간을 입력하면 AI가 자동으로 최적화된 스케줄을 생성해주는 팀 일정 관리 시스템 - sowon2222/autoschedule

github.com