Skip to content
Santekno.com | Level Up Your Engineering Skills
ID
📖 0%
06 Nov 2026 · 20 mnt baca ·Artikel 83 / 208
Go

OpenAPI Contract Golang E-Commerce: API Documentation dari Spec SDD

Buat OpenAPI contract lengkap untuk Santekno Shop dari spec SDD. Swagger/OpenAPI 3.0, code generation, mock server, dan contract testing untuk Go e-commerce.

IH
Ihsan Arif
Penulis di Santekno · Backend Engineer

OpenAPI Contract untuk Semua Service Santekno Shop

Membangun OpenAPI contract untuk e-commerce Golang berarti mendefinisikan API sebelum kode ditulis. OpenAPI spec adalah kontrak resmi antara backend dan frontend — frontend bisa mulai develop sambil kita implement backend. Di artikel ini, kita convert spec dari artikel pertama menjadi OpenAPI 3.0 yang lengkap, lalu memanfaatkannya untuk code generation, mock server, dan contract testing.

Contract-first bukan sekadar dokumentasi. Ia menjadi single source of truth yang mengikat backend, frontend, dan CI pada satu bentuk API yang sama — sehingga integrasi berhenti menjadi sumber bug yang tak berkesudahan.

03.01 Kenapa OpenAPI Contract Penting

Sebelum masuk teknis, penting memahami masalah yang dipecahkan contract. Perbandingan berikut menunjukkan kekacauan tanpa contract versus ketertiban dengan OpenAPI.

text
 1TANPA API CONTRACT:
 2  Backend developer: "Saya return snake_case"
 3  Frontend developer: "Loh, saya expect camelCase"
 4  → Wasted integration time
 5
 6  Backend: "Error message-nya 'order.not_found'"
 7  Frontend: "Saya kode untuk 'ORDER_NOT_FOUND'"
 8  → Bug di production
 9
10DENGAN OpenAPI CONTRACT:
11  Satu source of truth untuk API shape
12  Frontend bisa generate TypeScript types dari spec
13  Backend bisa validate response against spec
14  Mock server dari spec → frontend develop paralel
15  Contract testing → integrations yang reliable
16
17Keuntungan spesifik untuk Santekno Shop:
18  → spec SDD → OpenAPI spec → Go server code
19  → CI validates response format against OpenAPI
20  → Frontend team tidak perlu tunggu backend selesai

Poin terpentingnya: contract memindahkan penemuan mismatch (snake_case vs camelCase, kode error berbeda) dari fase integrasi ke fase desain — jauh sebelum kode bertemu di production.

03.02 Setup Tooling

Untuk bekerja dengan OpenAPI, kita butuh beberapa tool. Perintah berikut memasang generator kode, validator, mock server, dan generator TypeScript sekaligus.

bash
 1# Tools yang digunakan:
 2
 3# 1. oapi-codegen: generate Go server code dari OpenAPI
 4go install github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen@latest
 5
 6# 2. swagger-cli: validate OpenAPI spec
 7npm install -g @apidevtools/swagger-cli
 8
 9# 3. prism: mock server dari OpenAPI
10npm install -g @stoplight/prism-cli
11
12# 4. openapi-typescript: generate TypeScript types (untuk frontend)
13npm install -g openapi-typescript
14
15# Verify:
16oapi-codegen --version
17swagger-cli --version
18prism --version

Empat tool ini menutup seluruh siklus contract-first: menulis, memvalidasi, men-generate kode, dan menyediakan mock. Menjalankan blok verifikasi memastikan semuanya siap sebelum menulis spec.

03.03 OpenAPI 3.0 Spec: Auth Endpoints

Kita mulai dengan endpoint autentikasi. Spec YAML berikut mendefinisikan security scheme, schema umum, dan endpoint register/login lengkap dengan contoh response.

