Panduan praktis membangun REST API yang rapi di Go memakai GoFiber, GORM, dan arsitektur berlapis — route, handler, usecase, dan repository.
Membangun web API dengan Go itu lugas, dan memakai framework seperti GoFiber membuatnya lebih cepat lagi. Artikel ini membahas API CRUD produk sederhana — mulai dari struktur proyek, tiap lapisan arsitekturnya, sampai bagaimana semuanya terhubung.
Kode sumber lengkapnya tersedia di github.com/uhkrowi/go-simple-crud.
Persiapan
Pastikan environment Anda sudah terpasang Go. Proyek ini memakai MySQL untuk penyimpanan — Anda bisa menggantinya dengan database lain, tapi driver GORM dan konfigurasi .env perlu disesuaikan.
Titik Masuk
Di /cmd/app/main.go, fungsi main() menginisialisasi aplikasi dan menjalankan server. Fungsi setup() terpisah merangkai database, validator, dan route:
func main() {
defer config.CloseDBConnection()
app := fiber.New(fiber.Config{
AppName: "CRUD",
})
app.Use(cors.New())
setup(app)
port := os.Getenv("PORT")
if port == "" {
port = "8080"
}
err := app.Listen(":" + port)
if err != nil {
panic(err)
}
}
func setup(app *fiber.App) {
config.InitDB()
db := config.DBConn
validate := validator.New()
apiV1 := app.Group("/api/v1")
route.ProductRoute(apiV1.Group("/product"), db, validate)
}
Modelnya
Model Product berada di /internal/app/model/product.go. Sebuah produk punya sejumlah record Variant. Tag gorm:"-" memberi tahu GORM untuk mengabaikan Variants saat operasi database langsung — field itu diisi manual di lapisan usecase.
type Product struct {
ID uuid.UUID `gorm:"primaryKey;column:id" json:"id"`
Name string `gorm:"column:name" json:"name"`
IsActive bool `gorm:"column:is_active" json:"is_active"`
Variants []Variant `gorm:"-" json:"variants"`
}
type Variant struct {
ID uuid.UUID `gorm:"primaryKey;column:id" json:"id"`
Name string `gorm:"column:name" json:"name"`
ProductID uuid.UUID `gorm:"column:product_id" json:"product_id"`
ProductName string `gorm:"column:product_name" json:"product_name"`
Price float64 `gorm:"column:price" json:"price"`
Stock int `gorm:"column:stock" json:"stock"`
}
Arsitektur Berlapis
Proyek ini mengikuti struktur berlapis yang rapi. Setiap lapisan punya satu tanggung jawab:
- Route — memetakan endpoint HTTP ke handler yang tepat. Misalnya,
GET /api/v1/productmemanggilProductHandler.GetAll. - Handler — menerima request HTTP, mem-parsing dan memvalidasi input, memanggil usecase, lalu mengembalikan response. Ia mengurus bentuk request/response, bukan logika bisnis.
- Usecase — lapisan logika inti. Ia mengorkestrasi pemanggilan ke repository, menerapkan aturan bisnis (misalnya, "produk hanya bisa dinonaktifkan kalau tidak punya varian aktif"), dan bisa memanggil usecase lain.
- Repository — semua interaksi database ada di sini. Usecase memanggil repository lewat interface, sehingga logika bisnisnya tetap bisa diuji secara terpisah.
Alur Create (Contoh)
Ketika klien mengirim POST /api/v1/product:
- Router mengarahkan request ke
ProductHandler.Create. - Handler mem-parsing body JSON menjadi struct
Productdan menjalankan validasi lewatgo-playground/validator. Kalau validasi gagal, ia langsung mengembalikan400beserta detailnya. - Usecase membuat UUID untuk produk baru, lalu memanggil
ProductRepository.Create. - Repository menjalankan
db.Create(&product)lewat GORM dan mengembalikan error bila ada. - Handler menerima hasilnya dan mengembalikan
201beserta produk yang dibuat, atau500bila ada yang gagal.
Kenapa Struktur Ini?
Memisahkan lapisan-lapisan ini mungkin terasa seperti boilerplate berlebih untuk aplikasi CRUD kecil, tapi manfaatnya cepat terasa seiring proyek membesar:
- Anda bisa menguji usecase tanpa database sungguhan dengan mengganti repository-nya memakai mock.
- Mengganti database (misalnya dari MySQL ke PostgreSQL) hanya menyentuh lapisan repository.
- Menambah lapisan transport kedua (misalnya gRPC berdampingan dengan HTTP) bisa memakai ulang kode usecase dan repository yang sama.
Untuk API kecil ini opsional. Untuk apa pun yang menghadap produksi, biaya penyiapannya sepadan sejak hari pertama.