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 menggunakan net/http standar, Fiber menggunakan Fasthttp yang tidak kompatibel dengan http.Handler. Ini berarti middleware atau library yang ditulis untuk net/http tidak bisa digunakan langsung di Fiber. Fiber menyediakan adapter adaptor untuk 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:#fff
Karena *fiber.Ctx di-reuse dari pool, jangan simpan referensi ke c di 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 header

Parsing 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 memiliki binding tag terintegrasi dengan validasi, Fiber tidak menyertakan validator bawaan. Kamu perlu menginisialisasi go-playground/validator secara manual dan memanggilnya setelah BodyParser. 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, cleanup

Locals 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 package adaptor jika butuh middleware net/http.
  • *fiber.Ctx di-reuse — jangan simpan referensi ke context di goroutine lain; salin nilai yang dibutuhkan terlebih dahulu.
  • BodyParser otomatis deteksi format — JSON, XML, dan form ditangani otomatis berdasarkan Content-Type, tanpa method yang berbeda untuk tiap format.
  • Tidak ada built-in validator — tambahkan go-playground/validator secara manual dan buat helper error yang informatif.
  • fiber.NewError untuk error berstruktur — kembalikan error dengan status code yang tepat dan biarkan ErrorHandler global 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 ShutdownWithTimeout di production agar request yang sedang berjalan tidak terputus paksa.

← Sebelumnya: Gin   Berikutnya: Echo →

About | Author | Content Scope | Editorial Policy | Privacy Policy | Disclaimer | Contact