yaml
  1# api/openapi/santekno-shop.yaml
  2openapi: '3.0.0'
  3info:
  4  title: Santekno Shop API
  5  version: '1.0.0'
  6  description: |
  7    B2C E-Commerce Platform Indonesia.
  8    All monetary values are in IDR cents (1 IDR = 100 cents).
  9    All IDs are UUIDs.
 10
 11servers:
 12  - url: http://localhost:8080/api/v1
 13    description: Development
 14  - url: https://staging.santekno.com/api/v1
 15    description: Staging
 16
 17components:
 18  securitySchemes:
 19    BearerAuth:
 20      type: http
 21      scheme: bearer
 22      bearerFormat: JWT
 23
 24  schemas:
 25    ErrorResponse:
 26      type: object
 27      required: [error]
 28      properties:
 29        error:
 30          type: string
 31          example: "ORDER_NOT_FOUND"
 32          description: Machine-readable error code
 33        detail:
 34          type: string
 35          example: "Order with ID xxx was not found"
 36          description: Human-readable description
 37
 38    PaginationMeta:
 39      type: object
 40      properties:
 41        page: { type: integer, example: 1 }
 42        limit: { type: integer, example: 10 }
 43        total: { type: integer, example: 100 }
 44        total_pages: { type: integer, example: 10 }
 45
 46    UUID:
 47      type: string
 48      format: uuid
 49      example: "550e8400-e29b-41d4-a716-446655440000"
 50
 51    AmountIDR:
 52      type: integer
 53      format: int64
 54      description: "Amount in IDR cents. 1 IDR = 100 cents. Rp 50.000 = 5000000"
 55      example: 5000000
 56
 57    UserResponse:
 58      type: object
 59      properties:
 60        id: { $ref: '#/components/schemas/UUID' }
 61        email: { type: string, format: email }
 62        name: { type: string }
 63        created_at: { type: string, format: date-time }
 64
 65    RegisterRequest:
 66      type: object
 67      required: [email, password, name]
 68      properties:
 69        email:
 70          type: string
 71          format: email
 72          example: "user@example.com"
 73        password:
 74          type: string
 75          minLength: 8
 76          example: "Secret123"
 77          description: "Minimum 8 characters, must contain at least 1 digit"
 78        name:
 79          type: string
 80          minLength: 2
 81          maxLength: 100
 82          example: "John Doe"
 83
 84    LoginRequest:
 85      type: object
 86      required: [email, password]
 87      properties:
 88        email: { type: string, format: email }
 89        password: { type: string }
 90
 91    TokenResponse:
 92      type: object
 93      properties:
 94        access_token: { type: string }
 95        refresh_token: { type: string }
 96        expires_in: { type: integer, example: 3600 }
 97        token_type: { type: string, example: "Bearer" }
 98
 99paths:
100  /auth/register:
101    post:
102      tags: [Auth]
103      summary: Register new user
104      operationId: registerUser
105      requestBody:
106        required: true
107        content:
108          application/json:
109            schema: { $ref: '#/components/schemas/RegisterRequest' }
110      responses:
111        '201':
112          description: User registered successfully
113          content:
114            application/json:
115              schema: { $ref: '#/components/schemas/UserResponse' }
116        '400':
117          description: Validation error
118          content:
119            application/json:
120              schema: { $ref: '#/components/schemas/ErrorResponse' }
121        '409':
122          description: Email already registered
123          content:
124            application/json:
125              schema: { $ref: '#/components/schemas/ErrorResponse' }
126              example: { error: "EMAIL_ALREADY_REGISTERED" }
127
128  /auth/login:
129    post:
130      tags: [Auth]
131      summary: Login and get tokens
132      operationId: loginUser
133      requestBody:
134        required: true
135        content:
136          application/json:
137            schema: { $ref: '#/components/schemas/LoginRequest' }
138      responses:
139        '200':
140          description: Login successful
141          content:
142            application/json:
143              schema: { $ref: '#/components/schemas/TokenResponse' }
144        '401':
145          description: Invalid credentials
146          content:
147            application/json:
148              schema: { $ref: '#/components/schemas/ErrorResponse' }
149              example: { error: "INVALID_CREDENTIALS" }
150        '429':
151          description: Too many attempts
152          content:
153            application/json:
154              schema: { $ref: '#/components/schemas/ErrorResponse' }
155              example: { error: "TOO_MANY_ATTEMPTS", detail: "retry_after: 900" }

Setiap status code di spec ini persis mencerminkan AC dari spec SDD: 409 untuk email terdaftar, 429 untuk rate limit. Skema AmountIDR dan ErrorResponse yang reusable memastikan konsistensi di seluruh endpoint berikutnya.

03.04 OpenAPI Spec: Order Endpoints

Endpoint order lebih kompleks karena melibatkan array item dan berbagai error bisnis. Spec YAML berikut mendefinisikan schema order beserta endpoint place, list, dan cancel.

