Menulis handler, struct request-response, dan routing HTTP secara manual berisiko menimbulkan desinkronisasi antara dokumentasi API dan implementasi kode. Pendekatan contract-first menggunakan OpenAPI 3.0 mengeliminasi masalah ini. Tool oapi-codegen mengotomatisasi pembuatan tipe data (types), interface handler, dan registrasi router langsung ke framework Go Fiber.

1. Spesifikasi OpenAPI 3.0

Definisikan kontrak API pada file api/spec.yaml. Contoh endpoint pembuatan pengguna:

openapi: "3.0.3"
info:
  title: User API
  version: "1.0.0"
paths:
  /users:
    post:
      summary: Buat user baru
      operationId: CreateUser
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateUserRequest'
      responses:
        '201':
          description: User berhasil dibuat
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/UserResponse'
components:
  schemas:
    CreateUserRequest:
      type: object
      required: [name, email]
      properties:
        name:
          type: string
        email:
          type: string
          format: email
    UserResponse:
      type: object
      required: [id, name, email]
      properties:
        id:
          type: string
        name:
          type: string
        email:
          type: string

2. Konfigurasi oapi-codegen untuk Fiber

Buat file oapi-codegen.yaml di root project. Konfigurasi ini memerintahkan generator membuat struct data dan adaptor router berbasis Go Fiber.

package: api
output: internal/api/api.gen.go
generate:
  - types
  - fiber
  - spec

3. Otomasi Generasi via go:generate

Lacak dependensi generator menggunakan tools.go agar versi binary seragam di seluruh mesin tim:

//go:build tools
package tools

import (
	_ "github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen"
)

Tambahkan directive generator pada file root paket atau file terpisah seperti generate.go:

package main

//go:generate go run github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen --config=oapi-codegen.yaml api/spec.yaml

Jalankan perintah berikut untuk menghasilkan kode:

go generate ./...

File internal/api/api.gen.go akan terbuat, berisi interface ServerInterface dan fungsi RegisterHandlers.

4. Implementasi Handler Berbasis Interface

Buat struct yang mengimplementasikan interface api.ServerInterface. Kompiler Go akan memvalidasi apakah semua rute OpenAPI sudah ditangani.

package main

import (
	"github.com/gofiber/fiber/v2"
	"example.com/project/internal/api"
)

type UserHandler struct{}

func (h *UserHandler) CreateUser(c *fiber.Ctx) error {
	var req api.CreateUserRequest
	if err := c.BodyParser(&req); err != nil {
		return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": err.Error()})
	}

	// ponytail: direct struct binding without domain abstraction; upgrade when persistence layer exists.
	res := api.UserResponse{
		Id:    "usr-1001",
		Name:  req.Name,
		Email: req.Email,
	}

	return c.Status(fiber.StatusCreated).JSON(res)
}

func main() {
	app := fiber.New()
	handler := &UserHandler{}

	// Pasang router otomatis hasil generate
	api.RegisterHandlers(app, handler)

	if err := app.Listen(":8080"); err != nil {
		panic(err)
	}
}

5. Verifikasi Kontrak API pada CI (GitHub Actions)

Cegah drift antara kontrak OpenAPI dan kode Go yang ter-commit. Workflow CI mengeksekusi go generate dan mengecek perubahan workspace menggunakan git diff --exit-code.

name: Verify API Codegen

on:
  pull_request:
    branches: [main]

jobs:
  check-codegen:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout Repository
        uses: actions/checkout@v4

      - name: Setup Go
        uses: actions/setup-go@v5
        with:
          go-version: '1.22'
          cache: true

      - name: Run Codegen
        run: go generate ./...

      - name: Check Git Status
        run: |
          git diff --exit-code || (echo "Contract drift detected: Run 'go generate ./...' locally and commit the generated files." && exit 1)
Catatan: Perintah git diff --exit-code mengembalikan return code 0 jika tidak ada perubahan, dan 1 jika ada file yang berubah atau belum di-commit. Langkah ini memastikan pull request ditolak jika developer mengubah api/spec.yaml tanpa menyertakan hasil regenerasi kode.