SKIP TO CONTENT
HOME/LEARN/BACKEND
GOLANGFIBER

GO FOR WEB SERVICES

Building production REST APIs with Fiber, clean architecture, and graceful shutdown.

Why Go for APIs

Go compiles to a single binary with no runtime dependencies. Goroutines handle thousands of concurrent connections with minimal memory. For CPU-bound work like ETL pipelines, Go outperforms Node.js by 10-50x.

Fiber Framework Patterns

Fiber is Express for Go — familiar API, blazing fast (built on fasthttp). Group routes by resource, use middleware chains for auth/logging/rate-limiting, and return structured errors with consistent codes.

go
func SetupRoutes(app *fiber.App, h *handlers.Handler, auth *middleware.Auth) {
    api := app.Group("/api/v1")

    // Public routes
    api.Post("/auth/login", h.Login)
    api.Post("/auth/register", h.Register)

    // Protected routes — middleware chain
    protected := api.Group("", auth.Required())

    // Tickets — grouped by resource
    tickets := protected.Group("/tickets")
    tickets.Get("/", h.ListTickets)
    tickets.Post("/", auth.HasPermission("tickets:create"), h.CreateTicket)
    tickets.Get("/:id", h.GetTicket)
    tickets.Put("/:id/status", auth.HasPermission("tickets:update"), h.UpdateStatus)

    // Pipelines — admin only
    pipelines := protected.Group("/pipelines", auth.HasRole("admin"))
    pipelines.Post("/", h.CreatePipeline)
    pipelines.Post("/:id/execute", h.ExecutePipeline)
}

Flexible Query Pipelines

Instead of hardcoding queries, build a pipeline: parse filter params → validate → construct WHERE → sort → paginate. This scales to 30+ resource types with zero duplication.

go
type QueryBuilder struct {
    table   string
    filters []Filter
    sorts   []Sort
    cursor  string
    limit   int
    orgID   string  // always scoped by org
}

func (qb *QueryBuilder) Build() (string, []interface{}) {
    query := fmt.Sprintf("SELECT * FROM %s WHERE org_id = $1", qb.table)
    args := []interface{}{qb.orgID}
    idx := 2

    for _, f := range qb.filters {
        query += fmt.Sprintf(" AND %s %s $%d", f.Field, f.Op, idx)
        args = append(args, f.Value)
        idx++
    }

    if qb.cursor != "" {
        query += fmt.Sprintf(" AND id > $%d", idx)
        args = append(args, qb.cursor)
        idx++
    }

    for i, s := range qb.sorts {
        if i == 0 { query += " ORDER BY " } else { query += ", " }
        query += fmt.Sprintf("%s %s", s.Field, s.Direction)
    }

    query += fmt.Sprintf(" LIMIT %d", qb.limit)
    return query, args
}

Graceful Shutdown

Never kill connections abruptly. Listen for SIGTERM/SIGINT, stop accepting new connections, wait for in-flight requests to complete, close database pools, then exit.

go
func main() {
    app := fiber.New(fiber.Config{
        ReadTimeout:  10 * time.Second,
        WriteTimeout: 10 * time.Second,
        IdleTimeout:  30 * time.Second,
    })

    // Setup routes...
    setupRoutes(app)

    // Start server in goroutine
    go func() {
        if err := app.Listen(":8080"); err != nil {
            log.Fatalf("Server error: %v", err)
        }
    }()

    // Wait for interrupt signal
    quit := make(chan os.Signal, 1)
    signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
    <-quit

    log.Println("Shutting down gracefully...")

    // Give in-flight requests 10 seconds to complete
    if err := app.ShutdownWithTimeout(10 * time.Second); err != nil {
        log.Fatalf("Forced shutdown: %v", err)
    }

    // Close database connections
    db.Close()
    log.Println("Server stopped")
}