yaml
  1# (lanjutan api/openapi/santekno-shop.yaml)
  2
  3components:
  4  schemas:
  5    OrderItem:
  6      type: object
  7      properties:
  8        product_id: { $ref: '#/components/schemas/UUID' }
  9        product_name: { type: string }
 10        unit_price_idr: { $ref: '#/components/schemas/AmountIDR' }
 11        quantity: { type: integer, minimum: 1 }
 12        subtotal_idr: { $ref: '#/components/schemas/AmountIDR' }
 13
 14    OrderStatus:
 15      type: string
 16      enum: [PENDING, CONFIRMED, SHIPPED, DELIVERED, CANCELLED]
 17
 18    OrderResponse:
 19      type: object
 20      properties:
 21        id: { $ref: '#/components/schemas/UUID' }
 22        status: { $ref: '#/components/schemas/OrderStatus' }
 23        items:
 24          type: array
 25          items: { $ref: '#/components/schemas/OrderItem' }
 26        total_amount_idr: { $ref: '#/components/schemas/AmountIDR' }
 27        payment_method: { type: string, enum: [BANK_TRANSFER, QRIS, CREDIT_CARD] }
 28        payment_url: { type: string, format: uri, nullable: true }
 29        created_at: { type: string, format: date-time }
 30        updated_at: { type: string, format: date-time }
 31
 32    PlaceOrderRequest:
 33      type: object
 34      required: [items, shipping_address_id, payment_method]
 35      properties:
 36        items:
 37          type: array
 38          minItems: 1
 39          maxItems: 10
 40          items:
 41            type: object
 42            required: [product_id, quantity]
 43            properties:
 44              product_id: { $ref: '#/components/schemas/UUID' }
 45              quantity:
 46                type: integer
 47                minimum: 1
 48                maximum: 100
 49        shipping_address_id: { $ref: '#/components/schemas/UUID' }
 50        payment_method:
 51          type: string
 52          enum: [BANK_TRANSFER, QRIS, CREDIT_CARD]
 53
 54    OrderListResponse:
 55      type: object
 56      properties:
 57        data:
 58          type: array
 59          items: { $ref: '#/components/schemas/OrderResponse' }
 60        pagination: { $ref: '#/components/schemas/PaginationMeta' }
 61
 62paths:
 63  /orders:
 64    get:
 65      tags: [Orders]
 66      summary: Get user's orders
 67      security: [{ BearerAuth: [] }]
 68      operationId: getOrders
 69      parameters:
 70        - name: page
 71          in: query
 72          schema: { type: integer, default: 1, minimum: 1 }
 73        - name: limit
 74          in: query
 75          schema: { type: integer, default: 10, minimum: 1, maximum: 100 }
 76        - name: status
 77          in: query
 78          schema: { $ref: '#/components/schemas/OrderStatus' }
 79      responses:
 80        '200':
 81          description: List of orders
 82          content:
 83            application/json:
 84              schema: { $ref: '#/components/schemas/OrderListResponse' }
 85        '401':
 86          description: Unauthorized
 87          content:
 88            application/json:
 89              schema: { $ref: '#/components/schemas/ErrorResponse' }
 90              example: { error: "UNAUTHORIZED" }
 91
 92    post:
 93      tags: [Orders]
 94      summary: Place a new order
 95      security: [{ BearerAuth: [] }]
 96      operationId: placeOrder
 97      requestBody:
 98        required: true
 99        content:
100          application/json:
101            schema: { $ref: '#/components/schemas/PlaceOrderRequest' }
102      responses:
103        '201':
104          description: Order placed successfully
105          content:
106            application/json:
107              schema: { $ref: '#/components/schemas/OrderResponse' }
108        '400':
109          description: Invalid request
110          content:
111            application/json:
112              schema: { $ref: '#/components/schemas/ErrorResponse' }
113              example: { error: "EMPTY_ORDER" }
114        '404':
115          description: Product not found
116          content:
117            application/json:
118              schema: { $ref: '#/components/schemas/ErrorResponse' }
119              example: { error: "PRODUCT_NOT_FOUND", detail: "product_id: xxx" }
120        '422':
121          description: Insufficient stock
122          content:
123            application/json:
124              schema: { $ref: '#/components/schemas/ErrorResponse' }
125              example: { error: "INSUFFICIENT_STOCK", detail: "product_id: xxx" }
126
127  /orders/{orderId}:
128    delete:
129      tags: [Orders]
130      summary: Cancel an order
131      security: [{ BearerAuth: [] }]
132      operationId: cancelOrder
133      parameters:
134        - name: orderId
135          in: path
136          required: true
137          schema: { $ref: '#/components/schemas/UUID' }
138      responses:
139        '204':
140          description: Order cancelled successfully
141        '404':
142          description: Order not found
143          content:
144            application/json:
145              schema: { $ref: '#/components/schemas/ErrorResponse' }
146              example: { error: "ORDER_NOT_FOUND" }
147        '409':
148          description: Order cannot be cancelled
149          content:
150            application/json:
151              schema: { $ref: '#/components/schemas/ErrorResponse' }
152              examples:
153                not_cancellable:
154                  value: { error: "ORDER_NOT_CANCELLABLE" }
155                window_expired:
156                  value: { error: "CANCEL_WINDOW_EXPIRED" }

