Back to blog

Tài liệu API với OpenAPI và Swagger trong Spring Boot

javaspring-bootopenapiswaggerapidocumentation
Tài liệu API với OpenAPI và Swagger trong Spring Boot

Giới thiệu

Tài liệu API tốt là điểm khác biệt giữa việc developers yêu thích API của bạn và bỏ cuộc trong thất vọng. Với OpenAPI (trước đây là Swagger), bạn có thể tự động tạo tài liệu đẹp, tương tác trực tiếp từ code Spring Boot.

Bạn sẽ học được gì

✅ Các nguyên tắc cơ bản của OpenAPI 3.0
✅ Thiết lập SpringDoc OpenAPI trong Spring Boot
✅ Tự động tạo Swagger UI từ controllers
✅ Document endpoints với annotations phong phú
✅ Thêm ví dụ request/response và schemas
✅ Document authentication (JWT, OAuth2, API Keys)
✅ Tùy chỉnh giao diện Swagger UI
✅ Bảo mật API docs trong production
✅ Xuất OpenAPI JSON/YAML cho các công cụ bên ngoài

Yêu cầu


1. Hiểu về OpenAPI và Swagger

OpenAPI là gì?

OpenAPI Specification (OAS) là một tiêu chuẩn, giao diện không phụ thuộc ngôn ngữ để mô tả RESTful APIs. Nó cho phép cả con người và máy tính hiểu API làm gì mà không cần xem source code.

Lợi ích chính:

  • Định dạng chuẩn hóa: JSON hoặc YAML mô tả toàn bộ API
  • Tự động tạo: Tạo tài liệu, client SDKs, và server stubs
  • Kiểm thử tương tác: Thử API calls trực tiếp trong trình duyệt
  • Thiết kế contract-first: Định nghĩa API trước khi triển khai

Swagger là gì?

Swagger là hệ sinh thái công cụ xung quanh OpenAPI:

Công cụMục đích
Swagger UITài liệu API tương tác trong trình duyệt
Swagger EditorThiết kế và chỉnh sửa OpenAPI specs
Swagger CodegenTạo code client/server từ specs
SwaggerHubNền tảng thiết kế API cộng tác

Khuyến nghị: Luôn sử dụng OpenAPI 3.0+ cho các dự án mới.


2. Thiết lập SpringDoc OpenAPI

Thêm Dependencies

SpringDoc là thư viện hiện đại, được bảo trì tích cực cho OpenAPI trong Spring Boot.

Maven:

<dependency>
    <groupId>org.springdoc</groupId>
    <artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
    <version>2.3.0</version>
</dependency>

Gradle:

implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.3.0'

Cấu hình cơ bản

Với zero configuration, SpringDoc tự động:

  • Quét tất cả các class @RestController
  • Tạo OpenAPI specification
  • Phục vụ Swagger UI tại /swagger-ui.html

Truy cập tài liệu:

http://localhost:8080/swagger-ui.html  → Swagger UI tương tác
http://localhost:8080/v3/api-docs      → OpenAPI JSON spec
http://localhost:8080/v3/api-docs.yaml → OpenAPI YAML spec

Tùy chỉnh thông tin API

import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.info.Contact;
import io.swagger.v3.oas.models.info.Info;
import io.swagger.v3.oas.models.info.License;
import io.swagger.v3.oas.models.servers.Server;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
 
@Configuration
public class OpenApiConfig {
 
    @Bean
    public OpenAPI customOpenAPI() {
        return new OpenAPI()
            .info(new Info()
                .title("E-Commerce API")
                .version("1.0.0")
                .description("REST API để quản lý sản phẩm, đơn hàng và khách hàng")
                .termsOfService("https://example.com/terms")
                .contact(new Contact()
                    .name("Hỗ trợ API")
                    .email("[email protected]")
                    .url("https://example.com/support"))
                .license(new License()
                    .name("MIT License")
                    .url("https://opensource.org/licenses/MIT")))
            .addServersItem(new Server()
                .url("https://api.example.com")
                .description("Production server"))
            .addServersItem(new Server()
                .url("http://localhost:8080")
                .description("Development server"));
    }
}

