Tampilan
Kenapa penting
API yang tidak terdokumentasi memaksa setiap konsumen — tim frontend, mobile, partner, bahkan kamu sendiri tiga bulan lagi — membaca kode controller atau menebak lewat trial and error. Di tempat kerja, dokumen OpenAPI adalah kontrak resmi: dipakai untuk review desain, generate client, menyiapkan mock, test kontrak, dan konfigurasi API gateway. Kalau dokumentasinya ditulis manual di wiki, ia akan basi dalam hitungan minggu. Bab ini menunjukkan cara membuat dokumentasi yang dihasilkan dari kode (atau sebaliknya, kode dari kontrak), lengkap dengan autentikasi JWT dari bab-bab sebelumnya, dan cara memastikan dokumen itu tidak pernah berbohong.
Konsep inti
OpenAPI, Swagger, dan springdoc
| Istilah | Artinya |
|---|---|
| OpenAPI Specification | Standar format (JSON/YAML) untuk mendeskripsikan HTTP API: path, parameter, body, response, security. |
| OpenAPI 3.1 | Versi yang skemanya selaras penuh dengan JSON Schema 2020-12 (mis. type: [string, "null"]). |
| Swagger UI | Halaman web yang merender dokumen OpenAPI dan menyediakan tombol Try it out. |
| springdoc-openapi | Library yang memindai controller Spring saat runtime dan menghasilkan dokumen OpenAPI secara otomatis. |
Dokumen OpenAPI minimal berbentuk seperti ini:
yaml
openapi: 3.1.0
info:
title: Order API
version: 1.0.0
paths:
/api/orders/{id}:
get:
summary: Ambil satu pesanan
parameters:
- name: id
in: path
required: true
schema: { type: integer, format: int64 }
responses:
"200":
description: Pesanan ditemukan
"404":
description: Pesanan tidak adaMemasang springdoc di Spring Boot 4
Spring Boot 4 memakai Spring Framework 7, sehingga kamu butuh springdoc-openapi 3.x (seri 2.x untuk Boot 3). Untuk aplikasi Spring MVC:
xml
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
<version>3.1.1</version>
</dependency>Tanpa konfigurasi tambahan kamu langsung mendapat:
| URL | Isi |
|---|---|
/v3/api-docs | Dokumen OpenAPI dalam JSON |
/v3/api-docs.yaml | Dokumen yang sama dalam YAML |
/swagger-ui.html | Redirect ke Swagger UI (/swagger-ui/index.html) |
springdoc membaca @RestController, @GetMapping, @PathVariable, @RequestBody, tipe record, dan anotasi Bean Validation. Seri ini menghasilkan OpenAPI 3.1 secara default; perilakunya bisa diatur lewat properti:
yaml
springdoc:
api-docs:
path: /v3/api-docs
swagger-ui:
path: /swagger-ui.html
operations-sorter: method
packages-to-scan: com.example.orders.apiMemperkaya kontrak dengan anotasi
Hasil otomatis sudah lumayan, tetapi deskripsi, contoh, dan kode error belum ada. Anotasi dari paket io.swagger.v3.oas.annotations menambahkannya:
java
// Potongan
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
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.tags.Tag;
@Tag(name = "Orders", description = "Membuat dan membaca pesanan")
@RestController
@RequestMapping("/api/orders")
class OrderController {
@Operation(summary = "Ambil satu pesanan",
description = "Customer hanya bisa melihat pesanannya sendiri.")
@ApiResponse(responseCode = "200", description = "Pesanan ditemukan")
@ApiResponse(responseCode = "404", description = "Pesanan tidak ada atau bukan milikmu",
content = @Content(mediaType = "application/problem+json",
schema = @Schema(implementation = ProblemDetail.class)))
@GetMapping("/{id}")
OrderResponse get(@Parameter(description = "ID pesanan", example = "1001")
@PathVariable long id) {
return orderService.get(id);
}
}@ApiResponse bisa diulang (repeatable). Untuk DTO, @Schema di komponen record memberi deskripsi dan contoh:
java
// Potongan
@Schema(description = "Satu baris barang dalam pesanan")
record OrderLine(
@Schema(description = "Kode SKU produk", example = "KOPI-250G")
@NotBlank String sku,
@Schema(description = "Jumlah barang", example = "2", minimum = "1")
@Min(1) int quantity) {
}@Hidden menyembunyikan endpoint atau parameter dari dokumen — berguna untuk endpoint internal, tetapi ingat bahwa disembunyikan bukan berarti aman; otorisasi tetap wajib.
Mendokumentasikan bearer JWT
API kita dilindungi JWT (bab resource server). Supaya Swagger UI bisa mengirim header Authorization: Bearer <token>, definisikan security scheme sekali, lalu pakai di operasi:
java
// Potongan
import io.swagger.v3.oas.annotations.OpenAPIDefinition;
import io.swagger.v3.oas.annotations.enums.SecuritySchemeType;
import io.swagger.v3.oas.annotations.info.Info;
import io.swagger.v3.oas.annotations.security.SecurityRequirement;
import io.swagger.v3.oas.annotations.security.SecurityScheme;
@Configuration
@OpenAPIDefinition(info = @Info(title = "Order API", version = "1.0.0"),
security = @SecurityRequirement(name = "bearerAuth"))
@SecurityScheme(name = "bearerAuth", type = SecuritySchemeType.HTTP,
scheme = "bearer", bearerFormat = "JWT")
class OpenApiConfig {
}security di @OpenAPIDefinition memasang requirement secara global. Kalau sebagian endpoint publik (mis. katalog), pasang @SecurityRequirement(name = "bearerAuth") per controller/operasi saja. Di Swagger UI muncul tombol Authorize tempat kamu menempelkan token — token itu hanya tersimpan di browser.
Endpoint dokumentasi juga harus diizinkan di filter chain, dan sebaiknya dimatikan di produksi kecuali API-nya memang publik:
java
// Potongan — di SecurityFilterChain
http.authorizeHttpRequests(auth -> auth
.requestMatchers("/v3/api-docs/**", "/swagger-ui/**", "/swagger-ui.html").permitAll()
.anyRequest().authenticated());yaml
# application-prod.yaml
springdoc:
api-docs:
enabled: false
swagger-ui:
enabled: falseContract-first vs code-first
| Aspek | Code-first (springdoc) | Contract-first (openapi.yaml dulu) |
|---|---|---|
| Sumber kebenaran | Kode controller | File OpenAPI di repo |
| Kecepatan awal | Cepat, tinggal coding | Lebih lambat, perlu menulis & me-review YAML |
| Kerja paralel | Konsumen menunggu kode jadi | Konsumen bisa mulai dari mock/client hasil generate |
| Risiko | Desain API "bocor" dari detail implementasi | Butuh disiplin generate ulang saat kontrak berubah |
| Cocok untuk | API internal, tim kecil | API publik/partner, banyak tim konsumen |
Keduanya sah. Yang penting: pilih satu sumber kebenaran dan jangan memelihara dua dokumen yang ditulis tangan.
Generate client (dan server) dari kontrak
openapi-generator membaca dokumen OpenAPI dan menghasilkan kode. Contoh plugin Maven untuk membuat interface Spring dari openapi.yaml (contract-first):
xml
<plugin>
<groupId>org.openapitools</groupId>
<artifactId>openapi-generator-maven-plugin</artifactId>
<version>7.26.0</version>
<executions>
<execution>
<goals><goal>generate</goal></goals>
<configuration>
<inputSpec>${project.basedir}/src/main/resources/openapi.yaml</inputSpec>
<generatorName>spring</generatorName>
<apiPackage>com.example.orders.api</apiPackage>
<modelPackage>com.example.orders.api.model</modelPackage>
<configOptions>
<interfaceOnly>true</interfaceOnly>
<useSpringBoot4>true</useSpringBoot4>
</configOptions>
</configuration>
</execution>
</executions>
</plugin>Controller lalu implements OrdersApi — kalau kontrak berubah dan kode tidak, kompilasi gagal. Untuk konsumen, ganti generatorName ke java (client Java), typescript-fetch, dan sebagainya. Opsi tiap generator berubah antar versi; cek dokumentasinya sebelum memakai.
Menjaga dokumentasi tetap sinkron
Dokumen yang dihasilkan otomatis tetap bisa "berbohong" bila anotasi @ApiResponse tidak sesuai perilaku sebenarnya. Tiga kebiasaan yang membantu:
- Snapshot kontrak di repo. Simpan
openapi.jsonhasil generate, lalu test membandingkannya dengan/v3/api-docs. Perubahan kontrak jadi terlihat di code review. - Deteksi breaking change di CI dengan tool pembanding OpenAPI (mis.
openapi-diff): menghapus field response atau menambah field wajib di request harus disengaja. - Test perilaku, bukan hanya dokumen. Kode status yang didokumentasikan (401, 403, 404) harus punya test MockMvc.
java
// Potongan — test kontrak sederhana
@SpringBootTest
@AutoConfigureMockMvc
class OpenApiContractTest {
@Autowired MockMvc mvc;
@Test
void documentsOrderEndpointWithBearerAuth() throws Exception {
mvc.perform(get("/v3/api-docs"))
.andExpect(status().isOk())
.andExpect(jsonPath("$.paths['/api/orders/{id}'].get").exists())
.andExpect(jsonPath("$.components.securitySchemes.bearerAuth.scheme").value("bearer"));
}
}Contoh di dunia backend
Tim checkout membuka API pesanan untuk aplikasi mobile. Mereka memilih code-first, tetapi menambahkan deskripsi untuk alur pembayaran dan error yang mungkin muncul:
java
// Potongan
@Tag(name = "Orders")
@SecurityRequirement(name = "bearerAuth")
@RestController
@RequestMapping("/api/orders")
class OrderController {
@Operation(summary = "Buat pesanan baru",
description = "Harga dihitung ulang di server; harga dari client diabaikan.")
@ApiResponse(responseCode = "201", description = "Pesanan dibuat, header Location berisi URL pesanan")
@ApiResponse(responseCode = "400", description = "Validasi gagal",
content = @Content(mediaType = "application/problem+json",
schema = @Schema(implementation = ProblemDetail.class)))
@ApiResponse(responseCode = "401", description = "Token tidak ada atau tidak valid", content = @Content)
@ApiResponse(responseCode = "409", description = "Stok tidak cukup",
content = @Content(mediaType = "application/problem+json",
schema = @Schema(implementation = ProblemDetail.class)))
@PostMapping
ResponseEntity<OrderResponse> create(@Valid @RequestBody CreateOrderRequest request,
@AuthenticationPrincipal Jwt jwt) {
OrderResponse created = orderService.create(jwt.getSubject(), request);
return ResponseEntity.created(URI.create("/api/orders/" + created.id())).body(created);
}
}
@Schema(description = "Permintaan membuat pesanan")
record CreateOrderRequest(
@Schema(description = "Daftar barang, minimal satu")
@NotEmpty List<@Valid OrderLine> lines,
@Schema(description = "Kode voucher opsional", example = "HEMAT10", nullable = true)
String voucherCode) {
}Perhatikan bahwa @AuthenticationPrincipal Jwt tidak muncul sebagai parameter di dokumen — springdoc mengenali parameter yang diisi framework. content = @Content kosong pada 401 mencegah springdoc menebak body response yang tidak ada.
Pertanyaan interview
1. Apa beda contract-first dan code-first, dan kapan kamu memilih masing-masing?
Code-first menghasilkan kontrak dari kode (mis. springdoc), cepat dan cocok untuk API internal dengan sedikit konsumen. Contract-first menulis file OpenAPI lebih dulu, me-review-nya, lalu menghasilkan interface server dan client. Saya memilih contract-first untuk API publik atau yang dipakai banyak tim, karena kontrak disepakati sebelum implementasi dan kerja bisa paralel.
2. Bagaimana kamu mendokumentasikan autentikasi JWT di OpenAPI?
Definisikan security scheme bertipe http dengan scheme: bearer dan bearerFormat: JWT di components.securitySchemes, lalu pasang security requirement secara global atau per operasi. Di springdoc caranya dengan @SecurityScheme dan @SecurityRequirement dengan nama yang sama. Endpoint publik bisa diberi requirement kosong agar tidak menampilkan gembok.
3. Bagaimana memastikan dokumentasi API tidak basi?
Jadikan dokumen hasil generate dari satu sumber kebenaran, simpan snapshot kontrak di repo dan bandingkan dalam test, jalankan pendeteksi breaking change di CI, dan pastikan setiap kode status yang didokumentasikan punya test perilaku. Dokumentasi manual terpisah dari kode hampir selalu tertinggal.
Jebakan umum
- Memakai springdoc 2.x di Spring Boot 4 — seri yang kompatibel dengan Boot 4 adalah 3.x.
- Swagger UI terbuka di produksi tanpa sengaja, membocorkan peta lengkap API (termasuk endpoint admin) ke penyerang.
- Menganggap
@Hiddensebagai pengaman; endpoint tetap bisa dipanggil bila otorisasinya lemah. - Mendefinisikan
@SecuritySchemetetapi lupa@SecurityRequirement, sehingga tombol Try it out selalu401. - Menuliskan
@ApiResponseyang tidak sesuai perilaku nyata (mis. mendokumentasikan404padahal API mengembalikan403). - Memelihara
openapi.yamltulisan tangan sekaligus anotasi springdoc — dua sumber kebenaran pasti berbeda.
Ringkasan
- OpenAPI adalah format kontrak HTTP API; Swagger UI hanyalah penampilnya.
- Di Spring Boot 4 pakai
springdoc-openapi-starter-webmvc-uiseri 3.x; dokumen tersedia di/v3/api-docs. @Operation,@ApiResponse,@Schema, dan@Tagmenambah deskripsi, contoh, dan kode error yang tidak bisa ditebak otomatis.- Bearer JWT didokumentasikan dengan
@SecurityScheme+@SecurityRequirement; batasi akses dokumentasi di produksi. - Pilih satu sumber kebenaran (code-first atau contract-first) dan jaga sinkronisasi dengan snapshot, deteksi breaking change, dan test perilaku.