Batasan minItems: 1 dan maxItems: 10 pada items, serta maximum: 100 pada quantity, menegakkan business rule dari spec langsung di level contract. Validasi ini otomatis berlaku untuk mock server maupun kode yang di-generate.

03.05 OpenAPI Spec: Product Endpoints

Endpoint produk melengkapi katalog dengan filter dan search. Spec YAML berikut mendefinisikan schema produk serta endpoint list dan detail.

yaml
 1components:
 2  schemas:
 3    ProductResponse:
 4      type: object
 5      properties:
 6        id: { $ref: '#/components/schemas/UUID' }
 7        name: { type: string }
 8        description: { type: string, nullable: true }
 9        price_idr: { $ref: '#/components/schemas/AmountIDR' }
10        stock: { type: integer, minimum: 0 }
11        category:
12          type: object
13          properties:
14            id: { $ref: '#/components/schemas/UUID' }
15            name: { type: string }
16        images:
17          type: array
18          items: { type: string, format: uri }
19          maxItems: 10
20        is_active: { type: boolean }
21        created_at: { type: string, format: date-time }
22
23    ProductListResponse:
24      type: object
25      properties:
26        data:
27          type: array
28          items: { $ref: '#/components/schemas/ProductResponse' }
29        pagination: { $ref: '#/components/schemas/PaginationMeta' }
30
31paths:
32  /products:
33    get:
34      tags: [Products]
35      summary: List products with filters
36      operationId: listProducts
37      parameters:
38        - name: q
39          in: query
40          description: Search query (full-text search)
41          schema: { type: string }
42        - name: category
43          in: query
44          schema: { $ref: '#/components/schemas/UUID' }
45        - name: min_price
46          in: query
47          description: Minimum price in IDR cents
48          schema: { type: integer, minimum: 0 }
49        - name: max_price
50          in: query
51          description: Maximum price in IDR cents
52          schema: { type: integer, minimum: 0 }
53        - name: page
54          in: query
55          schema: { type: integer, default: 1 }
56        - name: limit
57          in: query
58          schema: { type: integer, default: 10, maximum: 50 }
59      responses:
60        '200':
61          description: List of products
62          content:
63            application/json:
64              schema: { $ref: '#/components/schemas/ProductListResponse' }
65
66  /products/{productId}:
67    get:
68      tags: [Products]
69      summary: Get product detail
70      operationId: getProduct
71      parameters:
72        - name: productId
73          in: path
74          required: true
75          schema: { $ref: '#/components/schemas/UUID' }
76      responses:
77        '200':
78          description: Product detail
79          content:
80            application/json:
81              schema: { $ref: '#/components/schemas/ProductResponse' }
82        '404':
83          description: Product not found
84          content:
85            application/json:
86              schema: { $ref: '#/components/schemas/ErrorResponse' }
87              example: { error: "NOT_FOUND" }

Endpoint produk sengaja tidak memakai security karena katalog bisa dibaca publik — konsisten dengan aturan auth di spec. Reuse AmountIDR dan PaginationMeta di sini menunjukkan nilai schema komponen yang terpusat.

03.06 Validasi OpenAPI Spec

Spec yang tidak valid akan menyesatkan semua tool di hilir. Perintah berikut memvalidasi spec dan memasangnya sebagai pre-commit hook agar drift tertangkap sedini mungkin.

bash
 1# Validate spec sebelum commit
 2swagger-cli validate api/openapi/santekno-shop.yaml
 3
 4# Expected output: api/openapi/santekno-shop.yaml is valid
 5# Jika ada error: fix sebelum commit
 6
 7# Tambahkan ke pre-commit:
 8# .pre-commit-config.yaml
 9repos:
10  - repo: local
11    hooks:
12      - id: validate-openapi
13        name: Validate OpenAPI Spec
14        language: system
15        entry: swagger-cli validate api/openapi/santekno-shop.yaml
16        files: 'api/openapi/.*\.yaml'

Memasang validasi sebagai pre-commit hook berarti spec yang rusak tidak akan pernah masuk ke repository. Ini gerbang paling awal dalam rantai pencegahan drift.

03.07 Code Generation dari OpenAPI

Kekuatan sesungguhnya OpenAPI muncul saat spec menghasilkan kode Go otomatis. Perintah dan config berikut menjalankan oapi-codegen untuk menghasilkan types dan server interface.