Application Properties

# application.yml
springdoc:
  api-docs:
    enabled: true
    path: /v3/api-docs
  swagger-ui:
    enabled: true
    path: /swagger-ui.html
    operationsSorter: method  # Sắp xếp theo HTTP method
    tagsSorter: alpha         # Sắp xếp tags theo alphabet
    displayRequestDuration: true
    filter: true              # Bật search filter
    tryItOutEnabled: true     # Bật "Try it out" mặc định
  show-actuator: false        # Ẩn actuator endpoints
  packages-to-scan: com.example.controller
  paths-to-match: /api/**     # Chỉ document /api/* paths

3. Document Controllers

Annotations cơ bản

import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
import io.swagger.v3.oas.annotations.tags.Tag;
 
@RestController
@RequestMapping("/api/v1/products")
@Tag(name = "Products", description = "Các thao tác quản lý sản phẩm")
public class ProductController {
 
    @GetMapping
    @Operation(
        summary = "Liệt kê tất cả sản phẩm",
        description = "Trả về danh sách sản phẩm phân trang với bộ lọc tùy chọn"
    )
    @ApiResponses({
        @ApiResponse(responseCode = "200", description = "Lấy sản phẩm thành công"),
        @ApiResponse(responseCode = "400", description = "Tham số request không hợp lệ")
    })
    public ResponseEntity<Page<ProductResponse>> getProducts(
            @Parameter(description = "Lọc theo danh mục")
            @RequestParam(required = false) String category,
 
            @Parameter(description = "Số trang (bắt đầu từ 0)", example = "0")
            @RequestParam(defaultValue = "0") int page,
 
            @Parameter(description = "Kích thước trang (tối đa 100)", example = "20")
            @RequestParam(defaultValue = "20") int size) {
 
        return ResponseEntity.ok(productService.findAll(category, page, size));
    }
 
    @GetMapping("/{id}")
    @Operation(summary = "Lấy sản phẩm theo ID")
    @ApiResponses({
        @ApiResponse(responseCode = "200", description = "Tìm thấy sản phẩm"),
        @ApiResponse(responseCode = "404", description = "Không tìm thấy sản phẩm")
    })
    public ResponseEntity<ProductResponse> getProduct(
            @Parameter(description = "ID sản phẩm", required = true, example = "123")
            @PathVariable Long id) {
 
        return ResponseEntity.ok(productService.findById(id));
    }
 
    @PostMapping
    @Operation(summary = "Tạo sản phẩm mới")
    @ApiResponses({
        @ApiResponse(responseCode = "201", description = "Tạo sản phẩm thành công"),
        @ApiResponse(responseCode = "400", description = "Dữ liệu đầu vào không hợp lệ"),
        @ApiResponse(responseCode = "409", description = "Sản phẩm đã tồn tại")
    })
    public ResponseEntity<ProductResponse> createProduct(
            @RequestBody @Valid ProductRequest request) {
 
        ProductResponse product = productService.create(request);
        return ResponseEntity
            .status(HttpStatus.CREATED)
            .header("Location", "/api/v1/products/" + product.id())
            .body(product);
    }
}

Nhóm với Tags

// Tổ chức endpoints thành các nhóm logic
@Tag(name = "Products", description = "Các thao tác CRUD sản phẩm")
@Tag(name = "Inventory", description = "Quản lý kho hàng")
public class ProductController {
 
    @GetMapping
    @Operation(tags = {"Products"})
    public ResponseEntity<Page<Product>> getProducts() { /* ... */ }
 
    @GetMapping("/{id}/stock")
    @Operation(tags = {"Inventory"})
    public ResponseEntity<StockInfo> getStock(@PathVariable Long id) { /* ... */ }
}

