Programming 15 Sep 2026 59 views 0 komentar

Go Fiber untuk Pemula - Membangun REST API Cepat dengan GORM dan PostgreSQL

Go Fiber untuk Pemula - Membangun REST API Cepat dengan GORM dan PostgreSQL

Pernahkah kamu mencoba membuat REST API dengan Go dan langsung kena "cultural shock"? Saya dulu begitu. Datang dari dunia Express.js dan CodeIgniter, saya terbiasa dengan routing yang gampang, middleware yang intuitif, dan dokumentasi yang ramah. Lalu saya coba net/http bawaan Go. Hasilnya? Tiga jam cuma untuk bikin satu endpoint CRUD yang di Express cuma butuh lima menit.

Nah, di sinilah Go Fiber masuk. Fiber adalah framework web untuk Go yang terinspirasi dari Express.js. Jadi kalau kamu pernah pakai Express, syntax-nya langsung terasa familiar. Tapi bedanya, Fiber jauh lebih cepat karena pakai engine Fasthttp bukan net/http standar. Dalam artikel ini, saya mau sharing pengalaman setup Go Fiber dari nol, lengkap dengan GORM untuk ORM dan PostgreSQL sebagai database.

Apa Itu Go Fiber dan Kenapa Pilih Saja?

Go Fiber dibuat oleh Fenny dan komunitasnya dengan satu tujuan jelas: bikin web development di Go se-familiar mungkin bagi developer yang sudah terbiasa dengan framework lain. Routing-nya mirip Express, middleware-nya gampang dipahami, dan performanya di benchmark sering jadi yang tercepat dibanding framework Go lainnya.

Buat saya, tiga alasan utama kenapa Fiber jadi pilihan:

  • Express-like API: app.Get("/users/:id", handler) siapapun yang pernah pakai Express langsung paham. Tidak perlu belajar konsep baru.
  • Fasthttp engine: Benchmark menunjukkan Fiber bisa handle jutaan request per detik di kondisi ideal. Untuk API production, ini relevan banget.
  • Zero allocation: Fiber dirancang untuk minim memory allocation. Tiap request tidak bikin garbage collector Go kerja keras.

Itu bukan berarti Fiber cocok untuk semua kasus. Kalau kamu butuh integrasi dengan library yang spesifik pakai net/http.Handler interface (seperti beberapa library OAuth), kamu perlu adapter tambahan. Tapi untuk 90% use case API development, Fiber lebih dari cukup.

Persiapan: Install Go, Fiber, dan Dependencies

Pertama, pastikan Go versi 1.21 atau yang lebih baru terinstall. Cek dengan:

go version
# go version go1.22.1 linux/amd64

Kalau belum ada, download dari go.dev/dl. Setelah itu, bikin project baru:

mkdir fiber-api
cd fiber-api
go mod init github.com/kamu/fiber-api

Lalu install dependencies yang kita butuhkan:

go get github.com/gofiber/fiber/v2
go get github.com/gofiber/fiber/v2/middleware/cors
go get gorm.io/gorm
go get gorm.io/driver/postgres