bash
 1# oapi-codegen config
 2# api/openapi/gen-config.yaml
 3
 4package: api
 5generate:
 6  - types   # Generate Go types dari schemas
 7  - server  # Generate Echo server interface
 8  - client  # Generate HTTP client (untuk testing)
 9  - spec    # Embed spec in binary
10
11output: internal/api/generated.go

Dengan konfigurasi generator di atas, perintah berikut menjalankan oapi-codegen untuk menghasilkan kode server dari spesifikasi.

bash
 1# Generate code:
 2oapi-codegen \
 3  --config api/openapi/gen-config.yaml \
 4  api/openapi/santekno-shop.yaml
 5
 6# Output: internal/api/generated.go
 7# Contains:
 8#   - RegisterRequest, LoginRequest, TokenResponse structs
 9#   - OrderResponse, PlaceOrderRequest structs
10#   - ProductResponse, ProductListResponse structs
11#   - ServerInterface (yang diimplementasi handler)

Config memerintahkan generator menghasilkan empat artefak sekaligus: types, server interface, client, dan embedded spec. Hasil generate-nya berupa kode Go seperti berikut.

go
 1// internal/api/generated.go (sample generated code)
 2// JANGAN edit manual — di-generate ulang dari spec
 3
 4// RegisterRequest defines model for RegisterRequest.
 5type RegisterRequest struct {
 6    Email    string `json:"email"`
 7    Name     string `json:"name"`
 8    Password string `json:"password"`
 9}
10
11// OrderResponse defines model for OrderResponse.
12type OrderResponse struct {
13    CreatedAt      time.Time   `json:"created_at"`
14    Id             uuid.UUID   `json:"id"`
15    Items          []OrderItem `json:"items"`
16    PaymentMethod  string      `json:"payment_method"`
17    PaymentUrl     *string     `json:"payment_url"`
18    Status         OrderStatus `json:"status"`
19    TotalAmountIdr int64       `json:"total_amount_idr"`
20    UpdatedAt      time.Time   `json:"updated_at"`
21}

Perhatikan TotalAmountIdr int64 — konvensi int64 cents dari spec otomatis muncul di kode Go. Karena ServerInterface juga di-generate, handler yang tidak sesuai contract akan gagal compile: contract enforcement di tingkat compiler.

03.08 Mock Server dengan Prism

Frontend tak perlu menunggu backend selesai. Perintah berikut menyalakan mock server dari spec, lengkap dengan cara memicu skenario sukses maupun error.

bash
 1# Start mock server dari OpenAPI spec
 2prism mock api/openapi/santekno-shop.yaml --port 4010
 3
 4# Mock server sekarang available di localhost:4010
 5# Frontend bisa develop sambil backend di-implement
 6
 7# Test mock server:
 8curl -X POST http://localhost:4010/api/v1/auth/register \
 9  -H "Content-Type: application/json" \
10  -d '{"email": "test@example.com", "password": "Secret123", "name": "Test User"}'
11
12# Response: 201 Created dengan generated fake data
13# {
14#   "id": "550e8400-e29b-41d4-a716-446655440000",
15#   "email": "test@example.com",
16#   "name": "Test User",
17#   "created_at": "2026-07-20T10:00:00Z"
18# }
19
20# Test error scenario dengan prefer header:
21curl -X POST http://localhost:4010/api/v1/auth/register \
22  -H "Content-Type: application/json" \
23  -H "Prefer: code=409" \
24  -d '{"email": "existing@example.com"}'
25# Response: 409 Conflict

Dengan header Prefer: code=409, frontend bahkan bisa menguji jalur error tanpa backend sama sekali. Mock server memungkinkan kedua tim bekerja paralel penuh — penghemat waktu terbesar dari contract-first.

03.09 Contract Testing

Setelah backend diimplementasi, kita perlu memastikan responsenya benar-benar sesuai contract. Kode Go berikut memuat OpenAPI spec dan memverifikasi response endpoint register terhadapnya.