Ẩn Endpoints

// Ẩn endpoints cụ thể khỏi tài liệu
@Operation(hidden = true)
@GetMapping("/internal/health")
public ResponseEntity<String> internalHealth() {
    return ResponseEntity.ok("OK");
}
 
// Hoặc loại trừ toàn bộ controllers
@Hidden
@RestController
@RequestMapping("/internal")
public class InternalController {
    // Tất cả endpoints đều bị ẩn
}

4. Document Request/Response Models

Schema Annotations

import io.swagger.v3.oas.annotations.media.Schema;
 
@Schema(description = "Request tạo/cập nhật sản phẩm")
public record ProductRequest(
 
    @Schema(
        description = "Tên sản phẩm",
        example = "MacBook Pro 16\"",
        minLength = 3,
        maxLength = 100,
        requiredMode = Schema.RequiredMode.REQUIRED
    )
    @NotBlank
    @Size(min = 3, max = 100)
    String name,
 
    @Schema(
        description = "Giá sản phẩm (USD)",
        example = "2499.99",
        minimum = "0.01",
        maximum = "999999.99"
    )
    @NotNull
    @DecimalMin("0.01")
    BigDecimal price,
 
    @Schema(
        description = "Danh mục sản phẩm",
        example = "electronics",
        allowableValues = {"electronics", "clothing", "books", "home", "sports"}
    )
    @NotBlank
    String category,
 
    @Schema(
        description = "Mô tả sản phẩm",
        example = "MacBook Pro mạnh mẽ nhất từ trước đến nay",
        maxLength = 1000,
        nullable = true
    )
    @Size(max = 1000)
    String description
) {}

Response Model với Examples

@Schema(description = "Object response sản phẩm")
public record ProductResponse(
 
    @Schema(description = "ID sản phẩm duy nhất", example = "12345")
    Long id,
 
    @Schema(description = "Tên sản phẩm", example = "MacBook Pro 16\"")
    String name,
 
    @Schema(description = "Giá (USD)", example = "2499.99")
    BigDecimal price,
 
    @Schema(description = "Danh mục sản phẩm", example = "electronics")
    String category,
 
    @Schema(description = "Tình trạng còn hàng")
    Boolean inStock,
 
    @Schema(description = "Thời gian tạo", example = "2026-02-07T10:30:00Z")
    LocalDateTime createdAt
) {}

5. Thêm Request/Response Examples

Inline Examples

@PostMapping
@Operation(summary = "Tạo đơn hàng")
@io.swagger.v3.oas.annotations.parameters.RequestBody(
    description = "Request tạo đơn hàng",
    required = true,
    content = @Content(
        mediaType = "application/json",
        schema = @Schema(implementation = OrderRequest.class),
        examples = {
            @ExampleObject(
                name = "Đơn hàng đơn giản",
                summary = "Đơn hàng một sản phẩm",
                value = """
                    {
                      "customerId": 123,
                      "items": [
                        {"productId": 456, "quantity": 1}
                      ],
                      "shippingAddress": {
                        "street": "123 Lý Thường Kiệt",
                        "city": "Hồ Chí Minh",
                        "zipCode": "70000"
                      }
                    }
                    """
            ),
            @ExampleObject(
                name = "Nhiều sản phẩm",
                summary = "Đơn hàng nhiều sản phẩm",
                value = """
                    {
                      "customerId": 123,
                      "items": [
                        {"productId": 456, "quantity": 2},
                        {"productId": 789, "quantity": 1}
                      ],
                      "shippingAddress": {
                        "street": "456 Nguyễn Huệ",
                        "city": "Hà Nội",
                        "zipCode": "10000"
                      },
                      "couponCode": "GIAM10"
                    }
                    """
            )
        }
    )
)
public ResponseEntity<OrderResponse> createOrder(@RequestBody @Valid OrderRequest request) {
    // ...
}

