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.
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.
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.
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")
}