go
 1// internal/api/contract_test.go
 2// Verify response format sesuai OpenAPI spec
 3
 4package api_test
 5
 6import (
 7    "encoding/json"
 8    "net/http/httptest"
 9    "strings"
10    "testing"
11
12    "github.com/getkin/kin-openapi/openapi3"
13    "github.com/stretchr/testify/assert"
14)
15
16func TestContractCompliance(t *testing.T) {
17    // Load OpenAPI spec
18    loader := openapi3.NewLoader()
19    doc, err := loader.LoadFromFile("../../api/openapi/santekno-shop.yaml")
20    if err != nil {
21        t.Fatal(err)
22    }
23
24    if err := doc.Validate(loader.Context); err != nil {
25        t.Fatal("Invalid OpenAPI spec:", err)
26    }
27
28    // Test: POST /auth/register
29    t.Run("register returns 201 matching schema", func(t *testing.T) {
30        e := setupTestServer()
31        req := httptest.NewRequest("POST", "/api/v1/auth/register",
32            strings.NewReader(`{"email":"test@example.com","password":"Secret123","name":"Test"}`))
33        req.Header.Set("Content-Type", "application/json")
34        rec := httptest.NewRecorder()
35        e.ServeHTTP(rec, req)
36
37        // Verify status
38        assert.Equal(t, 201, rec.Code)
39
40        // Validate response against OpenAPI schema
41        var body map[string]interface{}
42        json.NewDecoder(rec.Body).Decode(&body)
43
44        // body must have: id, email, name, created_at
45        assert.NotEmpty(t, body["id"])
46        assert.Equal(t, "test@example.com", body["email"])
47        assert.NotEmpty(t, body["created_at"])
48
49        // token MUST NOT be in response (per spec + security)
50        assert.Nil(t, body["access_token"])
51    })
52}

Test ini menegakkan requirement halus dari spec: register tidak boleh mengembalikan token (assert.Nil(t, body["access_token"])). Contract testing menangkap pelanggaran semacam ini yang mudah lolos dari review manual.

03.10 OpenAPI di CI Pipeline

Contract harus ditegakkan otomatis di setiap PR. Konfigurasi CI berikut memvalidasi spec dan memastikan kode yang di-generate selalu sinkron dengan spec.

yaml
 1# .github/workflows/ci.yml (tambahkan ke pipeline)
 2
 3- name: Validate OpenAPI spec
 4  run: swagger-cli validate api/openapi/santekno-shop.yaml
 5
 6- name: Verify generated code is up-to-date
 7  run: |
 8    oapi-codegen --config api/openapi/gen-config.yaml api/openapi/santekno-shop.yaml
 9    # Check jika ada diff (berarti generated code tidak di-update)
10    if ! git diff --quiet internal/api/generated.go; then
11      echo "::error::Generated API code is out of sync with OpenAPI spec"
12      echo "Run: oapi-codegen --config api/openapi/gen-config.yaml ..."
13      exit 1
14    fi

Langkah kedua mencegah drift paling umum: spec diperbarui tapi kode generate lupa di-regenerate. Jika ada diff, CI langsung memblokir merge — spec dan kode dijamin selalu sinkron.

03.11 TypeScript Types Generation

Frontend juga butuh type-safety dari contract yang sama. Perintah berikut men-generate TypeScript types langsung dari OpenAPI spec.

bash
 1# Generate TypeScript types untuk frontend team
 2openapi-typescript api/openapi/santekno-shop.yaml \
 3  --output frontend/src/types/api.ts
 4
 5# Generated types:
 6# export interface RegisterRequest {
 7#   email: string;
 8#   name: string;
 9#   password: string;
10# }
11#
12# export interface OrderResponse {
13#   id: string;  // UUID
14#   status: "PENDING" | "CONFIRMED" | "SHIPPED" | "DELIVERED" | "CANCELLED";
15#   total_amount_idr: number;  // int64 cents
16# }
17
18# Frontend package.json script:
19# "gen:types": "openapi-typescript ../api/openapi/santekno-shop.yaml -o src/types/api.ts"

Dengan types di-generate dari spec yang sama dengan backend, frontend dan backend mustahil melenceng: satu perubahan spec langsung memperbarui kedua sisi. Type-safety end-to-end tanpa duplikasi definisi.

03.12 API Versioning Strategy

API akan berevolusi, dan kita butuh aturan perubahan yang jelas. Panduan berikut menetapkan apa yang termasuk breaking change dan bagaimana strategi versioning-nya.

text
 1Santekno Shop API versioning:
 2
 3URL versioning: /api/v1/
 4  Pilihan paling explicit dan easiest untuk proxy/gateway
 5
 6Breaking change policy:
 7  Non-breaking changes: dapat ke v1 langsung
 8    + Add optional field ke response
 9    + Add optional query param
10    + Add new endpoint
11
12  Breaking changes: require v2
13    - Remove field dari response
14    - Change field type
15    - Change error format
16    - Rename endpoint
17
18V2 strategy (ketika diperlukan):
19  /api/v2/ untuk endpoint baru
20  /api/v1/ tetap di-maintain selama 6 bulan
21  Deprecation header di v1 response
22
23Saat ini: v1 only. V2 tidak perlu untuk saat ini.