6. Document Authentication

JWT Bearer Token

@Configuration
public class OpenApiConfig {
 
    @Bean
    public OpenAPI customOpenAPI() {
        return new OpenAPI()
            .info(new Info().title("Secure API").version("1.0.0"))
            .addSecurityItem(new SecurityRequirement().addList("bearerAuth"))
            .components(new Components()
                .addSecuritySchemes("bearerAuth", new SecurityScheme()
                    .type(SecurityScheme.Type.HTTP)
                    .scheme("bearer")
                    .bearerFormat("JWT")
                    .description("Nhập JWT token từ /api/auth/login")));
    }
}

API Key Authentication

@Bean
public OpenAPI customOpenAPI() {
    return new OpenAPI()
        .components(new Components()
            .addSecuritySchemes("apiKey", new SecurityScheme()
                .type(SecurityScheme.Type.APIKEY)
                .in(SecurityScheme.In.HEADER)
                .name("X-API-Key")
                .description("API key để xác thực")));
}

Security cấp Endpoint

@RestController
@RequestMapping("/api/v1/products")
public class ProductController {
 
    // Endpoint công khai - không cần auth
    @GetMapping
    @Operation(summary = "Liệt kê sản phẩm", security = {})
    public ResponseEntity<List<Product>> getProducts() { /* ... */ }
 
    // Yêu cầu authentication
    @PostMapping
    @Operation(
        summary = "Tạo sản phẩm",
        security = @SecurityRequirement(name = "bearerAuth")
    )
    public ResponseEntity<Product> createProduct(@RequestBody ProductRequest request) { /* ... */ }
}

7. Tùy chỉnh Swagger UI

Cấu hình giao diện

# application.yml
springdoc:
  swagger-ui:
    displayOperationId: false
    displayRequestDuration: true
    defaultModelsExpandDepth: 1
    defaultModelExpandDepth: 3
    docExpansion: none  # none, list, hoặc full
    filter: true
    operationsSorter: alpha  # alpha hoặc method
    tagsSorter: alpha
    tryItOutEnabled: true
    syntaxHighlight:
      activated: true
      theme: monokai

Nhóm APIs

@Configuration
public class OpenApiConfig {
 
    // Nhóm: Public APIs (v1)
    @Bean
    public GroupedOpenApi publicApiV1() {
        return GroupedOpenApi.builder()
            .group("public-v1")
            .displayName("Public API v1")
            .pathsToMatch("/api/v1/**")
            .pathsToExclude("/api/v1/admin/**")
            .build();
    }
 
    // Nhóm: Admin APIs
    @Bean
    public GroupedOpenApi adminApi() {
        return GroupedOpenApi.builder()
            .group("admin")
            .displayName("Admin API")
            .pathsToMatch("/api/v1/admin/**")
            .build();
    }
}

8. Bảo mật Documentation trong Production

Tắt trong Production

# application-prod.yml
springdoc:
  api-docs:
    enabled: false
  swagger-ui:
    enabled: false

Bảo vệ bằng Password

@Configuration
@Profile("!prod")
public class SwaggerSecurityConfig {
 
    @Bean
    public SecurityFilterChain swaggerSecurityChain(HttpSecurity http) throws Exception {
        http
            .securityMatcher("/swagger-ui/**", "/v3/api-docs/**")
            .authorizeHttpRequests(auth -> auth
                .anyRequest().authenticated())
            .httpBasic(Customizer.withDefaults());
 
        return http.build();
    }
}

Hiển thị có điều kiện

@Configuration
public class OpenApiConfig {
 
    @Value("${app.swagger.enabled:true}")
    private boolean swaggerEnabled;
 
    @Bean
    @ConditionalOnProperty(name = "app.swagger.enabled", havingValue = "true", matchIfMissing = true)
    public OpenAPI openAPI() {
        return new OpenAPI()
            .info(new Info().title("My API").version("1.0.0"));
    }
}

