Fiber #
Fiber adalah web framework Go yang dibangun di atas Fasthttp — HTTP engine tercepat untuk Go — dan terinspirasi dari Express.js. Hasilnya adalah framework dengan throughput yang secara konsisten lebih tinggi dibandingkan Gin maupun Echo di berbagai benchmark, sekaligus menawarkan API yang familiar bagi developer yang pernah bekerja dengan Node.js. Fiber cocok untuk kamu yang membangun layanan high-throughput seperti API gateway, proxy, atau endpoint yang harus menangani ratusan ribu request per detik. Artikel ini membahas semua fitur utama Fiber: routing, middleware, parsing request, response, WebSocket, hingga pola organisasi kode untuk production.
Instalasi #
go get github.com/gofiber/fiber/v2
Server minimal untuk memverifikasi instalasi:
package main
import "github.com/gofiber/fiber/v2"
func main() {
app := fiber.New()
app.Get("/ping", func(c *fiber.Ctx) error {
return c.JSON(fiber.Map{"message": "pong"})
})
app.Listen(":8080")
}
Berbeda dengan Gin yang menggunakannet/httpstandar, Fiber menggunakan Fasthttp yang tidak kompatibel denganhttp.Handler. Ini berarti middleware atau library yang ditulis untuknet/httptidak bisa digunakan langsung di Fiber. Fiber menyediakan adapteradaptoruntuk kasus ini.
Cara Kerja Request di Fiber #
Memahami arsitektur Fiber penting sebelum menulis kode. Fiber memanfaatkan zero-allocation request handling dari Fasthttp — artinya objek *fiber.Ctx di-reuse antar request untuk menghindari tekanan garbage collector.
flowchart TD
A([HTTP Request]) --> B[Fasthttp Engine]
B --> C[fiber.Ctx dari pool]
C --> D[Router — trie-based matching]
D --> E{Route ditemukan?}
E -- Tidak --> F[404 / Error Handler]
E -- Ya --> G[Middleware Chain]
G --> H[Handler Utama]
H --> I[Response ditulis ke Fasthttp]
I --> J[fiber.Ctx dikembalikan ke pool]
J --> K([Response ke Client])
F --> K
style A fill:#00adb5,color:#fff
style K fill:#00adb5,color:#fff
style F fill:#e05252,color:#fffKarena*fiber.Ctxdi-reuse dari pool, jangan simpan referensi kecdi goroutine lain. Jika perlu menggunakan data dari context di goroutine terpisah, salin nilainya terlebih dahulu sebelum memanggil goroutine tersebut.
Konfigurasi App #
Fiber menerima fiber.Config saat inisialisasi untuk menyesuaikan perilaku server:
app := fiber.New(fiber.Config{
AppName: "MyAPI v1.0",
Prefork: false, // true: fork per CPU core (Linux only)
ReadTimeout: 5 * time.Second,
WriteTimeout: 10 * time.Second,
BodyLimit: 4 * 1024 * 1024, // 4 MB body limit
// Custom error handler global
ErrorHandler: func(c *fiber.Ctx, err error) error {
code := fiber.StatusInternalServerError
var e *fiber.Error
if errors.As(err, &e) {
code = e.Code
}
return c.Status(code).JSON(fiber.Map{
"success": false,
"error": err.Error(),
})
},
})
Mode Production #
// ANTI-PATTERN: stack trace error terekspos ke client di production
app := fiber.New() // default PrintRoutes: true, stack trace: true
// BENAR: matikan info sensitif di production
isProd := os.Getenv("APP_ENV") == "production"
app := fiber.New(fiber.Config{
AppName: "MyAPI",
DisableStartupMessage: isProd,
// Gunakan custom ErrorHandler agar stack trace tidak bocor
ErrorHandler: productionErrorHandler,
})
Routing #
Routing di Fiber menggunakan method HTTP sebagai nama fungsi. Sintaksnya sengaja dibuat mirip Express.js.
app.Get("/users", listUsers)
app.Post("/users", createUser)
app.Put("/users/:id", updateUser)
app.Patch("/users/:id", patchUser)
app.Delete("/users/:id", deleteUser)
// Semua method
app.All("/webhook", handleWebhook)
Parameter Route #
// Parameter wajib
app.Get("/users/:id", func(c *fiber.Ctx) error {
id := c.Params("id")
return c.JSON(fiber.Map{"id": id})
})
// Parameter opsional — cocokkan /posts dan /posts/123
app.Get("/posts/:id?", func(c *fiber.Ctx) error {
id := c.Params("id", "all") // "all" jika tidak ada
return c.JSON(fiber.Map{"id": id})
})
// Wildcard
app.Get("/files/*", func(c *fiber.Ctx) error {
path := c.Params("*")
return c.JSON(fiber.Map{"path": path})
})
Query String dan Headers #
// GET /search?q=golang&page=2
app.Get("/search", func(c *fiber.Ctx) error {
q := c.Query("q")
page := c.QueryInt("page", 1) // default 1 jika tidak ada
return c.JSON(fiber.Map{"q": q, "page": page})
})
// Membaca header
app.Get("/profile", func(c *fiber.Ctx) error {
token := c.Get("Authorization")
lang := c.Get("Accept-Language", "id") // default "id"
return c.JSON(fiber.Map{"token": token, "lang": lang})
})
Route Groups #
api := app.Group("/api")
// v1 — tanpa auth
v1 := api.Group("/v1")
v1.Get("/health", healthHandler)
// v2 — dengan auth middleware
v2 := api.Group("/v2", authMiddleware)
v2.Get("/users", listUsers)
v2.Post("/users", createUser)
// Nested group
admin := v2.Group("/admin", adminOnlyMiddleware)
admin.Get("/metrics", metricsHandler)
admin.Delete("/users/:id", forceDeleteUser)
graph TD
A["/api"] --> B["/v1"]
A --> C["/v2 + authMiddleware"]
B --> D["GET /health"]
C --> E["GET /users"]
C --> F["POST /users"]
C --> G["/admin + adminOnly"]
G --> H["GET /metrics"]
G --> I["DELETE /users/:id"]Middleware #
Middleware di Fiber bertipe fiber.Handler — sama dengan handler biasa. Fiber menyediakan banyak middleware resmi via package terpisah di gofiber/fiber/v2/middleware.
Middleware Bawaan #
import (
"github.com/gofiber/fiber/v2/middleware/logger"
"github.com/gofiber/fiber/v2/middleware/recover"
"github.com/gofiber/fiber/v2/middleware/cors"
"github.com/gofiber/fiber/v2/middleware/limiter"
"github.com/gofiber/fiber/v2/middleware/compress"
)
app := fiber.New()
// Recovery — tangkap panic
app.Use(recover.New())
// Logger
app.Use(logger.New(logger.Config{
Format: "${time} | ${status} | ${latency} | ${method} ${path}\n",
}))
// CORS
app.Use(cors.New(cors.Config{
AllowOrigins: "https://app.example.com",
AllowHeaders: "Origin, Content-Type, Authorization",
AllowMethods: "GET, POST, PUT, DELETE",
}))
// Rate limiter
app.Use(limiter.New(limiter.Config{
Max: 100, // 100 request
Expiration: 1 * time.Minute, // per menit per IP
}))
// Kompresi response
app.Use(compress.New(compress.Config{
Level: compress.LevelBestSpeed,
}))
Middleware Kustom #
func RequestIDMiddleware() fiber.Handler {
return func(c *fiber.Ctx) error {
requestID := uuid.New().String()
// Simpan di locals — mirip c.Set di Gin
c.Locals("requestID", requestID)
// Tambahkan ke response header
c.Set("X-Request-ID", requestID)
return c.Next() // lanjutkan ke handler berikutnya
}
}
func AuthMiddleware() fiber.Handler {
return func(c *fiber.Ctx) error {
token := c.Get("Authorization")
if token == "" {
return fiber.NewError(fiber.StatusUnauthorized, "token tidak ditemukan")
}
userID, err := validateToken(token)
if err != nil {
return fiber.NewError(fiber.StatusUnauthorized, "token tidak valid")
}
c.Locals("userID", userID)
return c.Next()
}
}
Urutan Eksekusi Middleware #
sequenceDiagram
participant Client
participant RID as RequestIDMiddleware
participant Auth as AuthMiddleware
participant H as Handler
Client->>RID: Request masuk
RID->>RID: Generate request ID
RID->>Auth: c.Next()
Auth->>Auth: Validasi token
Auth->>H: c.Next()
H->>H: Proses bisnis
H-->>Auth: return response
Auth-->>RID: return
RID->>RID: Set header X-Request-ID
RID-->>Client: Response + X-Request-ID headerParsing Request #
Fiber menyediakan method BodyParser untuk mendecode body request ke struct, dengan dukungan JSON, XML, dan form secara otomatis berdasarkan Content-Type.
Body Parsing #
type CreateProductRequest struct {
Name string `json:"name" form:"name" xml:"name" validate:"required,min=3"`
Price float64 `json:"price" form:"price" xml:"price" validate:"required,gt=0"`
Stock int `json:"stock" form:"stock" xml:"stock" validate:"required,gte=0"`
Category string `json:"category" form:"category" xml:"category" validate:"required"`
}
func createProduct(c *fiber.Ctx) error {
var req CreateProductRequest
if err := c.BodyParser(&req); err != nil {
return fiber.NewError(fiber.StatusBadRequest, "request body tidak valid")
}
// Fiber tidak punya built-in validator — gunakan go-playground/validator
if err := validate.Struct(req); err != nil {
return fiber.NewError(fiber.StatusUnprocessableEntity, err.Error())
}
// proses bisnis...
return c.Status(fiber.StatusCreated).JSON(fiber.Map{
"success": true,
"data": req,
})
}
Berbeda dengan Gin yang memilikibindingtag terintegrasi dengan validasi, Fiber tidak menyertakan validator bawaan. Kamu perlu menginisialisasigo-playground/validatorsecara manual dan memanggilnya setelahBodyParser. Ini memberikan fleksibilitas lebih, tetapi membutuhkan boilerplate tambahan.
Integrasi Validator #
import "github.com/go-playground/validator/v10"
// Inisialisasi sekali, gunakan ulang (validator thread-safe)
var validate = validator.New()
// Helper untuk error yang lebih informatif
func parseValidationErrors(err error) []fiber.Map {
var errs []fiber.Map
for _, e := range err.(validator.ValidationErrors) {
errs = append(errs, fiber.Map{
"field": e.Field(),
"message": validationMessage(e),
})
}
return errs
}
func validationMessage(e validator.FieldError) string {
switch e.Tag() {
case "required":
return e.Field() + " wajib diisi"
case "min":
return e.Field() + " minimal " + e.Param() + " karakter"
case "gt":
return e.Field() + " harus lebih besar dari " + e.Param()
default:
return e.Field() + " tidak valid"
}
}
Query dan Params Parsing ke Struct #
type PaginationQuery struct {
Page int `query:"page"`
Limit int `query:"limit"`
Sort string `query:"sort"`
}
func listUsers(c *fiber.Ctx) error {
var q PaginationQuery
if err := c.QueryParser(&q); err != nil {
return fiber.NewError(fiber.StatusBadRequest, err.Error())
}
// Default values
if q.Page <= 0 {
q.Page = 1
}
if q.Limit <= 0 || q.Limit > 100 {
q.Limit = 20
}
// proses...
return c.JSON(fiber.Map{"page": q.Page, "limit": q.Limit})
}
flowchart LR
A["JSON Body\nContent-Type: application/json"] --> E
B["Form Data\nContent-Type: multipart/form-data"] --> E
C["XML Body\nContent-Type: application/xml"] --> E
D["Query String\n?page=1&limit=20"] --> F["QueryParser"]
E["BodyParser"] --> G[Struct Go]
F --> G
G --> H{go-playground\nvalidator}
H -- Gagal --> I["422 / 400"]
H -- Lolos --> J[Handler Logic]Response #
Fiber menyediakan API response yang kaya dan ekspresif. Method-method ini bisa di-chain karena semuanya mengembalikan error.
JSON dan Format Lain #
// JSON
return c.JSON(fiber.Map{"success": true, "data": user})
// JSON dengan status code eksplisit
return c.Status(fiber.StatusCreated).JSON(fiber.Map{
"success": true,
"data": user,
})
// String
return c.SendString("Hello, World!")
// Status tanpa body (DELETE)
return c.SendStatus(fiber.StatusNoContent)
// XML
return c.XML(user)
// File download
return c.Download("/path/to/file.pdf", "laporan.pdf")
// Redirect
return c.Redirect("https://example.com", fiber.StatusMovedPermanently)
Response Helper Terpusat #
Sama seperti Gin, memiliki helper response yang konsisten sangat direkomendasikan:
// pkg/response/response.go
package response
import "github.com/gofiber/fiber/v2"
type Response struct {
Success bool `json:"success"`
Data interface{} `json:"data,omitempty"`
Error string `json:"error,omitempty"`
Meta interface{} `json:"meta,omitempty"`
}
type PaginatedMeta struct {
Page int `json:"page"`
Limit int `json:"limit"`
TotalItems int `json:"total_items"`
TotalPages int `json:"total_pages"`
}
func OK(c *fiber.Ctx, data interface{}) error {
return c.JSON(Response{Success: true, Data: data})
}
func Created(c *fiber.Ctx, data interface{}) error {
return c.Status(fiber.StatusCreated).JSON(Response{
Success: true, Data: data,
})
}
func Paginated(c *fiber.Ctx, data interface{}, meta PaginatedMeta) error {
return c.JSON(Response{Success: true, Data: data, Meta: meta})
}
func BadRequest(c *fiber.Ctx, msg string) error {
return c.Status(fiber.StatusBadRequest).JSON(Response{
Success: false, Error: msg,
})
}
func NotFound(c *fiber.Ctx, resource string) error {
return c.Status(fiber.StatusNotFound).JSON(Response{
Success: false, Error: resource + " tidak ditemukan",
})
}
func InternalError(c *fiber.Ctx) error {
return c.Status(fiber.StatusInternalServerError).JSON(Response{
Success: false, Error: "terjadi kesalahan internal",
})
}
Upload File #
// Upload file tunggal
app.Post("/upload", func(c *fiber.Ctx) error {
file, err := c.FormFile("file")
if err != nil {
return fiber.NewError(fiber.StatusBadRequest, "file tidak ditemukan")
}
// Validasi ukuran (misalnya max 5 MB)
if file.Size > 5*1024*1024 {
return fiber.NewError(fiber.StatusRequestEntityTooLarge,
"ukuran file melebihi batas 5 MB")
}
// Validasi ekstensi
ext := filepath.Ext(file.Filename)
allowed := map[string]bool{".jpg": true, ".png": true, ".pdf": true}
if !allowed[strings.ToLower(ext)] {
return fiber.NewError(fiber.StatusBadRequest, "tipe file tidak diizinkan")
}
// Simpan dengan nama unik
filename := fmt.Sprintf("%d%s", time.Now().UnixNano(), ext)
dst := filepath.Join("uploads", filename)
if err := c.SaveFile(file, dst); err != nil {
return fiber.NewError(fiber.StatusInternalServerError, "gagal menyimpan file")
}
return c.JSON(fiber.Map{
"filename": filename,
"size": file.Size,
})
})
WebSocket #
Salah satu keunggulan Fiber dibandingkan Gin adalah dukungan WebSocket yang lebih mature via package gofiber/websocket.
go get github.com/gofiber/websocket/v2
import "github.com/gofiber/websocket/v2"
// Middleware untuk upgrade koneksi
app.Use("/ws", func(c *fiber.Ctx) error {
if websocket.IsWebSocketUpgrade(c) {
c.Locals("allowed", true)
return c.Next()
}
return fiber.ErrUpgradeRequired
})
// Handler WebSocket
app.Get("/ws/chat", websocket.New(func(c *websocket.Conn) {
// c.Locals tersedia dari middleware
userID := c.Locals("userID")
for {
msgType, msg, err := c.ReadMessage()
if err != nil {
// Client disconnect
break
}
// Echo balik pesan
if err := c.WriteMessage(msgType, msg); err != nil {
break
}
}
}, websocket.Config{
ReadBufferSize: 1024,
WriteBufferSize: 1024,
}))
sequenceDiagram
participant Client
participant MW as WebSocket Middleware
participant WS as WebSocket Handler
Client->>MW: HTTP GET /ws/chat\nUpgrade: websocket
MW->>MW: IsWebSocketUpgrade?
MW->>WS: c.Next()
WS->>Client: 101 Switching Protocols
loop Koneksi aktif
Client->>WS: WriteMessage(data)
WS->>WS: Proses pesan
WS->>Client: WriteMessage(response)
end
Client->>WS: Disconnect
WS->>WS: Loop break, cleanupLocals dan Data Sharing #
Fiber menggunakan c.Locals() untuk berbagi data antar middleware dan handler — setara dengan c.Set()/c.Get() di Gin.
// Di middleware — simpan data
c.Locals("userID", 42)
c.Locals("role", "admin")
// Di handler — baca data
userID := c.Locals("userID").(int)
role := c.Locals("role").(string)
Helper Bertipe untuk Locals #
// ANTI-PATTERN: type assertion langsung, panik jika nil
func getUser(c *fiber.Ctx) error {
userID := c.Locals("userID").(int) // panik jika middleware tidak dijalankan
// ...
}
// BENAR: helper dengan penanganan nil
func GetUserID(c *fiber.Ctx) (int, bool) {
val := c.Locals("userID")
if val == nil {
return 0, false
}
id, ok := val.(int)
return id, ok
}
func getUser(c *fiber.Ctx) error {
userID, ok := GetUserID(c)
if !ok {
return fiber.NewError(fiber.StatusUnauthorized, "tidak terautentikasi")
}
// gunakan userID
return c.JSON(fiber.Map{"userID": userID})
}
Error Handling #
Fiber memiliki sistem error handling global yang elegan. Sembalikan error dari handler dan biarkan ErrorHandler menanganinya.
// Gunakan fiber.NewError untuk error dengan status code
app.Get("/users/:id", func(c *fiber.Ctx) error {
id, err := strconv.Atoi(c.Params("id"))
if err != nil {
return fiber.NewError(fiber.StatusBadRequest, "ID harus berupa angka")
}
user, err := userService.GetByID(id)
if err != nil {
if errors.Is(err, ErrNotFound) {
return fiber.NewError(fiber.StatusNotFound, "user tidak ditemukan")
}
// Error tak terduga — kembalikan tanpa detail ke client
log.Printf("GetByID error: %v", err)
return fiber.ErrInternalServerError
}
return c.JSON(user)
})
Custom Error Handler Global #
app := fiber.New(fiber.Config{
ErrorHandler: func(c *fiber.Ctx, err error) error {
code := fiber.StatusInternalServerError
message := "terjadi kesalahan internal"
var e *fiber.Error
if errors.As(err, &e) {
code = e.Code
message = e.Message
}
return c.Status(code).JSON(fiber.Map{
"success": false,
"error": message,
"path": c.Path(),
})
},
})
flowchart TD
A[Handler mengembalikan error] --> B{errors.As fiber.Error?}
B -- Ya --> C[Ambil Code dan Message dari fiber.Error]
B -- Tidak --> D[Code: 500, Message: kesalahan internal]
C --> E[c.Status code .JSON response]
D --> E
E --> F([Response ke Client])Struktur Project yang Direkomendasikan #
myapp/
├── main.go
├── internal/
│ ├── handler/
│ │ ├── user.go
│ │ └── product.go
│ ├── middleware/
│ │ ├── auth.go
│ │ └── request_id.go
│ ├── service/
│ │ ├── user.go
│ │ └── product.go
│ └── repository/
│ ├── user.go
│ └── product.go
├── pkg/
│ ├── response/
│ │ └── response.go
│ └── validator/
│ └── validator.go
└── router/
└── router.go
Inisialisasi Router #
// router/router.go
package router
import (
"github.com/gofiber/fiber/v2"
"github.com/gofiber/fiber/v2/middleware/recover"
"github.com/gofiber/fiber/v2/middleware/logger"
"myapp/internal/handler"
"myapp/internal/middleware"
)
func Setup(userHandler *handler.UserHandler) *fiber.App {
app := fiber.New(fiber.Config{
ErrorHandler: middleware.ErrorHandler,
})
// Middleware global
app.Use(recover.New())
app.Use(logger.New())
app.Use(middleware.RequestID())
// Health check
app.Get("/health", func(c *fiber.Ctx) error {
return c.JSON(fiber.Map{"status": "ok"})
})
// API routes
api := app.Group("/api/v1")
api.Use(middleware.Auth())
users := api.Group("/users")
users.Get("", userHandler.List)
users.Post("", userHandler.Create)
users.Get("/:id", userHandler.GetByID)
users.Put("/:id", userHandler.Update)
users.Delete("/:id", userHandler.Delete)
return app
}
Graceful Shutdown #
Fiber mendukung graceful shutdown — memastikan semua request yang sedang diproses selesai sebelum server berhenti.
func main() {
app := fiber.New()
// ... setup routes
// Jalankan server di goroutine terpisah
go func() {
if err := app.Listen(":8080"); err != nil {
log.Printf("server error: %v", err)
}
}()
// Tunggu sinyal OS (Ctrl+C atau SIGTERM dari orchestrator)
quit := make(chan os.Signal, 1)
signal.Notify(quit, os.Interrupt, syscall.SIGTERM)
<-quit
log.Println("Mematikan server...")
// Beri waktu 10 detik untuk menyelesaikan request yang sedang berjalan
if err := app.ShutdownWithTimeout(10 * time.Second); err != nil {
log.Printf("shutdown error: %v", err)
}
log.Println("Server berhenti.")
}
Kapan Tidak Menggunakan Fiber #
Tetap gunakan Fiber jika:
✓ Performa ekstrem adalah prioritas utama
✓ Tim familiar dengan Express.js dan ingin transisi ke Go
✓ Butuh built-in WebSocket yang mature
✓ Membangun proxy, gateway, atau layanan high-throughput
Pertimbangkan Gin jika:
✗ Butuh kompatibilitas penuh dengan ekosistem net/http
✗ Tim lebih familiar dengan konvensi Gin yang sudah mapan
✗ Library pihak ketiga yang kamu gunakan mengharapkan http.Handler
Pertimbangkan Echo jika:
✗ Butuh HTTP/2 native atau binding yang lebih fleksibel
✗ Ingin framework yang lebih dekat ke standar Go
Pertimbangkan net/http standar jika:
✗ Aplikasi sangat sederhana dan overhead framework tidak diinginkan
Ringkasan #
- Fasthttp, bukan net/http — Fiber dibangun di atas Fasthttp yang tidak kompatibel dengan
http.Handler; gunakan packageadaptorjika butuh middleware net/http.*fiber.Ctxdi-reuse — jangan simpan referensi ke context di goroutine lain; salin nilai yang dibutuhkan terlebih dahulu.BodyParserotomatis deteksi format — JSON, XML, dan form ditangani otomatis berdasarkanContent-Type, tanpa method yang berbeda untuk tiap format.- Tidak ada built-in validator — tambahkan
go-playground/validatorsecara manual dan buat helper error yang informatif.fiber.NewErroruntuk error berstruktur — kembalikan error dengan status code yang tepat dan biarkanErrorHandlerglobal yang memformatnya.c.Locals()untuk data sharing — gunakan helper bertipe untuk menghindari type assertion yang berpotensi panik.- Middleware resmi tersedia terpisah — logger, recover, CORS, rate limiter, compress, dan lainnya tersedia di subpackage
middleware.- Graceful shutdown — selalu implementasikan
ShutdownWithTimeoutdi production agar request yang sedang berjalan tidak terputus paksa.