Saya pakai GORM sebagai ORM karena dia paling matang di ekosistem Go. Alternatif lain ada sqlx (lebih low-level) dan ent (Facebook's ent framework), tapi GORM paling gampang untuk pemula karena ada auto-migration, hooks, dan eager loading.

Struktur Project yang Rapi

Saya belajar susahnya debugging code yang ditumpuk di satu file main.go sebesar 500 baris. Setelah refactor beberapa kali, struktur ini yang paling nyaman untuk project Fiber menengah:

fiber-api/
|-- main.go
|-- config/
| |-- database.go
|-- models/
| |-- user.go
|-- handlers/
| |-- user_handler.go
|-- go.mod
|-- go.sum

Bagi yang baru di Go: package diletakkan berdasarkan direktori, bukan file. Jadi semua file .go di direktori handlers/ bagian dari package handlers. Kalau kamu datang dari PHP atau Node, ini agak beda tapi cepat terbiasa.

Konfigurasi Database PostgreSQL

Buat file config/database.go untuk handle koneksi PostgreSQL via GORM:

package config

import (
 "fmt"
 "os"
 "gorm.io/driver/postgres"
 "gorm.io/gorm"
 "gorm.io/gorm/logger"
)

func DatabaseConnection() *gorm.DB {
 dsn := fmt.Sprintf(
 "host=%s user=%s password=%s dbname=%s port=%s sslmode=disable",
 os.Getenv("DB_HOST"),
 os.Getenv("DB_USER"),
 os.Getenv("DB_PASS"),
 os.Getenv("DB_NAME"),
 os.Getenv("DB_PORT"),
 )

 db, err := gorm.Open(postgres.Open(dsn), &gorm.Config{
 Logger: logger.Default.LogMode(logger.Info),
 })
 if err != nil {
 panic("Failed to connect database: " + err.Error())
 }

 return db
}

Kenapa pakai environment variable? Karena hardcoding credentials di source code adalah hal pertama yang ditulis di hall of shame saat code review. Untuk development lokal, buat file .env dan pakai package seperti joho/godotenv untuk load-nya.

go get github.com/joho/godotenv

File .env untuk development lokal:

DB_HOST=localhost
DB_USER=postgres
DB_PASS=rahasia123
DB_NAME=fiber_api
DB_PORT=5432
PORT=3000

Definisi Model dengan GORM

GORM pakai struct tags untuk mapping ke database. Bikin file models/user.go:

package models

import (
 "time"
 "gorm.io/gorm"
)

type User struct {
 ID uint `gorm:"primaryKey" json:"id"`
 Name string `gorm:"size:100;not null" json:"name"`
 Email string `gorm:"size:150;uniqueIndex;not null" json:"email"`
 Bio string `gorm:"size:500" json:"bio"`
 CreatedAt time.Time `json:"created_at"`
 UpdatedAt time.Time `json:"updated_at"`
 DeletedAt gorm.DeletedAt `gorm:"index" json:"-"`
}

Beberapa hal yang perlu kamu perhatikan di model ini:

  • gorm:"primaryKey" bikin field jadi primary key. GORM auto-detect ID sebagai PK, tapi eksplisit lebih jelas.
  • uniqueIndex di Email mencegah duplikat email. GORM bikin index-nya saat auto-migrate.
  • gorm.DeletedAt mengaktifkan soft delete. Record tidak benar-benar dihapus dari DB, cuma di-flag. Bagus untuk data yang butuh audit trail.
  • json:"-" di DeletedAt supaya field itu tidak muncul di response API.

Membuat Handler dan Routing

File handlers/user_handler.go berisi logic CRUD. Saya pakai pattern dimana handler menerima *gorm.DB sebagai dependency, bukan akses global. Ini bikin testing lebih gampang:

package handlers

import (
 "github.com/gofiber/fiber/v2"
 "github.com/kamu/fiber-api/models"
 "gorm.io/gorm"
)

func GetUsers(c *fiber.Ctx) error {
 db := c.Locals("db").(*gorm.DB)
 var users []models.User

 page := c.QueryInt("page", 1)
 limit := c.QueryInt("limit", 10)
 offset := (page - 1) * limit

 if err := db.Limit(limit).Offset(offset).Find(&users).Error; err != nil {
 return c.Status(500).JSON(fiber.Map{
 "error": "Gagal mengambil data user",
 })
 }

 return c.JSON(fiber.Map{
 "data": users,
 "page": page,
 "limit": limit,
 })
}

func CreateUser(c *fiber.Ctx) error {
 db := c.Locals("db").(*gorm.DB)
 var input models.User

 if err := c.BodyParser(&input); err != nil {
 return c.Status(400).JSON(fiber.Map{
 "error": "Format request tidak valid",
 })
 }

 if input.Name == "" || input.Email == "" {
 return c.Status(400).JSON(fiber.Map{
 "error": "Name dan Email wajib diisi",
 })
 }

 if err := db.Create(&input).Error; err != nil {
 return c.Status(500).JSON(fiber.Map{
 "error": "Gagal membuat user, mungkin email sudah terdaftar",
 })
 }

 return c.Status(201).JSON(input)
}

func GetUser(c *fiber.Ctx) error {
 db := c.Locals("db").(*gorm.DB)
 id := c.Params("id")
 var user models.User

 if err := db.First(&user, id).Error; err != nil {
 return c.Status(404).JSON(fiber.Map{
 "error": "User tidak ditemukan",
 })
 }

 return c.JSON(user)
}

func UpdateUser(c *fiber.Ctx) error {
 db := c.Locals("db").(*gorm.DB)
 id := c.Params("id")
 var user models.User

 if err := db.First(&user, id).Error; err != nil {
 return c.Status(404).JSON(fiber.Map{
 "error": "User tidak ditemukan",
 })
 }

 if err := c.BodyParser(&user); err != nil {
 return c.Status(400).JSON(fiber.Map{
 "error": "Format request tidak valid",
 })
 }

 db.Save(&user)
 return c.JSON(user)
}

func DeleteUser(c *fiber.Ctx) error {
 db := c.Locals("db").(*gorm.DB)
 id := c.Params("id")
 var user models.User

 if err := db.First(&user, id).Error; err != nil {
 return c.Status(404).JSON(fiber.Map{
 "error": "User tidak ditemukan",
 })
 }

 db.Delete(&user)
 return c.JSON(fiber.Map{
 "message": "User berhasil dihapus",
 })
}

Beberapa hal penting di code di atas:

  • c.Locals("db") mengambil instance database yang kita set di middleware. Ini lebih clean daripada global variable.
  • c.QueryInt("page", 1) otomatis parse query string ke int dengan default value. Fiber handle edge case-nya untuk kamu.
  • Error handling eksplisit di tiap operasi DB. Jangan pernah abaikan err di Go itu cara Go mencegah bug yang di bahasa lain tertangkap lewat exception.
  • db.Create() otomatis isi CreatedAt dan UpdatedAt karena field-nya pakai tipe time.Time.

Menyatukan Semua di main.go

Sekarang satukan semuanya di main.go:

package main

import (
 "log"
 "os"

 "github.com/gofiber/fiber/v2"
 "github.com/gofiber/fiber/v2/middleware/cors"
 "github.com/gofiber/fiber/v2/middleware/logger"
 "github.com/joho/godotenv"

 "github.com/kamu/fiber-api/config"
 "github.com/kamu/fiber-api/handlers"
 "github.com/kamu/fiber-api/models"
)

func main() {
 err := godotenv.Load()
 if err != nil {
 log.Println("Tidak ada file .env, pakai env dari sistem")
 }

 db := config.DatabaseConnection()

 // Auto-migrate: buat tabel kalau belum ada
 err = db.AutoMigrate(&models.User{})
 if err != nil {
 panic("Gagal migrate: " + err.Error())
 }

 app := fiber.New(fiber.Config{
 AppName: "fiber-api v1.0",
 BodyLimit: 10 * 1024 * 1024, // 10MB
 ErrorHandler: customErrorHandler,
 })

 app.Use(logger.New())
 app.Use(cors.New(cors.Config{
 AllowOrigins: []string{"*"},
 AllowHeaders: []string{"Origin", "Content-Type", "Accept"},
 }))

 // Middleware: inject DB ke setiap request
 app.Use(func(c *fiber.Ctx) error {
 c.Locals("db", db)
 return c.Next()
 })

 api := app.Group("/api/v1")

 api.Get("/users", handlers.GetUsers)
 api.Post("/users", handlers.CreateUser)
 api.Get("/users/:id", handlers.GetUser)
 api.Put("/users/:id", handlers.UpdateUser)
 api.Delete("/users/:id", handlers.DeleteUser)

 port := os.Getenv("PORT")
 if port == "" {
 port = "3000"
 }

 log.Fatal(app.Listen(":" + port))
}

func customErrorHandler(c *fiber.Ctx, err error) error {
 code := fiber.StatusInternalServerError
 if e, ok := err.(*fiber.Error); ok {
 code = e.Code
 }
 return c.Status(code).JSON(fiber.Map{
 "error": err.Error(),
 })
}

Beberapa pilihan design di sini yang perlu dijelaskan:

  • API versioning: Semua route di bawah /api/v1. Kalau nanti ada breaking change, bikin /api/v2 tanpa ganggu versi lama.
  • BodyLimit 10MB: Default Fiber 4MB. Kalau API kamu menerima upload file, naikkan limit ini. Tapi ingat pakai streaming atau S3 presigned URL untuk file besar.
  • Custom error handler: Semua error diformat sebagai JSON konsisten. Frontend tidak perlu handle response format yang beda-beda.
  • CORS wildcard: Untuk development ini fine. Production sebaiknya restrict AllowOrigins ke domain yang valid saja.

Menjalankan dan Testing API

Jalankan server dengan:

go run main.go

Buka terminal lain dan test dengan curl:

# Create user
curl -X POST http://localhost:3000/api/v1/users \
 -H "Content-Type: application/json" \
 -d '{"name":"Budi Santoso","email":"[email protected]","bio":"Developer"}'

# Get all users
curl http://localhost:3000/api/v1/users

# Get single user
curl http://localhost:3000/api/v1/users/1

# Update user
curl -X PUT http://localhost:3000/api/v1/users/1 \
 -H "Content-Type: application/json" \
 -d '{"name":"Budi S.","bio":"Senior Developer"}'

# Delete user (soft delete)
curl -X DELETE http://localhost:3000/api/v1/users/1

Untuk testing yang lebih terstruktur, saya rekomendasi bikin test file dengan package testing bawaan Go plus net/http/httptest. Fiber punya method app.Test() yang super handy untuk unit test handler tanpa perlu server jalan.

Deploy ke Production

Untuk production, ada beberapa langkah yang wajib:

Pertama, build binary dengan optimasi:

CGO_ENABLED=0 go build -ldflags="-s -w" -o fiber-api main.go

Flag -s -w strip debug info dan symbol table. Ukuran binary jadi lebih kecil, sekitar 10-15MB dari 30MB+. CGO_ENABLED=0 bikin binary static-linked, cocok untuk container Docker dari scratch.

Kedua, pakai systemd atau Docker untuk run-nya. Systemd contohnya:

[Unit]
Description=Fiber API
After=network.target postgresql.service

[Service]
Type=simple
User=www-data
WorkingDirectory=/opt/fiber-api
ExecStart=/opt/fiber-api/fiber-api
Restart=always
RestartSec=5
EnvironmentFile=/opt/fiber-api/.env

[Install]
WantedBy=multi-user.target

Ketiga, pakai Nginx atau Caddy sebagai reverse proxy untuk SSL termination dan rate limiting. API langsung exposed ke internet tanpa reverse proxy adalah resep bencana, terutama kalau tidak ada rate limiting.

Tips Tambahan dari Pengalaman

Satu hal yang bikin saya struggling awalnya: GORM query yang n+1. Kalau kamu punya relasi has many dan loop di handler, tiap iterasi bikin query DB baru. Selalu pakai Preload() untuk eager loading:

// WRONG: n+1 query
var users []models.User
db.Find(&users)
for _, u := range users {
 db.Model(&u).Related(&u.Posts) // n queries!
}

// CORRECT: single query dengan JOIN
var users []models.User
db.Preload("Posts").Find(&users)

Hal kedua: validasi input. GORM punya hook BeforeCreate yang bisa dipakai untuk validasi:

func (u *User) BeforeCreate(tx *gorm.DB) error {
 if !strings.Contains(u.Email, "@") {
 return errors.New("email tidak valid")
 }
 return nil
}

Tapi untuk validasi yang kompleks, pakai library terpisah seperti go-playground/validator. Fiber punya integration yang gampang dengan package itu lewat parser middleware.

Hal ketiga: logging. Pakai slog (structured logging bawaan Go 1.21+). Lebih gampang di-query di production dibanding log.Println yang plain text. Output JSON yang bisa langsung di-consume oleh ELK stack atau Grafana Loki.

Kesimpulan

Go Fiber + GORM + PostgreSQL adalah kombinasi yang powerful untuk bikin REST API. Kalau kamu datang dari Node.js atau PHP, learning curve-nya cukup landai karena Fiber sengaja didesain mirip Express. GORM meskipun kadang dianggap "heavy" untuk standar Go, tetap paling produktif untuk development cepat.

Yang penting: mulai dari struktur project yang rapi dari awal. Refactor setelah production jalan jauh lebih menyakitkan daripada spend 30 menit setup struktur folder sebelum nulis kode pertama.

Kamu sudah pernah coba Go Fiber atau lebih prefer framework lain seperti Echo atau Gin? Bagikan pengalamanmu di kolom komentar, saya penasaran denger perspektif lain soal ini!


Bagikan artikel ini:

Komentar (0)

Belum ada komentar. Jadilah yang pertama memberikan tanggapan!

Tinggalkan Komentar