9. Tạo OpenAPI Specification

Export trong Build Time

Maven Plugin:

<plugin>
    <groupId>org.springdoc</groupId>
    <artifactId>springdoc-openapi-maven-plugin</artifactId>
    <version>1.4</version>
    <executions>
        <execution>
            <id>generate-openapi</id>
            <goals>
                <goal>generate</goal>
            </goals>
        </execution>
    </executions>
    <configuration>
        <apiDocsUrl>http://localhost:8080/v3/api-docs</apiDocsUrl>
        <outputFileName>openapi.json</outputFileName>
        <outputDir>${project.build.directory}</outputDir>
    </configuration>
</plugin>

Tạo Client SDK

Sử dụng OpenAPI spec đã export để tạo client SDKs:

# Cài đặt OpenAPI Generator
npm install @openapitools/openapi-generator-cli -g
 
# Tạo TypeScript client
openapi-generator-cli generate \
  -i http://localhost:8080/v3/api-docs \
  -g typescript-fetch \
  -o ./generated/typescript-client
 
# Tạo Java client
openapi-generator-cli generate \
  -i http://localhost:8080/v3/api-docs \
  -g java \
  -o ./generated/java-client

10. Các lỗi thường gặp và giải pháp

1. Annotations không được phát hiện

Vấn đề: Controller endpoints không hiển thị trong Swagger UI.

Giải pháp: Đảm bảo packages được quét:

springdoc:
  packages-to-scan: com.example.controller
  paths-to-match: /api/**

2. Security không được áp dụng

Vấn đề: Nút "Authorize" bị thiếu hoặc không hoạt động.

Giải pháp: Thêm security globally hoặc per-operation:

@Bean
public OpenAPI openAPI() {
    return new OpenAPI()
        .addSecurityItem(new SecurityRequirement().addList("bearerAuth"))
        .components(new Components()
            .addSecuritySchemes("bearerAuth", new SecurityScheme()...));
}

3. Validation Annotations bị bỏ qua

Vấn đề: @NotNull, @Size không phản ánh trong docs.

Giải pháp: Thêm validation dependency:

<dependency>
    <groupId>org.hibernate.validator</groupId>
    <artifactId>hibernate-validator</artifactId>
</dependency>

4. Khởi động chậm

Vấn đề: Application khởi động chậm do OpenAPI scanning.

Giải pháp: Giới hạn phạm vi quét:

springdoc:
  packages-to-scan: com.example.controller
  packages-to-exclude: com.example.internal

Tóm tắt và điểm chính

✅ SpringDoc OpenAPI là lựa chọn hiện đại cho tài liệu API Spring Boot
✅ Zero configuration để bắt đầu—tự động tạo từ controllers
✅ Rich annotations cho phép document parameters, responses, examples
✅ Authentication docs hỗ trợ JWT, API keys, OAuth2
✅ Tùy chỉnh Swagger UI cho branding và usability
✅ Bảo mật trong production bằng cách tắt hoặc password-protect
✅ Export OpenAPI spec để tạo client SDK
✅ Nhóm APIs để tổ chức tốt hơn

Tiếp theo

Bây giờ API của bạn đã được document tốt, khám phá các chủ đề sau:

Tiếp tục Spring Boot series:

Bài viết Spring Boot liên quan:

Nền tảng REST API:

OpenAPI trong các Framework khác:


Một phần của Spring Boot Learning Roadmap series

📬 Đăng ký nhận bản tin

Nhận những bài viết mới nhất qua email mỗi tuần. Không spam, hủy đăng ký bất cứ lúc nào.

Chúng tôi tôn trọng quyền riêng tư của bạn. Hủy đăng ký bất cứ lúc nào.

💬 Comments

Đăng nhập để bình luận

Chúng tôi sẽ không đăng bất cứ điều gì mà không có sự cho phép của bạn.