Membedakan breaking dari non-breaking secara eksplisit mencegah kejutan bagi konsumen API. Selama perubahan hanya menambah field opsional atau endpoint baru, klien lama tetap aman tanpa perlu versi baru.

03.13 Webhook Spec

Webhook payment perlu didokumentasikan dengan requirement keamanannya. Spec YAML berikut mendefinisikan endpoint webhook lengkap dengan header signature dan perilaku idempoten.

yaml
 1# Tambahkan ke santekno-shop.yaml
 2
 3paths:
 4  /webhooks/payment:
 5    post:
 6      tags: [Webhooks]
 7      summary: Handle payment webhook
 8      description: |
 9        Receives payment status updates from payment gateway.
10        Request must include HMAC-SHA256 signature in X-Signature header.
11        Idempotent: duplicate requests (same external_payment_id) return 200.
12      operationId: handlePaymentWebhook
13      parameters:
14        - name: X-Signature
15          in: header
16          required: true
17          schema: { type: string }
18          description: HMAC-SHA256 signature of request body
19      requestBody:
20        required: true
21        content:
22          application/json:
23            schema:
24              type: object
25              required: [order_id, payment_id, status, amount_idr, signature]
26              properties:
27                order_id: { $ref: '#/components/schemas/UUID' }
28                payment_id: { type: string, example: "pay_xxx123" }
29                status:
30                  type: string
31                  enum: [PAID, FAILED]
32                amount_idr: { $ref: '#/components/schemas/AmountIDR' }
33      responses:
34        '200':
35          description: Webhook processed (including idempotent duplicates)
36        '401':
37          description: Invalid signature
38          content:
39            application/json:
40              schema: { $ref: '#/components/schemas/ErrorResponse' }
41              example: { error: "INVALID_SIGNATURE" }

Dengan mendokumentasikan X-Signature sebagai header wajib dan menyatakan perilaku idempoten di deskripsi, requirement keamanan menjadi terlihat di contract — bukan tersembunyi di kode. Tim integrator tahu persis apa yang harus mereka kirim.

03.14 API Documentation Website

Contract yang sama bisa menjadi dokumentasi interaktif. Perintah berikut menyajikan spec sebagai website dokumentasi lewat Redoc atau Swagger UI.

bash
 1# Setup Swagger UI untuk API documentation
 2# Bisa menggunakan redoc atau swagger-ui
 3
 4# Option 1: Redoc (lebih clean)
 5docker run -p 8081:80 \
 6  -v $(pwd)/api/openapi:/usr/share/nginx/html/openapi \
 7  -e SPEC_URL=/openapi/santekno-shop.yaml \
 8  redocly/redoc
 9
10# Option 2: Swagger UI
11docker run -p 8081:8080 \
12  -e SWAGGER_JSON=/openapi/santekno-shop.yaml \
13  -v $(pwd)/api/openapi:/openapi \
14  swaggerapi/swagger-ui

Karena dokumentasi di-render dari spec yang sama, ia tidak akan pernah basi: setiap update contract otomatis memperbarui website. Tidak ada lagi dokumentasi API yang berbeda dari perilaku sebenarnya.

03.15 Spec Drift Prevention

Untuk menjaga spec, kode, dan test selalu selaras, kita satukan pengecekan dalam satu script. Script berikut memvalidasi spec, memastikan kode sinkron, dan menjalankan contract test sekaligus.

bash
 1# Script untuk cek spec vs implementasi alignment
 2# scripts/check-spec-drift.sh
 3
 4# 1. Validate spec valid
 5swagger-cli validate api/openapi/santekno-shop.yaml || exit 1
 6
 7# 2. Check generated code up-to-date
 8oapi-codegen --config api/openapi/gen-config.yaml api/openapi/santekno-shop.yaml
 9git diff --quiet internal/api/generated.go || {
10    echo "Generated code not in sync with spec"
11    exit 1
12}
13
14# 3. Run contract tests
15go test ./internal/api/... -run TestContractCompliance
16
17echo "✅ Spec alignment check passed"

Satu script yang menggabungkan tiga pengecekan ini menjadi definisi tunggal “spec selaras”. Dijalankan di pre-commit dan CI, ia menutup celah drift dari tiga arah sekaligus.

03.16 OpenAPI Extension untuk Santekno Shop

OpenAPI mengizinkan metadata kustom lewat extension x-. Spec berikut menautkan setiap endpoint kembali ke spec SDD dan AC yang mendasarinya.

yaml
 1# Custom extensions (x-) untuk additional metadata
 2paths:
 3  /orders:
 4    post:
 5      x-spec-file: ".specify/features/place-order/spec.md"  # link ke SDD spec
 6      x-acceptance-criteria:
 7        - AC1
 8        - AC2
 9        - AC3
