Swagger 경험이 더 많기도 하고, 프로젝트 초반에 이미 Swagger 의존성을 추가해두었기 때문에 이번에는 Swagger 기반으로 API 문서 작성을 진행하였다.
오늘 진행한 작업은 다음과 같다.
- Swagger 의존성 추가
- SecurityConfig에서 Swagger 경로 허용
- 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의 설명이 기본값(엔드포인트 이름)으로만 표시된다.

(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를 활용한 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
'Project > AutoSchedule' 카테고리의 다른 글
| 8.5일차 - Spring WebSocket 보안 3단계 추가 (0) | 2025.11.11 |
|---|---|
| 8일차 - WebSocket 환경 세팅 (0) | 2025.11.10 |
| 6일차 - Task/CalendarEvent API 검증 & 캘린더 통합 (0) | 2025.11.07 |
| 5일차 - Team / TeamMember API 기능 구현 (0) | 2025.11.05 |
| 4일차 - 프론트엔드 환경 세팅 & 구조 설계 (0) | 2025.11.03 |