Go’s standard library net/http is renowned for its reliability and simplicity. However, as backend services expand in complexity, engineering teams frequently require ergonomics like rapid parametric routing, robust middleware chaining, and unified JSON handling.
Fiber is an Express-inspired Go web framework built on top of Fasthttp, the fastest HTTP engine for Go. In this article, we explore how to structure a production-grade REST API service using Go Fiber, emphasizing clean architecture, separation of concerns, and zero-allocation routing.
Why Go Fiber?
Unlike traditional Go web frameworks that wrap net/http, Fiber utilizes Fashtttp’s pre-allocated memory structures, achieving:
- Ultra-low latency: Zero memory allocation routing on the critical path.
- Express-like API syntax: Familiar request/response ergonomics (
c.JSON(),c.Params(),c.Query()). - Comprehensive middleware ecosystem: Built-in CORS, Logger, Recover, Limiter, and Compression.
1. Project Directory Architecture
For scalable microservices, avoid throwing all handlers into a single file. Structure your project into domain layers:
cmd/
api/
main.go
internal/
config/
config.go
handlers/
language_handler.go
models/
language.go
routes/
routes.go
2. Defining Domain Models & DTOs
Let’s define a domain entity representing programming languages:
package models
import "time"
type Language struct {
ID uint `json:"id"`
Name string `json:"name" validate:"required,min=2"`
Year int `json:"year" validate:"required,gt=1950"`
Creator string `json:"creator"`
Paradigm string `json:"paradigm"`
CreatedAt time.Time `json:"created_at"`
}
3. Implementing the Handler Layer
Fiber handlers accept a *fiber.Ctx pointer and return an error:
package handlers
import (
"github.com/gofiber/fiber/v2"
"yourproject/internal/models"
)
type LanguageHandler struct {
// repository or service dependency injected here
}
func (h *LanguageHandler) Create(c *fiber.Ctx) error {
var payload models.Language
if err := c.BodyParser(&payload); err != nil {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
"error": "Invalid request payload",
"details": err.Error(),
})
}
// Process business logic / database write
return c.Status(fiber.StatusCreated).JSON(fiber.Map{
"message": "Language registered successfully",
"data": payload,
})
}
4. Middleware & Route Registration
Keep route definitions decoupled from application boot logic:
package routes
import (
"github.com/gofiber/fiber/v2"
"github.com/gofiber/fiber/v2/middleware/cors"
"github.com/gofiber/fiber/v2/middleware/logger"
"github.com/gofiber/fiber/v2/middleware/recover"
)
func Setup(app *fiber.App, handler *handlers.LanguageHandler) {
// Global middleware stack
app.Use(recover.New())
app.Use(logger.New())
app.Use(cors.New())
// API versioning group
api := app.Group("/api/v1")
languages := api.Group("/languages")
languages.Post("/", handler.Create)
languages.Get("/", handler.List)
languages.Get("/:id", handler.GetByID)
languages.Put("/:id", handler.Update)
languages.Delete("/:id", handler.Delete)
}
5. Graceful Shutdown
Production services must honor SIGINT and SIGTERM to complete inflight requests before termination:
package main
import (
"log"
"os"
"os/signal"
"syscall"
"github.com/gofiber/fiber/v2"
)
func main() {
app := fiber.New(fiber.Config{
AppName: "Language Registry Service v1.0",
})
// Setup routes...
// Listen on goroutine
go func() {
if err := app.Listen(":8080"); err != nil {
log.Fatalf("Server shutdown error: %v", err)
}
}()
// Await termination signal
quit := make(chan os.Signal, 1)
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
<-quit
log.Println("Gracefully stopping server...")
if err := app.Shutdown(); err != nil {
log.Fatalf("Forced shutdown: %v", err)
}
log.Println("Server closed cleanly.")
}
Key Takeaways
- Fiber’s Fasthttp backing provides incredible throughput with minimal heap allocation.
- Grouping routes by versioning schemes (
/api/v1) ensures backward compatibility. - Leveraging Fiber’s built-in recover and error handling protects your process from uncaught runtime panics.
In the next article, we explore how to hook Go Fiber directly into persistent relational databases using GORM.