10        - AC4
11        - AC5
12      x-implementation-status: "complete"  # atau "planned", "in-progress"
13      x-since-version: "1.0.0"

Extension ini menciptakan rantai telusur: kebutuhan bisnis → spec SDD → OpenAPI contract → implementasi. Saat audit, kita bisa langsung tahu AC mana yang menjadi dasar sebuah endpoint.

03.17 API Rate Limits Documentation

Rate limit perlu terlihat oleh konsumen API. Spec berikut mendokumentasikan header rate limit pada response login, termasuk Retry-After saat limit terlampaui.

yaml
 1# Tambahkan rate limit info ke OpenAPI spec via headers
 2paths:
 3  /auth/login:
 4    post:
 5      responses:
 6        '200':
 7          headers:
 8            X-RateLimit-Limit:
 9              schema: { type: integer }
10              description: Max requests per window (5)
11            X-RateLimit-Remaining:
12              schema: { type: integer }
13              description: Remaining requests in window
14            X-RateLimit-Reset:
15              schema: { type: integer }
16              description: Unix timestamp when window resets
17        '429':
18          headers:
19            Retry-After:
20              schema: { type: integer }
21              description: Seconds to wait before retry (900)

Mendokumentasikan header seperti X-RateLimit-Remaining membantu klien berperilaku baik: mereka bisa memperlambat request sebelum kena 429. Rate limit menjadi kontrak, bukan kejutan.

03.18 Pagination Standard

Pagination harus seragam di semua endpoint list. Spec berikut mendefinisikan parameter pagination yang reusable agar tidak ada variasi antar endpoint.

yaml
 1# Consistent pagination across all list endpoints
 2
 3components:
 4  parameters:
 5    PageParam:
 6      name: page
 7      in: query
 8      schema: { type: integer, default: 1, minimum: 1 }
 9    LimitParam:
10      name: limit
11      in: query
12      schema: { type: integer, default: 10, minimum: 1, maximum: 100 }
13
14# Usage:
15paths:
16  /orders:
17    get:
18      parameters:
19        - $ref: '#/components/parameters/PageParam'
20        - $ref: '#/components/parameters/LimitParam'
21        - name: status
22          in: query
23          schema: { $ref: '#/components/schemas/OrderStatus' }

Dengan mendefinisikan PageParam dan LimitParam sekali lalu me-reuse via $ref, semua endpoint list otomatis punya default dan batas yang sama. Konsistensi pagination terjaga tanpa duplikasi.

03.19 Changelog Spec

Riwayat perubahan API sebaiknya hidup di dalam spec itu sendiri. Extension berikut menyimpan changelog terstruktur di dalam blok info.

yaml
 1# Maintain changelog di OpenAPI spec
 2
 3info:
 4  title: Santekno Shop API
 5  version: '1.0.0'
 6  x-changelog:
 7    - version: "1.0.0"
 8      date: "2026-07-01"
 9      changes:
10        - "Initial release"
11        - "Auth: register, login, refresh token"
12        - "Orders: place, cancel, list"
13        - "Products: list, search, detail"
14        - "Webhooks: payment callback"

Menyimpan changelog di dalam spec berarti riwayat versi ikut ter-version bersama contract di git. Tidak ada lagi changelog terpisah yang mudah lupa diperbarui.

03.20 Ringkasan: Contract Sebelum Code

OpenAPI spec yang kita buat adalah jembatan antara spec SDD (artikel pertama) dan implementasi Go (artikel-artikel berikutnya).

Yang sudah tersedia sekarang:

  • 4 tag groups: Auth, Orders, Products, Webhooks
  • 10+ endpoint dengan request/response schema lengkap
  • Mock server siap untuk frontend development
  • Code generation untuk Go types dan interfaces
  • TypeScript types untuk frontend
  • Contract testing skeleton

Pipeline CI checks:

  • Spec validation (swagger-cli)
  • Generated code sync check
  • Contract compliance tests

Dengan OpenAPI spec yang solid, tim bisa bekerja paralel: frontend consume mock server, backend implement sesuai contract, dan CI menjaga keduanya tetap selaras. Setiap perubahan perilaku API mengalir melalui rantai ini sehingga drift terdeteksi di level spec sebelum sampai production.

Berikutnya, kita rakit fondasi operasional: CLAUDE.md yang komprehensif, konstitusi proyek, dan CI pipeline awal yang menegakkan semua aturan ini secara otomatis.

Artikel Terkait

💬 Komentar