Fiber & Chi Frameworks

Why This Matters - The Framework Dilemma

In the world of Go web development, choosing the right framework can make or break your application's success. Imagine building a high-frequency trading platform where every microsecond counts, or creating an enterprise application where long-term maintainability and ecosystem integration are paramount.

Real-world scenario: A gaming company needed to handle 100,000+ concurrent WebSocket connections with sub-10ms latency for their real-time multiplayer platform. They chose Fiber for its extreme performance. Meanwhile, an enterprise bank selected Chi for their customer portal because they needed seamless integration with existing Prometheus monitoring, OpenTelemetry tracing, and custom authentication middleware.

These frameworks represent opposite ends of the spectrum—Fiber prioritizes raw performance while Chi prioritizes ecosystem compatibility. Your choice depends on whether you need a Formula 1 racing car or a perfectly engineered toolbox that works with every standard tool.

Learning Objectives

By the end of this article, you will be able to:

Choose the Right Framework: Make informed decisions between performance-optimized vs. ecosystem-compatible frameworks
Implement Production Applications: Build complete web services with both frameworks
Master Middleware Patterns: Create reusable middleware for authentication, logging, and performance monitoring
Optimize for Performance: Apply framework-specific optimizations for maximum throughput
Handle Real-world Scenarios: Implement WebSocket support, file uploads, and error handling
Deploy with Confidence: Set up production-ready configurations with proper security and monitoring

Core Concepts - Understanding the Trade-offs

The Performance vs. Compatibility Spectrum

Think of web frameworks like vehicles in a transportation system. A Formula 1 car achieves incredible speed but requires special fuel, special tires, and can only drive on specific tracks. A versatile delivery truck can drive on any road, use any standard fuel, and works with all existing infrastructure, though it won't win any races.

Fiber: The Performance Champion

  • Built on FastHTTP instead of Go's standard net/http
  • 2-3x faster request handling through zero-allocation routing
  • Express.js syntax makes it familiar for Node.js developers
  • Prefork mode automatically utilizes all CPU cores
  • 40-60% less memory usage through connection reuse

Chi: The Ecosystem Champion

  • 100% net/http compatible - uses standard interfaces
  • Zero external dependencies - pure Go standard library
  • Universal compatibility with all Go middleware and monitoring tools
  • Gradual migration path from existing net/http applications
  • Enterprise-ready with long-term maintainability focus

When to Choose Which Framework

Choose Fiber when:

  • Performance is the absolute critical requirement
  • You need Express.js-like syntax for team familiarity
  • Building simple, focused microservices
  • Startup environment requiring rapid iteration
  • Real-time applications

Choose Chi when:

  • Ecosystem integration is more important than raw speed
  • Building enterprise applications with long maintenance cycles
  • Need to integrate with existing monitoring/tracing infrastructure
  • Regulatory compliance requires standard tooling
  • Team values idiomatic Go patterns over framework abstractions

⚠️ Critical Decision Point: If you need ANY standard Go middleware, choose Chi. If you need maximum raw performance above all else, choose Fiber.

Fiber Framework - The Performance Specialist

Fiber is like a Formula 1 racing car—blazingly fast and technically impressive, but highly specialized and incompatible with regular roads. Built on FastHTTP, Fiber achieves incredible performance that can handle millions of requests per second, but this speed comes at a significant compatibility cost.

Installation and Quick Start

Let's dive into Fiber with a complete, production-ready example that shows why it's the go-to choice for performance-critical applications.

1# Install Fiber and essential middleware
2go get -u github.com/gofiber/fiber/v2
3go get -u github.com/gofiber/fiber/v2/middleware/...
  1// run
  2package main
  3
  4import (
  5	"log"
  6	"time"
  7
  8	"github.com/gofiber/fiber/v2"
  9	"github.com/gofiber/fiber/v2/middleware/logger"
 10	"github.com/gofiber/fiber/v2/middleware/recover"
 11	"github.com/gofiber/fiber/v2/middleware/cors"
 12	"github.com/gofiber/fiber/v2/middleware/limiter"
 13)
 14
 15// Product model with validation tags
 16type Product struct {
 17	ID    int     `json:"id"`
 18	Name  string  `json:"name"`
 19	Price float64 `json:"price"`
 20	Stock int     `json:"stock"`
 21}
 22
 23// In-memory data store for demonstration
 24var products = []Product{
 25	{ID: 1, Name: "Laptop", Price: 999.99, Stock: 50},
 26	{ID: 2, Name: "Mouse", Price: 29.99, Stock: 200},
 27}
 28
 29func main() {
 30	// Configure Fiber for maximum performance
 31	app := fiber.New(fiber.Config{
 32		// Production optimizations
 33		AppName:                "Product API v1.0.0",
 34		DisableStartupMessage: true,  // Clean logs
 35		ReadTimeout:            10 * time.Second,
 36		WriteTimeout:           10 * time.Second,
 37		IdleTimeout:            30 * time.Second,
 38
 39		// Performance settings
 40		Prefork:               true,    // Use all CPU cores
 41		ReduceMemoryUsage:      true,    // Optimize memory usage
 42		BodyLimit:             4 * 1024 * 1024, // 4MB limit
 43
 44		// Error handling
 45		ErrorHandler: customErrorHandler,
 46	})
 47
 48	// Add middleware in optimal order
 49	setupMiddleware(app)
 50	setupRoutes(app)
 51
 52	// Start with graceful shutdown
 53	log.Fatal(app.Listen(":8080"))
 54}
 55
 56func setupMiddleware(app *fiber.App) {
 57	// Recovery should be first
 58	app.Use(recover.New(recover.Config{
 59		EnableStackTrace: true,
 60		StackTraceHandler: func(c *fiber.Ctx, e any) {
 61			log.Printf("Panic recovered: %v", e)
 62		},
 63	}))
 64
 65	// Request logging
 66	app.Use(logger.New(logger.Config{
 67		Format: "${time} ${status} ${method} ${path} - ${ip} - ${latency}\n",
 68		TimeFormat: "2006-01-02 15:04:05",
 69		Output:    os.Stdout,
 70	}))
 71
 72	// CORS for frontend integration
 73	app.Use(cors.New(cors.Config{
 74		AllowOrigins:     []string{"https://*", "http://*"},
 75		AllowMethods:     []string{"GET", "POST", "PUT", "DELETE"},
 76		AllowHeaders:     []string{"Origin", "Content-Type", "Accept", "Authorization"},
 77		AllowCredentials: true,
 78	}))
 79
 80	// Rate limiting to prevent abuse
 81	app.Use(limiter.New(limiter.Config{
 82		Max:        1000,                     // 1000 requests per minute
 83		Expiration: 1 * time.Minute,           // Per IP
 84		KeyGenerator: func(c *fiber.Ctx) string {
 85			return c.IP()                    // Rate limit by IP
 86		},
 87		LimitReached: func(c *fiber.Ctx) error {
 88			return c.Status(fiber.StatusTooManyRequests).JSON(fiber.Map{
 89				"error": "Rate limit exceeded",
 90				"retry_after": "60s",
 91			})
 92		},
 93	}))
 94
 95	// Custom metrics middleware
 96	app.Use(metricsMiddleware)
 97}
 98
 99func setupRoutes(app *fiber.App) {
100	// Health check
101	app.Get("/health", healthCheck)
102
103	// API versioning
104	api := app.Group("/api/v1")
105
106	// Product routes
107	api.Get("/products", getProducts)
108	api.Get("/products/:id", getProduct)
109	api.Post("/products", createProduct)
110	api.Put("/products/:id", updateProduct)
111	api.Delete("/products/:id", deleteProduct)
112
113	// File upload route
114	api.Post("/products/:id/image", uploadProductImage)
115}
116
117// Handlers with comprehensive error handling
118func getProducts(c *fiber.Ctx) error {
119	// Query parameters with defaults
120	page := c.QueryInt("page", 1)
121	limit := c.QueryInt("limit", 10)
122	search := c.Query("search")
123
124	// Apply search filter if provided
125	if search != "" {
126		return c.JSON(filterProducts(search, page, limit))
127	}
128
129	// Paginate results
130	start := * limit
131	end := start + limit
132	if end > len(products) {
133		end = len(products)
134	}
135	if start > len(products) {
136		return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
137			"error": "Page not found",
138		})
139	}
140
141	paginated := products[start:end]
142
143	return c.JSON(fiber.Map{
144		"products": paginated,
145		"page": page,
146		"limit": limit,
147		"total": len(products),
148		"pages": + limit - 1) / limit,
149	})
150}
151
152func getProduct(c *fiber.Ctx) error {
153	id := c.ParamsInt("id")
154
155	// Validate ID
156	if id <= 0 {
157		return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
158			"error": "Invalid product ID",
159		})
160	}
161
162	for _, product := range products {
163		if product.ID == id {
164			return c.JSON(product)
165		}
166	}
167
168	return c.Status(fiber.StatusNotFound).JSON(fiber.Map{
169		"error": "Product not found",
170	})
171}
172
173func createProduct(c *fiber.Ctx) error {
174	var product Product
175
176	// Parse and validate request body
177	if err := c.BodyParser(&product); err != nil {
178		return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
179			"error": "Invalid request body: " + err.Error(),
180		})
181	}
182
183	// Validate business rules
184	if product.Name == "" {
185		return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
186			"error": "Product name is required",
187		})
188	}
189
190	if product.Price <= 0 {
191		return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
192			"error": "Product price must be positive",
193		})
194	}
195
196	if product.Stock < 0 {
197		return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
198			"error": "Product stock cannot be negative",
199		})
200	}
201
202	// Generate new ID
203	product.ID = len(products) + 1
204	products = append(products, product)
205
206	// Log creation
207	log.Printf("Created product: %+v", product)
208
209	return c.Status(fiber.StatusCreated).JSON(product)
210}
211
212func updateProduct(c *fiber.Ctx) error {
213	id := c.ParamsInt("id")
214
215	var updates Product
216	if err := c.BodyParser(&updates); err != nil {
217		return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
218			"error": "Invalid request body: " + err.Error(),
219		})
220	}
221
222	// Find and update product
223	for i, product := range products {
224		if product.ID == id {
225			// Update fields
226			if updates.Name != "" {
227				products[i].Name = updates.Name
228			}
229			if updates.Price > 0 {
230				products[i].Price = updates.Price
231			}
232			if updates.Stock >= 0 {
233				products[i].Stock = updates.Stock
234			}
235
236			return c.JSON(products[i])
237		}
238	}
239
240	return c.Status(fiber.StatusNotFound).JSON(fiber.Map{
241		"error": "Product not found",
242	})
243}
244
245func deleteProduct(c *fiber.Ctx) error {
246	id := c.ParamsInt("id")
247
248	// Find and remove product
249	for i, product := range products {
250		if product.ID == id {
251			products = append(products[:i], products[i+1:]...)
252			return c.SendStatus(fiber.StatusNoContent)
253		}
254	}
255
256	return c.Status(fiber.StatusNotFound).JSON(fiber.Map{
257		"error": "Product not found",
258	})
259}
260
261func uploadProductImage(c *fiber.Ctx) error {
262	id := c.ParamsInt("id")
263
264	// Validate product exists
265	productExists := false
266	for _, product := range products {
267		if product.ID == id {
268			productExists = true
269			break
270		}
271	}
272
273	if !productExists {
274		return c.Status(fiber.StatusNotFound).JSON(fiber.Map{
275			"error": "Product not found",
276		})
277	}
278
279	// Handle file upload
280	file, err := c.FormFile("image")
281	if err != nil {
282		return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
283			"error": "File upload failed: " + err.Error(),
284		})
285	}
286
287	// Validate file
288	allowedTypes := []string{"image/jpeg", "image/png", "image/gif"}
289	contentType := file.Header.Get("Content-Type")
290
291	validType := false
292	for _, allowedType := range allowedTypes {
293		if contentType == allowedType {
294			validType = true
295			break
296		}
297	}
298
299	if !validType {
300		return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
301			"error": "Invalid file type. Only JPEG, PNG, and GIF are allowed",
302		})
303	}
304
305	// Check file size
306	if file.Size > 5*1024*1024 {
307		return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
308			"error": "File too large. Maximum size is 5MB",
309		})
310	}
311
312	// Save file
313	filename := fmt.Sprintf("product_%d_%s", id, file.Filename)
314	dst := fmt.Sprintf("./uploads/%s", filename)
315
316	if err := c.SaveFile(file, dst); err != nil {
317		return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
318			"error": "Failed to save file: " + err.Error(),
319		})
320	}
321
322	log.Printf("Saved uploaded file: %s", dst)
323
324	return c.JSON(fiber.Map{
325		"message":  "File uploaded successfully",
326		"filename":  filename,
327		"size":     file.Size,
328	})
329}
330
331// Utility functions
332func healthCheck(c *fiber.Ctx) error {
333	return c.JSON(fiber.Map{
334		"status":    "healthy",
335		"timestamp": time.Now(),
336		"version":   "1.0.0",
337		"framework": "Fiber",
338	})
339}
340
341func customErrorHandler(c *fiber.Ctx, err error) error {
342	// Log error for debugging
343	log.Printf("Error: %v", err)
344
345	// Don't send response if already sent
346	if c.Response().StatusCode() != fiber.StatusOK {
347		return nil
348	}
349
350	// Return JSON error response
351	code := fiber.StatusInternalServerError
352	if e, ok := err.(*fiber.Error); ok {
353		code = e.Code
354	}
355
356	return c.Status(code).JSON(fiber.Map{
357		"error": err.Error(),
358		"code":  code,
359	})
360}
361
362func metricsMiddleware(c *fiber.Ctx) error {
363	start := time.Now()
364
365	// Process request
366	err := c.Next()
367
368	// Record metrics
369	duration := time.Since(start)
370
371	log.Printf("Request: %s %s - Status: %d - Duration: %v",
372		c.Method(), c.Path(), c.Response().StatusCode(), duration)
373
374	return err
375}
376
377func filterProducts(search string, page, limit int) map[string]interface{} {
378	var filtered []Product
379	search = strings.ToLower(search)
380
381	for _, product := range products {
382		if strings.Contains(strings.ToLower(product.Name), search) {
383			filtered = append(filtered, product)
384		}
385	}
386
387	// Apply pagination to filtered results
388	start := * limit
389	end := start + limit
390	if end > len(filtered) {
391		end = len(filtered)
392	}
393	if start > len(filtered) {
394		return map[string]interface{}{
395			"products": []Product{},
396			"page": page,
397			"limit": limit,
398			"total": len(filtered),
399			"pages": 0,
400		}
401	}
402
403	paginated := filtered[start:end]
404
405	return map[string]interface{}{
406		"products": paginated,
407		"page": page,
408		"limit": limit,
409		"total": len(filtered),
410		"pages": + limit - 1) / limit,
411	}
412}

What makes this Fiber example production-ready:

  1. Performance Configuration: Prefork mode, memory optimization, and proper timeouts
  2. Comprehensive Middleware: Recovery, logging, CORS, rate limiting, and custom metrics
  3. Error Handling: Custom error handler with proper HTTP status codes
  4. Input Validation: Parameter validation with meaningful error messages
  5. File Upload Handling: Secure file upload with type and size validation
  6. Business Logic: Real-world validation rules and data management
  7. Logging & Monitoring: Request tracking and performance metrics

Advanced Fiber Features

Let's explore Fiber's advanced capabilities that make it ideal for high-performance applications.

  1// run
  2package main
  3
  4import (
  5	"context"
  6	"fmt"
  7	"time"
  8
  9	"github.com/gofiber/fiber/v2"
 10	"github.com/gofiber/fiber/v2/middleware/compress"
 11	"github.com/gofiber/fiber/v2/middleware/cache"
 12	"github.com/gofiber/fiber/v2/middleware/encryptcookie"
 13	"github.com/gofiber/websocket/v2"
 14)
 15
 16func main() {
 17	app := fiber.New(fiber.Config{
 18		Prefork:               true,
 19		CaseSensitive:         true,
 20		StrictRouting:         true,
 21		DisableStartupMessage:  true,
 22		ServerHeader:          "Fiber-Server",
 23		AppName:               "Advanced API v2.0.0",
 24	})
 25
 26	// Advanced middleware setup
 27	setupAdvancedMiddleware(app)
 28	setupWebSocketSupport(app)
 29	setupCaching(app)
 30
 31	app.Listen(":8080")
 32}
 33
 34func setupAdvancedMiddleware(app *fiber.App) {
 35	// Smart compression based on content type
 36	app.Use(compress.New(compress.Config{
 37		Level: compress.LevelBestSpeed,
 38		Next: func(c *fiber.Ctx) bool {
 39			// Skip compression for already compressed content
 40			contentType := c.Get("Content-Type")
 41			compressibleTypes := map[string]bool{
 42				"application/json": true,
 43				"text/html":        true,
 44				"text/css":         true,
 45				"text/javascript":  true,
 46			}
 47			return !compressibleTypes[contentType]
 48		},
 49	}))
 50
 51	// Intelligent caching with conditional requests
 52	app.Use(cache.New(cache.Config{
 53		Expiration:   5 * time.Minute,
 54		CacheControl: true,
 55		Next: func(c *fiber.Ctx) bool {
 56			// Skip cache for authenticated requests
 57			return c.Get("Authorization") != ""
 58		},
 59	}))
 60
 61	// Cookie encryption for security
 62	app.Use(encryptcookie.New(encryptcookie.Config{
 63		Key: "your-secret-key-32-bytes-long-please-change",
 64	}))
 65
 66	// Request timeout with cancellation
 67	app.Use(func(c *fiber.Ctx) error {
 68		ctx, cancel := context.WithTimeout(c.Context(), 10*time.Second)
 69		defer cancel()
 70
 71		c.SetUserContext(ctx)
 72		return c.Next()
 73	})
 74}
 75
 76func setupWebSocketSupport(app *fiber.App) {
 77	// WebSocket middleware
 78	app.Use("/ws", func(c *fiber.Ctx) error {
 79		if websocket.IsWebSocketUpgrade(c) {
 80			c.Locals("allowed", true)
 81			return c.Next()
 82		}
 83		return fiber.ErrUpgradeRequired
 84	})
 85
 86	// WebSocket endpoint with room support
 87	app.Get("/ws/:room", websocket.New(func(c *websocket.Conn) {
 88		roomID := c.Params("room")
 89		userID := c.Query("user_id")
 90
 91		// Join room
 92		fmt.Printf("User %s joined room %s\n", userID, roomID)
 93
 94		// Handle messages
 95		for {
 96			msgType, msg, err := c.ReadMessage()
 97			if err != nil {
 98				break
 99			}
100
101			// Broadcast to room
102			broadcastMessage(roomID, userID, string(msg))
103		}
104	}))
105}
106
107func setupCaching(app *fiber.App) {
108	// In-memory cache for frequently accessed data
109	cache := NewMemoryCache()
110
111	app.Get("/api/cached-products", func(c *fiber.Ctx) error {
112		cacheKey := "products:v1"
113
114		// Try cache first
115		if cached, found := cache.Get(cacheKey); found {
116			c.Set("X-Cache", "HIT")
117			return c.JSON(cached)
118		}
119
120		// Cache miss - generate data
121		products := generateExpensiveProducts()
122
123		// Store in cache
124		cache.Set(cacheKey, products, 5*time.Minute)
125
126		c.Set("X-Cache", "MISS")
127		return c.JSON(products)
128	})
129}
130
131// Simple in-memory cache implementation
132type MemoryCache struct {
133	data map[string]cacheItem
134	mu   sync.RWMutex
135}
136
137type cacheItem struct {
138	value      interface{}
139	expiresAt  time.Time
140}
141
142func NewMemoryCache() *MemoryCache {
143	cache := &MemoryCache{
144		data: make(map[string]cacheItem),
145	}
146
147	// Start cleanup goroutine
148	go cache.cleanup()
149
150	return cache
151}
152
153func Get(key string) {
154	c.mu.RLock()
155	defer c.mu.RUnlock()
156
157	item, exists := c.data[key]
158	if !exists {
159		return nil, false
160	}
161
162	if time.Now().After(item.expiresAt) {
163		delete(c.data, key)
164		return nil, false
165	}
166
167	return item.value, true
168}
169
170func Set(key string, value interface{}, ttl time.Duration) {
171	c.mu.Lock()
172	defer c.mu.Unlock()
173
174	c.data[key] = cacheItem{
175		value:     value,
176		expiresAt: time.Now().Add(ttl),
177	}
178}
179
180func cleanup() {
181	ticker := time.NewTicker(time.Minute)
182	defer ticker.Stop()
183
184	for range ticker.C {
185		c.mu.Lock()
186		now := time.Now()
187		for key, item := range c.data {
188			if now.After(item.expiresAt) {
189				delete(c.data, key)
190			}
191		}
192		c.mu.Unlock()
193	}
194}
195
196func generateExpensiveProducts() []Product {
197	// Simulate expensive operation
198	time.Sleep(100 * time.Millisecond)
199	return products
200}
201
202func broadcastMessage(roomID, userID, message string) {
203	fmt.Printf("[Room %s] %s: %s\n", roomID, userID, message)
204}

Advanced Features Demonstrated:

  1. Smart Compression: Content-aware compression that skips already compressed files
  2. Intelligent Caching: In-memory cache with TTL and cleanup
  3. WebSocket Support: Real-time communication with room management
  4. Security Headers: Cookie encryption and timeout handling
  5. Performance Monitoring: Cache hit/miss tracking and request timing
  6. Context Cancellation: Proper timeout handling with context propagation

⚠️ Critical Trade-off: Uses fasthttp instead of net/http, meaning:

  • ❌ Incompatible with 99% of Go's HTTP ecosystem
  • ❌ Cannot use standard http.Handler or http.HandlerFunc without adapters
  • ❌ No access to ecosystem tools like Prometheus middleware, OpenTelemetry tracing, etc.
  • ✅ Best for performance-critical microservices with simple, self-contained requirements

Installation

1go get -u github.com/gofiber/fiber/v2
2go get -u github.com/gofiber/fiber/v2/middleware/...

Basic Fiber Application

 1// run
 2package main
 3
 4import (
 5	"github.com/gofiber/fiber/v2"
 6	"github.com/gofiber/fiber/v2/middleware/logger"
 7	"github.com/gofiber/fiber/v2/middleware/recover"
 8	"github.com/gofiber/fiber/v2/middleware/cors"
 9)
10
11func main() {
12	app := fiber.New(fiber.Config{
13		AppName:      "Fiber API v1.0.0",
14		ErrorHandler: customErrorHandler,
15	})
16
17	// Middleware
18	app.Use(logger.New())
19	app.Use(recover.New())
20	app.Use(cors.New())
21
22	// Routes
23	app.Get("/", func(c *fiber.Ctx) error {
24		return c.JSON(fiber.Map{
25			"message": "Hello from Fiber!",
26		})
27	})
28
29	app.Get("/users/:id", func(c *fiber.Ctx) error {
30		id := c.Params("id")
31		return c.JSON(fiber.Map{
32			"id":   id,
33			"name": "John Doe",
34		})
35	})
36
37	// Query parameters
38	app.Get("/search", func(c *fiber.Ctx) error {
39		query := c.Query("q")
40		page := c.QueryInt("page", 1)
41		return c.JSON(fiber.Map{
42			"query": query,
43			"page":  page,
44		})
45	})
46
47	// Route groups
48	api := app.Group("/api/v1")
49	api.Get("/products", getProductsFiber)
50	api.Post("/products", createProductFiber)
51
52	app.Listen(":8080")
53}
54
55type ProductFiber struct {
56	ID    int     `json:"id"`
57	Name  string  `json:"name"`
58	Price float64 `json:"price"`
59}
60
61func getProductsFiber(c *fiber.Ctx) error {
62	products := []ProductFiber{
63		{ID: 1, Name: "Laptop", Price: 999.99},
64		{ID: 2, Name: "Mouse", Price: 29.99},
65	}
66	return c.JSON(products)
67}
68
69func createProductFiber(c *fiber.Ctx) error {
70	product := new(ProductFiber)
71	if err := c.BodyParser(product); err != nil {
72		return err
73	}
74	return c.Status(fiber.StatusCreated).JSON(product)
75}
76
77func customErrorHandler(c *fiber.Ctx, err error) error {
78	code := fiber.StatusInternalServerError
79	if e, ok := err.(*fiber.Error); ok {
80		code = e.Code
81	}
82	return c.Status(code).JSON(fiber.Map{
83		"error": err.Error(),
84	})
85}

What's Happening in the Fiber Example:

  1. fiber.New() creates a new Fiber instance with configuration
  2. fiber.Ctx is Fiber's context that combines request and response functionality
  3. c.Params("id") extracts URL path parameters
  4. c.Query() and c.QueryInt() extract query parameters
  5. c.JSON() automatically sets content-type and encodes JSON response
  6. c.BodyParser() parses request body into structs

Advanced Fiber Features

  1// run
  2package main
  3
  4import (
  5	"fmt"
  6	"time"
  7
  8	"github.com/gofiber/fiber/v2"
  9	"github.com/gofiber/fiber/v2/middleware/compress"
 10	"github.com/gofiber/fiber/v2/middleware/limiter"
 11	"github.com/gofiber/fiber/v2/middleware/timeout"
 12	"github.com/gofiber/fiber/v2/middleware/cache"
 13	"github.com/gofiber/fiber/v2/middleware/helmet"
 14)
 15
 16func main() {
 17	app := fiber.New(fiber.Config{
 18		Prefork:       true, // Enable prefork for production
 19		CaseSensitive: true,
 20		StrictRouting: true,
 21		ServerHeader:  "Fiber",
 22		AppName:       "Production API v1.0.0",
 23	})
 24
 25	// Security middleware
 26	app.Use(helmet.New())
 27
 28	// Compression
 29	app.Use(compress.New(compress.Config{
 30		Level: compress.LevelBestSpeed,
 31	}))
 32
 33	// Rate limiting
 34	app.Use(limiter.New(limiter.Config{
 35		Max:        100,
 36		Expiration: 1 * time.Minute,
 37		LimitReached: func(c *fiber.Ctx) error {
 38			return c.Status(fiber.StatusTooManyRequests).JSON(fiber.Map{
 39				"error": "Rate limit exceeded",
 40			})
 41		},
 42	}))
 43
 44	// Cache middleware
 45	app.Use(cache.New(cache.Config{
 46		Next: func(c *fiber.Ctx) bool {
 47			return c.Query("noCache") == "true"
 48		},
 49		Expiration:   10 * time.Minute,
 50		CacheControl: true,
 51	}))
 52
 53	// Custom middleware
 54	app.Use(requestIDMiddleware)
 55	app.Use(metricsMiddleware)
 56
 57	// Routes with timeout
 58	app.Get("/api/slow", timeout.New(func(c *fiber.Ctx) error {
 59		time.Sleep(2 * time.Second)
 60		return c.JSON(fiber.Map{"message": "Slow endpoint"})
 61	}, 5*time.Second))
 62
 63	// Validation
 64	app.Post("/api/users", createUserFiber)
 65
 66	// File upload
 67	app.Post("/api/upload", uploadFileFiber)
 68
 69	// WebSocket
 70	app.Get("/ws", websocketHandler)
 71
 72	app.Listen(":8080")
 73}
 74
 75// Request ID middleware
 76func requestIDMiddleware(c *fiber.Ctx) error {
 77	id := c.Get("X-Request-ID")
 78	if id == "" {
 79		id = fmt.Sprintf("%d", time.Now().UnixNano())
 80	}
 81	c.Set("X-Request-ID", id)
 82	c.Locals("requestID", id)
 83	return c.Next()
 84}
 85
 86// Metrics middleware
 87func metricsMiddleware(c *fiber.Ctx) error {
 88	start := time.Now()
 89
 90	err := c.Next()
 91
 92	duration := time.Since(start)
 93	fmt.Printf("[METRICS] %s %s - %v - %d\n",
 94		c.Method(),
 95		c.Path(),
 96		duration,
 97		c.Response().StatusCode(),
 98	)
 99
100	return err
101}
102
103type UserFiber struct {
104	Name     string `json:"name" validate:"required,min=3"`
105	Email    string `json:"email" validate:"required,email"`
106	Age      int    `json:"age" validate:"gte=18"`
107	Password string `json:"password" validate:"required,min=8"`
108}
109
110func createUserFiber(c *fiber.Ctx) error {
111	user := new(UserFiber)
112	if err := c.BodyParser(user); err != nil {
113		return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
114			"error": "Invalid request body",
115		})
116	}
117
118	// Validation would go here
119	// Using github.com/go-playground/validator/v10
120
121	return c.Status(fiber.StatusCreated).JSON(fiber.Map{
122		"message": "User created",
123		"user":    user,
124	})
125}
126
127func uploadFileFiber(c *fiber.Ctx) error {
128	file, err := c.FormFile("file")
129	if err != nil {
130		return err
131	}
132
133	// Validate file
134	if file.Size > 10*1024*1024 {
135		return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
136			"error": "File too large",
137		})
138	}
139
140	// Save file
141	dst := "./uploads/" + file.Filename
142	if err := c.SaveFile(file, dst); err != nil {
143		return err
144	}
145
146	return c.JSON(fiber.Map{
147		"filename": file.Filename,
148		"size":     file.Size,
149		"path":     dst,
150	})
151}
152
153func websocketHandler(c *fiber.Ctx) error {
154	// Fiber WebSocket implementation
155	return c.SendString("WebSocket endpoint")
156}

Fiber WebSocket Support

  1// run
  2package main
  3
  4import (
  5	"fmt"
  6	"log"
  7	"time"
  8
  9	"github.com/gofiber/fiber/v2"
 10	"github.com/gofiber/websocket/v2"
 11)
 12
 13func main() {
 14	app := fiber.New()
 15
 16	// WebSocket middleware
 17	app.Use("/ws", func(c *fiber.Ctx) error {
 18		// IsWebSocketUpgrade returns true if the client
 19		// requested upgrade to the WebSocket protocol
 20		if websocket.IsWebSocketUpgrade(c) {
 21			c.Locals("allowed", true)
 22			return c.Next()
 23		}
 24		return fiber.ErrUpgradeRequired
 25	})
 26
 27	// WebSocket endpoint
 28	app.Get("/ws", websocket.New(func(c *websocket.Conn) {
 29		for {
 30			// Read message from client
 31			msgType, msg, err := c.ReadMessage()
 32			if err != nil {
 33				log.Println("read:", err)
 34				break
 35			}
 36
 37			log.Printf("recv: %s", msg)
 38
 39			// Echo message back to client
 40			if err := c.WriteMessage(msgType, msg); err != nil {
 41				log.Println("write:", err)
 42				break
 43			}
 44		}
 45	}))
 46
 47	// Chat room WebSocket
 48	app.Get("/chat", websocket.New(handleChat))
 49
 50	log.Fatal(app.Listen(":3000"))
 51}
 52
 53type Client struct {
 54	Conn   *websocket.Conn
 55	Send   chan []byte
 56	RoomID string
 57}
 58
 59type Hub struct {
 60	clients    map[*Client]bool
 61	broadcast  chan []byte
 62	register   chan *Client
 63	unregister chan *Client
 64	rooms      map[string]map[*Client]bool
 65}
 66
 67var hub = &Hub{
 68	clients:    make(map[*Client]bool),
 69	broadcast:  make(chan []byte),
 70	register:   make(chan *Client),
 71	unregister: make(chan *Client),
 72	rooms:      make(map[string]map[*Client]bool),
 73}
 74
 75func Run() {
 76	for {
 77		select {
 78		case client := <-h.register:
 79			if h.rooms[client.RoomID] == nil {
 80				h.rooms[client.RoomID] = make(map[*Client]bool)
 81			}
 82			h.rooms[client.RoomID][client] = true
 83			h.clients[client] = true
 84
 85		case client := <-h.unregister:
 86			if _, ok := h.clients[client]; ok {
 87				delete(h.clients, client)
 88				delete(h.rooms[client.RoomID], client)
 89				close(client.Send)
 90			}
 91
 92		case message := <-h.broadcast:
 93			for client := range h.clients {
 94				select {
 95				case client.Send <- message:
 96				default:
 97					close(client.Send)
 98					delete(h.clients, client)
 99					delete(h.rooms[client.RoomID], client)
100				}
101			}
102		}
103	}
104}
105
106func handleChat(c *websocket.Conn) {
107	roomID := c.Query("room")
108	if roomID == "" {
109		roomID = "default"
110	}
111
112	client := &Client{
113		Conn:   c,
114		Send:   make(chan []byte, 256),
115		RoomID: roomID,
116	}
117
118	hub.register <- client
119
120	// Allow collection of memory referenced by the caller
121	go func() {
122		defer func() {
123			hub.unregister <- client
124			c.Close()
125		}()
126
127		for {
128			_, message, err := c.ReadMessage()
129			if err != nil {
130				if websocket.IsUnexpectedCloseError(err, websocket.CloseGoingAway, websocket.CloseAbnormalClosure) {
131					log.Printf("error: %v", err)
132				}
133				break
134			}
135
136			msg := fmt.Sprintf("[%s] %s", roomID, string(message))
137			hub.broadcast <- []byte(msg)
138		}
139	}()
140
141	// Write messages to client
142	for {
143		select {
144		case message, ok := <-client.Send:
145			if !ok {
146				c.WriteMessage(websocket.CloseMessage, []byte{})
147				return
148			}
149
150			if err := c.WriteMessage(websocket.TextMessage, message); err != nil {
151				return
152			}
153		}
154	}
155}
156
157func init() {
158	go hub.Run()
159}

Fiber Performance Optimization

  1// run
  2package main
  3
  4import (
  5	"encoding/json"
  6	"runtime"
  7	"sync"
  8	"time"
  9
 10	"github.com/gofiber/fiber/v2"
 11	"github.com/gofiber/fiber/v2/middleware/compress"
 12	"github.com/gofiber/fiber/v2/middleware/encryptcookie"
 13	"github.com/valyala/fasthttp"
 14)
 15
 16// Performance configuration
 17func setupHighPerformanceFiber() *fiber.App {
 18	app := fiber.New(fiber.Config{
 19		// Performance optimizations
 20		Prefork:           true,               // Use multiple processes
 21		ReduceMemoryUsage: true,               // Reduce memory usage
 22		UnescapePathValues: true,              // Unescape path values
 23		BodyLimit:         4 * 1024 * 1024,   // 4MB limit
 24		ReadTimeout:       5 * time.Second,   // Read timeout
 25		WriteTimeout:      10 * time.Second,  // Write timeout
 26		IdleTimeout:       30 * time.Second,  // Idle timeout
 27
 28		// FastHTTP specific optimizations
 29		DisableStartupMessage: true,
 30		StreamRequestBody:      true,
 31		Network:                fiber.TCP4,
 32
 33		// Concurrency settings
 34		Concurrency: runtime.NumCPU() * 1024,
 35	})
 36
 37	// Compression with optimization
 38	app.Use(compress.New(compress.Config{
 39		Level: compress.LevelBestSpeed, // Fastest compression
 40		Next: func(c *fiber.Ctx) bool {
 41			// Skip compression for already compressed content
 42			contentType := c.Get("Content-Type")
 43			return contentType == "application/gzip" ||
 44				   contentType == "application/zip"
 45		},
 46	}))
 47
 48	// Cookie encryption for security
 49	app.Use(encryptcookie.New(encryptcookie.Config{
 50		Key: "your-secret-key-32-bytes-long",
 51	}))
 52
 53	// Optimized JSON middleware
 54	app.Use(func(c *fiber.Ctx) error {
 55		c.Set("X-Content-Type-Options", "nosniff")
 56		c.Set("X-Frame-Options", "DENY")
 57		c.Set("X-XSS-Protection", "1; mode=block")
 58		return c.Next()
 59	})
 60
 61	return app
 62}
 63
 64// In-memory cache for performance
 65type Cache struct {
 66	data map[string][]byte
 67	mu   sync.RWMutex
 68	ttl  map[string]time.Time
 69}
 70
 71func NewCache() *Cache {
 72	go func() {
 73		for {
 74			time.Sleep(time.Minute)
 75			cleanup()
 76		}
 77	}()
 78	return &Cache{
 79		data: make(map[string][]byte),
 80		ttl:  make(map[string]time.Time),
 81	}
 82}
 83
 84func Set(key string, value []byte, ttl time.Duration) {
 85	c.mu.Lock()
 86	defer c.mu.Unlock()
 87	c.data[key] = value
 88	c.ttl[key] = time.Now().Add(ttl)
 89}
 90
 91func Get(key string) {
 92	c.mu.RLock()
 93	defer c.mu.RUnlock()
 94
 95	if expires, exists := c.ttl[key]; exists && time.Now().Before(expires) {
 96		return c.data[key], true
 97	}
 98	return nil, false
 99}
100
101func cleanup() {
102	// Clean up expired entries
103}
104
105var cache = NewCache()
106
107func main() {
108	app := setupHighPerformanceFiber()
109
110	// High-performance endpoints
111	app.Get("/api/fast", fastEndpoint)
112	app.Get("/api/cached/:key", cachedEndpoint)
113	app.Post("/api/bulk", bulkProcess)
114
115	app.Listen(":8080")
116}
117
118func fastEndpoint(c *fiber.Ctx) error {
119	// Zero-allocation response
120	return c.SendString("OK")
121}
122
123func cachedEndpoint(c *fiber.Ctx) error {
124	key := c.Params("key")
125
126	if data, exists := cache.Get(key); exists {
127		return c.Type("json").Send(data)
128	}
129
130	// Simulate expensive operation
131	time.Sleep(100 * time.Millisecond)
132
133	response := map[string]interface{}{
134		"key":   key,
135		"value": "cached data",
136		"time":  time.Now(),
137	}
138
139	data, _ := json.Marshal(response)
140	cache.Set(key, data, 5*time.Minute)
141
142	return c.JSON(response)
143}
144
145func bulkProcess(c *fiber.Ctx) error {
146	var requests []map[string]interface{}
147	if err := json.Unmarshal(c.Body(), &requests); err != nil {
148		return err
149	}
150
151	responses := make([]map[string]interface{}, len(requests))
152	var wg sync.WaitGroup
153	var mu sync.Mutex
154
155	for i, req := range requests {
156		wg.Add(1)
157		go func(index int, request map[string]interface{}) {
158			defer wg.Done()
159
160			// Process request
161			time.Sleep(10 * time.Millisecond) // Simulate work
162
163			response := map[string]interface{}{
164				"request_id": request["id"],
165				"result":     "processed",
166				"timestamp":  time.Now(),
167			}
168
169			mu.Lock()
170			responses[index] = response
171			mu.Unlock()
172		}(i, req)
173	}
174
175	wg.Wait()
176
177	return c.JSON(responses)
178}

Chi Framework

Chi is like a perfectly engineered toolbox that works with every standard tool and workshop out there. It's built entirely on Go's standard library, making it completely compatible with the entire Go ecosystem while still providing the routing and middleware features developers need.

Real-world Chi Success Story:

IBM chose Chi for their internal microservices platform because they needed to integrate with extensive existing infrastructure including Prometheus monitoring, OpenTelemetry tracing, and custom authentication middleware. Chi's net/http compatibility meant they could reuse all their existing middleware libraries and integrate seamlessly with their deployment pipelines.

Why Chi:

  • 100% net/http Compatible: Uses standard http.Handler, http.HandlerFunc, http.ResponseWriter—no custom contexts, no adapter layers
  • Zero External Dependencies: Pure Go stdlib-based implementation means smaller binaries and fewer security vulnerabilities
  • Gradual Adoption: Can mix Chi routing with existing net/http code, making it perfect for incremental migrations
  • Context-Based Routing: Stores URL parameters in request context using standard Go patterns
  • Full Ecosystem Access: Works with ANY net/http middleware

Why Choose Chi:

  • ✅ Building long-lived applications where ecosystem compatibility matters more than raw performance
  • ✅ Team values idiomatic Go and standard library conventions over framework-specific abstractions
  • ✅ Need to integrate with existing net/http infrastructure or migrate legacy systems
  • ✅ Want access to the complete Go ecosystem of middleware, monitoring, and testing tools
  • ❌ Less convenient than Gin/Echo but more maintainable long-term

💡 Key Takeaway: Chi for Enterprise Success

Chi's philosophy of "compatibility over convenience" makes it ideal for enterprise environments where long-term maintainability, security compliance, and ecosystem integration matter more than development speed or benchmark performance.

Installation

1go get -u github.com/go-chi/chi/v5
2go get -u github.com/go-chi/chi/v5/middleware
3go get -u github.com/go-chi/cors
4go get -u github.com/go-chi/render

Basic Chi Application

  1// run
  2package main
  3
  4import (
  5	"encoding/json"
  6	"net/http"
  7
  8	"github.com/go-chi/chi/v5"
  9	"github.com/go-chi/chi/v5/middleware"
 10)
 11
 12func main() {
 13	r := chi.NewRouter()
 14
 15	// Middleware
 16	r.Use(middleware.RequestID)
 17	r.Use(middleware.RealIP)
 18	r.Use(middleware.Logger)
 19	r.Use(middleware.Recoverer)
 20
 21	// Routes
 22	r.Get("/", func(w http.ResponseWriter, r *http.Request) {
 23		json.NewEncoder(w).Encode(map[string]string{
 24			"message": "Hello from Chi!",
 25		})
 26	})
 27
 28	r.Get("/users/{id}", func(w http.ResponseWriter, r *http.Request) {
 29		id := chi.URLParam(r, "id")
 30		json.NewEncoder(w).Encode(map[string]string{
 31			"id":   id,
 32			"name": "John Doe",
 33		})
 34	})
 35
 36	// Route groups
 37	r.Route("/api/v1", func(r chi.Router) {
 38		r.Get("/products", getProductsChi)
 39		r.Post("/products", createProductChi)
 40		r.Put("/products/{id}", updateProductChi)
 41		r.Delete("/products/{id}", deleteProductChi)
 42	})
 43
 44	// Subrouter with middleware
 45	r.Group(func(r chi.Router) {
 46		r.Use(authMiddleware)
 47		r.Get("/admin/dashboard", adminDashboard)
 48	})
 49
 50	http.ListenAndServe(":8080", r)
 51}
 52
 53type ProductChi struct {
 54	ID    string  `json:"id"`
 55	Name  string  `json:"name"`
 56	Price float64 `json:"price"`
 57}
 58
 59func getProductsChi(w http.ResponseWriter, r *http.Request) {
 60	products := []ProductChi{
 61		{ID: "1", Name: "Laptop", Price: 999.99},
 62		{ID: "2", Name: "Mouse", Price: 29.99},
 63	}
 64	w.Header().Set("Content-Type", "application/json")
 65	json.NewEncoder(w).Encode(products)
 66}
 67
 68func createProductChi(w http.ResponseWriter, r *http.Request) {
 69	var product ProductChi
 70	if err := json.NewDecoder(r.Body).Decode(&product); err != nil {
 71		http.Error(w, err.Error(), http.StatusBadRequest)
 72		return
 73	}
 74	w.Header().Set("Content-Type", "application/json")
 75	w.WriteHeader(http.StatusCreated)
 76	json.NewEncoder(w).Encode(product)
 77}
 78
 79func updateProductChi(w http.ResponseWriter, r *http.Request) {
 80	id := chi.URLParam(r, "id")
 81	var product ProductChi
 82	if err := json.NewDecoder(r.Body).Decode(&product); err != nil {
 83		http.Error(w, err.Error(), http.StatusBadRequest)
 84		return
 85	}
 86	product.ID = id
 87	w.Header().Set("Content-Type", "application/json")
 88	json.NewEncoder(w).Encode(product)
 89}
 90
 91func deleteProductChi(w http.ResponseWriter, r *http.Request) {
 92	id := chi.URLParam(r, "id")
 93	w.Header().Set("Content-Type", "application/json")
 94	json.NewEncoder(w).Encode(map[string]string{
 95		"message": "Product deleted",
 96		"id":      id,
 97	})
 98}
 99
100func authMiddleware(next http.Handler) http.Handler {
101	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
102		token := r.Header.Get("Authorization")
103		if token == "" {
104			http.Error(w, "Unauthorized", http.StatusUnauthorized)
105			return
106		}
107		next.ServeHTTP(w, r)
108	})
109}
110
111func adminDashboard(w http.ResponseWriter, r *http.Request) {
112	json.NewEncoder(w).Encode(map[string]string{
113		"message": "Admin dashboard",
114	})
115}

What's Happening in the Chi Example:

  1. chi.NewRouter() creates a new router that implements http.Handler
  2. r.Use() adds standard net/http middleware
  3. chi.URLParam(r, "id") extracts URL parameters stored in request context
  4. Route Groups`) create sub-routers with shared middleware
  5. Standard Response Writing: Uses http.ResponseWriter directly like standard Go HTTP

Advanced Chi Features

  1// run
  2package main
  3
  4import (
  5	"context"
  6	"encoding/json"
  7	"fmt"
  8	"net/http"
  9	"os"
 10	"os/signal"
 11	"strings"
 12	"time"
 13
 14	"github.com/go-chi/chi/v5"
 15	"github.com/go-chi/chi/v5/middleware"
 16	"github.com/go-chi/cors"
 17	"github.com/go-chi/httprate"
 18	"github.com/go-chi/render"
 19)
 20
 21func main() {
 22	r := chi.NewRouter()
 23
 24	// Standard middleware
 25	r.Use(middleware.RequestID)
 26	r.Use(middleware.RealIP)
 27	r.Use(middleware.Logger)
 28	r.Use(middleware.Recoverer)
 29	r.Use(middleware.Timeout(60 * time.Second))
 30
 31	// CORS
 32	r.Use(cors.Handler(cors.Options{
 33		AllowedOrigins:   []string{"https://*", "http://*"},
 34		AllowedMethods:   []string{"GET", "POST", "PUT", "DELETE", "OPTIONS"},
 35		AllowedHeaders:   []string{"Accept", "Authorization", "Content-Type"},
 36		ExposedHeaders:   []string{"Link"},
 37		AllowCredentials: true,
 38		MaxAge:           300,
 39	}))
 40
 41	// Rate limiting
 42	r.Use(httprate.LimitByIP(100, 1*time.Minute))
 43
 44	// Compression
 45	r.Use(middleware.Compress(5))
 46
 47	// Routes
 48	r.Get("/", homeHandler)
 49	r.Get("/health", healthCheck)
 50
 51	// API routes with versioning
 52	r.Route("/api", func(r chi.Router) {
 53		r.Use(render.SetContentType(render.ContentTypeJSON))
 54
 55		r.Route("/v1", func(r chi.Router) {
 56			r.Mount("/users", usersRouter())
 57			r.Mount("/posts", postsRouter())
 58		})
 59	})
 60
 61	// Static files
 62	fileServer(r, "/static", http.Dir("./public"))
 63
 64	// Graceful shutdown
 65	srv := &http.Server{
 66		Addr:         ":8080",
 67		Handler:      r,
 68		ReadTimeout:  15 * time.Second,
 69		WriteTimeout: 15 * time.Second,
 70		IdleTimeout:  60 * time.Second,
 71	}
 72
 73	go func() {
 74		fmt.Println("Server starting on :8080")
 75		if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
 76			fmt.Printf("Server error: %v\n", err)
 77		}
 78	}()
 79
 80	// Wait for interrupt
 81	c := make(chan os.Signal, 1)
 82	signal.Notify(c, os.Interrupt)
 83	<-c
 84
 85	ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
 86	defer cancel()
 87
 88	srv.Shutdown(ctx)
 89	fmt.Println("Server gracefully stopped")
 90}
 91
 92func homeHandler(w http.ResponseWriter, r *http.Request) {
 93	render.JSON(w, r, map[string]string{
 94		"message": "Chi API",
 95		"version": "1.0.0",
 96	})
 97}
 98
 99func healthCheck(w http.ResponseWriter, r *http.Request) {
100	render.JSON(w, r, map[string]interface{}{
101		"status":    "healthy",
102		"timestamp": time.Now(),
103	})
104}
105
106func usersRouter() http.Handler {
107	r := chi.NewRouter()
108	r.Get("/", listUsers)
109	r.Post("/", createUserChi)
110	r.Route("/{userID}", func(r chi.Router) {
111		r.Use(userCtx)
112		r.Get("/", getUser)
113		r.Put("/", updateUser)
114		r.Delete("/", deleteUser)
115	})
116	return r
117}
118
119func postsRouter() http.Handler {
120	r := chi.NewRouter()
121	r.Get("/", listPosts)
122	r.Post("/", createPost)
123	return r
124}
125
126type User struct {
127	ID    string `json:"id"`
128	Name  string `json:"name"`
129	Email string `json:"email"`
130}
131
132func listUsers(w http.ResponseWriter, r *http.Request) {
133	users := []User{
134		{ID: "1", Name: "Alice", Email: "alice@example.com"},
135		{ID: "2", Name: "Bob", Email: "bob@example.com"},
136	}
137	render.JSON(w, r, users)
138}
139
140func createUserChi(w http.ResponseWriter, r *http.Request) {
141	var user User
142	if err := json.NewDecoder(r.Body).Decode(&user); err != nil {
143		render.Status(r, http.StatusBadRequest)
144		render.JSON(w, r, map[string]string{"error": err.Error()})
145		return
146	}
147	user.ID = "new-id"
148	render.Status(r, http.StatusCreated)
149	render.JSON(w, r, user)
150}
151
152func userCtx(next http.Handler) http.Handler {
153	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
154		userID := chi.URLParam(r, "userID")
155		// Load user from database
156		user := &User{ID: userID, Name: "John", Email: "john@example.com"}
157		ctx := context.WithValue(r.Context(), "user", user)
158		next.ServeHTTP(w, r.WithContext(ctx))
159	})
160}
161
162func getUser(w http.ResponseWriter, r *http.Request) {
163	user := r.Context().Value("user").(*User)
164	render.JSON(w, r, user)
165}
166
167func updateUser(w http.ResponseWriter, r *http.Request) {
168	user := r.Context().Value("user").(*User)
169	var updates User
170	if err := json.NewDecoder(r.Body).Decode(&updates); err != nil {
171		render.Status(r, http.StatusBadRequest)
172		render.JSON(w, r, map[string]string{"error": err.Error()})
173		return
174	}
175	user.Name = updates.Name
176	user.Email = updates.Email
177	render.JSON(w, r, user)
178}
179
180func deleteUser(w http.ResponseWriter, r *http.Request) {
181	render.Status(r, http.StatusNoContent)
182}
183
184func listPosts(w http.ResponseWriter, r *http.Request) {
185	render.JSON(w, r, []map[string]string{
186		{"id": "1", "title": "First Post"},
187	})
188}
189
190func createPost(w http.ResponseWriter, r *http.Request) {
191	render.Status(r, http.StatusCreated)
192	render.JSON(w, r, map[string]string{"id": "new-post"})
193}
194
195func fileServer(r chi.Router, path string, root http.FileSystem) {
196	if path != "/" && path[len(path)-1] != '/' {
197		r.Get(path, http.RedirectHandler(path+"/", 301).ServeHTTP)
198		path += "/"
199	}
200	path += "*"
201
202	r.Get(path, func(w http.ResponseWriter, r *http.Request) {
203		rctx := chi.RouteContext(r.Context())
204		pathPrefix := strings.TrimSuffix(rctx.RoutePattern(), "/*")
205		fs := http.StripPrefix(pathPrefix, http.FileServer(root))
206		fs.ServeHTTP(w, r)
207	})
208}

Chi Context Management

  1// run
  2package main
  3
  4import (
  5	"context"
  6	"net/http"
  7	"time"
  8
  9	"github.com/go-chi/chi/v5"
 10	"github.com/go-chi/chi/v5/middleware"
 11	"github.com/go-chi/render"
 12)
 13
 14// Custom context keys
 15type contextKey string
 16
 17const (
 18	userKey     contextKey = "user"
 19	requestKey  contextKey = "request"
 20	sessionKey  contextKey = "session"
 21	tenantKey   contextKey = "tenant"
 22)
 23
 24// Request context for storing request metadata
 25type RequestContext struct {
 26	ID        string    `json:"id"`
 27	StartTime time.Time `json:"start_time"`
 28	IP        string    `json:"ip"`
 29	UserAgent string    `json:"user_agent"`
 30	Method    string    `json:"method"`
 31	Path      string    `json:"path"`
 32}
 33
 34// User model
 35type User struct {
 36	ID       string   `json:"id"`
 37	Username string   `json:"username"`
 38	Email    string   `json:"email"`
 39	Roles    []string `json:"roles"`
 40}
 41
 42// Session model
 43type Session struct {
 44	ID        string    `json:"id"`
 45	UserID    string    `json:"user_id"`
 46	CreatedAt time.Time `json:"created_at"`
 47	ExpiresAt time.Time `json:"expires_at"`
 48}
 49
 50// Tenant model
 51type Tenant struct {
 52	ID   string `json:"id"`
 53	Name string `json:"name"`
 54}
 55
 56// Context middleware
 57func contextMiddleware(next http.Handler) http.Handler {
 58	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
 59		// Create request context
 60		reqCtx := &RequestContext{
 61			ID:        middleware.GetReqID(r.Context()),
 62			StartTime: time.Now(),
 63			IP:        middleware.GetRealIP(r),
 64			UserAgent: r.UserAgent(),
 65			Method:    r.Method,
 66			Path:      r.URL.Path,
 67		}
 68
 69		// Store request context
 70		ctx := context.WithValue(r.Context(), requestKey, reqCtx)
 71
 72		// Load user from session
 73		if sessionID := r.Header.Get("X-Session-ID"); sessionID != "" {
 74			session := &Session{
 75				ID:     sessionID,
 76				UserID: "123",
 77			}
 78			ctx = context.WithValue(ctx, sessionKey, session)
 79
 80			// Load user
 81			user := &User{
 82				ID:       "123",
 83				Username: "john_doe",
 84				Email:    "john@example.com",
 85				Roles:    []string{"user", "admin"},
 86			}
 87			ctx = context.WithValue(ctx, userKey, user)
 88		}
 89
 90		// Load tenant
 91		if tenantID := r.Header.Get("X-Tenant-ID"); tenantID != "" {
 92			tenant := &Tenant{
 93				ID:   tenantID,
 94				Name: "Acme Corp",
 95			}
 96			ctx = context.WithValue(ctx, tenantKey, tenant)
 97		}
 98
 99		next.ServeHTTP(w, r.WithContext(ctx))
100	})
101}
102
103// Context helpers
104func getRequestContext(r *http.Request) *RequestContext {
105	if ctx, ok := r.Context().Value(requestKey).(*RequestContext); ok {
106		return ctx
107	}
108	return nil
109}
110
111func getUser(r *http.Request) *User {
112	if user, ok := r.Context().Value(userKey).(*User); ok {
113		return user
114	}
115	return nil
116}
117
118func getSession(r *http.Request) *Session {
119	if session, ok := r.Context().Value(sessionKey).(*Session); ok {
120		return session
121	}
122	return nil
123}
124
125func getTenant(r *http.Request) *Tenant {
126	if tenant, ok := r.Context().Value(tenantKey).(*Tenant); ok {
127		return tenant
128	}
129	return nil
130}
131
132// Middleware for role-based access control
133func requireRole(roles ...string) func(http.Handler) http.Handler {
134	return func(next http.Handler) http.Handler {
135		return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
136			user := getUser(r)
137			if user == nil {
138				render.Status(r, http.StatusUnauthorized)
139				render.JSON(w, r, map[string]string{"error": "unauthorized"})
140				return
141			}
142
143			// Check if user has required role
144			hasRole := false
145			for _, userRole := range user.Roles {
146				for _, requiredRole := range roles {
147					if userRole == requiredRole {
148						hasRole = true
149						break
150					}
151				}
152				if hasRole {
153					break
154				}
155			}
156
157			if !hasRole {
158				render.Status(r, http.StatusForbidden)
159				render.JSON(w, r, map[string]string{"error": "insufficient permissions"})
160				return
161			}
162
163			next.ServeHTTP(w, r)
164		})
165	}
166}
167
168func main() {
169	r := chi.NewRouter()
170	r.Use(middleware.RequestID)
171	r.Use(contextMiddleware)
172
173	// Public routes
174	r.Get("/", func(w http.ResponseWriter, r *http.Request) {
175		reqCtx := getRequestContext(r)
176		render.JSON(w, r, map[string]interface{}{
177			"message":      "Hello from Chi!",
178			"request_id":   reqCtx.ID,
179			"client_ip":    reqCtx.IP,
180			"authenticated": getUser(r) != nil,
181		})
182	})
183
184	// Protected routes
185	r.Route("/api", func(r chi.Router) {
186		r.Use(requireRole("user"))
187
188		r.Get("/profile", func(w http.ResponseWriter, r *http.Request) {
189			user := getUser(r)
190			session := getSession(r)
191			tenant := getTenant(r)
192
193			render.JSON(w, r, map[string]interface{}{
194				"user":    user,
195				"session": session,
196				"tenant":  tenant,
197			})
198		})
199
200		// Admin-only routes
201		r.Route("/admin", func(r chi.Router) {
202			r.Use(requireRole("admin"))
203
204			r.Get("/dashboard", func(w http.ResponseWriter, r *http.Request) {
205				user := getUser(r)
206				render.JSON(w, r, map[string]interface{}{
207					"message": "Admin dashboard",
208					"user":    user.Username,
209				})
210			})
211		})
212	})
213
214	http.ListenAndServe(":8080", r)
215}

Chi Integration with Go Ecosystem

  1// run
  2package main
  3
  4import (
  5	"context"
  6	"net/http"
  7	"time"
  8
  9	"github.com/go-chi/chi/v5"
 10	"github.com/go-chi/chi/v5/middleware"
 11	"github.com/prometheus/client_golang/prometheus"
 12	"github.com/prometheus/client_golang/prometheus/promhttp"
 13	"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
 14	"go.opentelemetry.io/otel"
 15	"go.opentelemetry.io/otel/exporters/stdout/stdouttrace"
 16	"go.opentelemetry.io/otel/sdk/resource"
 17	sdktrace "go.opentelemetry.io/otel/sdk/trace"
 18	semconv "go.opentelemetry.io/otel/semconv/v1.4.0"
 19)
 20
 21// Prometheus metrics
 22var (
 23	httpRequestsTotal = prometheus.NewCounterVec(
 24		prometheus.CounterOpts{
 25			Name: "http_requests_total",
 26			Help: "Total number of HTTP requests",
 27		},
 28		[]string{"method", "path", "status"},
 29	)
 30
 31	httpRequestDuration = prometheus.NewHistogramVec(
 32		prometheus.HistogramOpts{
 33			Name:    "http_request_duration_seconds",
 34			Help:    "HTTP request duration in seconds",
 35			Buckets: prometheus.DefBuckets,
 36		},
 37		[]string{"method", "path", "status"},
 38	)
 39)
 40
 41func init() {
 42	prometheus.MustRegister(httpRequestsTotal)
 43	prometheus.MustRegister(httpRequestDuration)
 44}
 45
 46// Prometheus middleware
 47func prometheusMiddleware(next http.Handler) http.Handler {
 48	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
 49		start := time.Now()
 50
 51		// Wrap response writer to capture status code
 52		wrapped := &responseWriter{ResponseWriter: w, statusCode: http.StatusOK}
 53
 54		next.ServeHTTP(wrapped, r)
 55
 56		duration := time.Since(start).Seconds()
 57
 58		httpRequestsTotal.WithLabelValues(r.Method, r.URL.Path, http.StatusText(wrapped.statusCode)).Inc()
 59		httpRequestDuration.WithLabelValues(r.Method, r.URL.Path, http.StatusText(wrapped.statusCode)).Observe(duration)
 60	})
 61}
 62
 63type responseWriter struct {
 64	http.ResponseWriter
 65	statusCode int
 66}
 67
 68func WriteHeader(code int) {
 69	rw.statusCode = code
 70	rw.ResponseWriter.WriteHeader(code)
 71}
 72
 73// Initialize OpenTelemetry
 74func initTracer() {
 75	exporter, err := stdouttrace.New(stdouttrace.WithPrettyPrint())
 76	if err != nil {
 77		return nil, err
 78	}
 79
 80	tp := sdktrace.NewTracerProvider(
 81		sdktrace.WithBatcher(exporter),
 82		sdktrace.WithResource(resource.NewWithAttributes(
 83			semconv.SchemaURL,
 84			semconv.ServiceNameKey.String("chi-service"),
 85			semconv.ServiceVersionKey.String("1.0.0"),
 86		)),
 87	)
 88
 89	otel.SetTracerProvider(tp)
 90	return tp, nil
 91}
 92
 93func main() {
 94	// Initialize tracing
 95	tp, err := initTracer()
 96	if err != nil {
 97		panic(err)
 98	}
 99	defer func() {
100		if err := tp.Shutdown(context.Background()); err != nil {
101			panic(err)
102		}
103	}()
104
105	r := chi.NewRouter()
106
107	// Standard middleware
108	r.Use(middleware.RequestID)
109	r.Use(middleware.RealIP)
110	r.Use(middleware.Logger)
111	r.Use(middleware.Recoverer)
112
113	// Observability middleware
114	r.Use(prometheusMiddleware)
115
116	// Routes
117	r.Get("/", func(w http.ResponseWriter, r *http.Request) {
118		w.Write([]byte("Hello from Chi with observability!"))
119	})
120
121	// OpenTelemetry-instrumented handler
122	r.Handle("/otel-trace", otelhttp.NewHandler(
123		http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
124			tracer := otel.Tracer("example-tracer")
125			ctx, span := tracer.Start(r.Context(), "example-handler")
126			defer span.End()
127
128			// Simulate work
129			time.Sleep(50 * time.Millisecond)
130
131			w.Write([]byte("OpenTelemetry instrumentation working!"))
132		}),
133		"example-handler",
134	))
135
136	// Prometheus metrics endpoint
137	r.Handle("/metrics", promhttp.Handler())
138
139	// Standard library compatible middleware example
140	r.Use(customLoggingMiddleware)
141
142	// Custom handlers using standard library patterns
143	r.HandleFunc("/std", standardHandler)
144
145	http.ListenAndServe(":8080", r)
146}
147
148// Custom logging middleware using standard library patterns
149func customLoggingMiddleware(next http.Handler) http.Handler {
150	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
151		start := time.Now()
152
153		// Create custom response writer
154		wrapped := &responseWriter{ResponseWriter: w}
155
156		next.ServeHTTP(wrapped, r)
157
158		duration := time.Since(start)
159
160		// Log using standard library
161		println(r.Method, r.URL.Path, wrapped.statusCode, duration.String())
162	})
163}
164
165// Standard library handler function
166func standardHandler(w http.ResponseWriter, r *http.Request) {
167	w.Header().Set("Content-Type", "application/json")
168	w.WriteHeader(http.StatusOK)
169	w.Write([]byte(`{"message": "Standard library handler works!"}`))
170}

Comparative Analysis - Fiber vs Chi

Performance vs. Compatibility Trade-off

  1// run
  2package main
  3
  4import (
  5	"fmt"
  6	"time"
  7)
  8
  9// Framework comparison
 10type FrameworkComparison struct {
 11	Performance    int    // 1-5 scale
 12	Compatibility  int    // 1-5 scale
 13	LearningCurve  int    // 1-5 scale
 14	Ecosystem      int    // 1-5 scale
 15	Maintainability int   // 1-5 scale
 16	ProductionReady bool
 17	UseCase        string
 18	Tradeoffs      []string
 19}
 20
 21func main() {
 22	fiber := FrameworkComparison{
 23		Performance:     5,
 24		Compatibility:   1,
 25		LearningCurve:   2, // Easy for Express.js devs
 26		Ecosystem:       1,
 27		Maintainability: 2,
 28		ProductionReady: true,
 29		UseCase:         "High-performance APIs, gaming, real-time analytics",
 30		Tradeoffs: []string{
 31			"FastHTTP incompatibility",
 32			"Limited middleware ecosystem",
 33			"No standard monitoring tools",
 34			"Difficult integration with existing code",
 35		},
 36	}
 37
 38	chi := FrameworkComparison{
 39		Performance:     3,
 40		Compatibility:   5,
 41		LearningCurve:   4, // Requires Go knowledge
 42		Ecosystem:       5,
 43		Maintainability: 5,
 44		ProductionReady: true,
 45		UseCase:         "Enterprise applications, gradual migrations, ecosystem integration",
 46		Tradeoffs: []string{
 47			"More verbose syntax",
 48			"Manual JSON encoding",
 49			"Slower than specialized frameworks",
 50			"Requires more boilerplate",
 51		},
 52	}
 53
 54	fmt.Println("Framework Comparison: Fiber vs Chi")
 55	fmt.Println("==================================")
 56	fmt.Println()
 57
 58	fmt.Printf("Fiber - %s\n", fiber.UseCase)
 59	fmt.Printf("Performance:     %d/5\n", fiber.Performance)
 60	fmt.Printf("Compatibility:   %d/5\n", fiber.Compatibility)
 61	fmt.Printf("Learning Curve:  %d/5\n", fiber.LearningCurve)
 62	fmt.Printf("Ecosystem:       %d/5\n", fiber.Ecosystem)
 63	fmt.Printf("Maintainability: %d/5\n", fiber.Maintainability)
 64	fmt.Println("Tradeoffs:")
 65	for _, tradeoff := range fiber.Tradeoffs {
 66		fmt.Printf("  - %s\n", tradeoff)
 67	}
 68	fmt.Println()
 69
 70	fmt.Printf("Chi - %s\n", chi.UseCase)
 71	fmt.Printf("Performance:     %d/5\n", chi.Performance)
 72	fmt.Printf("Compatibility:   %d/5\n", chi.Compatibility)
 73	fmt.Printf("Learning Curve:  %d/5\n", chi.LearningCurve)
 74	fmt.Printf("Ecosystem:       %d/5\n", chi.Ecosystem)
 75	fmt.Printf("Maintainability: %d/5\n", chi.Maintainability)
 76	fmt.Println("Tradeoffs:")
 77	for _, tradeoff := range chi.Tradeoffs {
 78		fmt.Printf("  - %s\n", tradeoff)
 79	}
 80	fmt.Println()
 81
 82	// Decision matrix
 83	fmt.Println("Decision Matrix")
 84	fmt.Println("===============")
 85	fmt.Println("Choose Fiber when:")
 86	fmt.Println("✅ Performance is the absolute priority")
 87	fmt.Println("✅ You need Express.js-like syntax")
 88	fmt.Println("✅ Simple, self-contained requirements")
 89	fmt.Println("✅ High-frequency or real-time applications")
 90	fmt.Println()
 91	fmt.Println("Choose Chi when:")
 92	fmt.Println("✅ Ecosystem compatibility is critical")
 93	fmt.Println("✅ Long-term maintainability matters")
 94	fmt.Println("✅ Need to integrate with existing infrastructure")
 95	fmt.Println("✅ Enterprise environment with compliance requirements")
 96	fmt.Println()
 97	fmt.Println("Critical Question: Will you need monitoring, tracing, or third-party middleware?")
 98	fmt.Println("- Yes → Choose Chi")
 99	fmt.Println("- No  → Consider Fiber")
100}

When to Use Each Framework

Choose Fiber when:

  • Performance is critical: Gaming servers, real-time analytics, high-frequency trading
  • Simple requirements: APIs that don't need complex integrations
  • Express.js background: Team familiar with Node.js/Express patterns
  • Microservices architecture: Simple, focused services with specific performance needs
  • Startup environment: Need to iterate quickly with simple features

Choose Chi when:

  • Enterprise applications: Long-term maintenance and compliance requirements
  • Gradual migration: Phasing from standard library to framework
  • Complex integrations: Need Prometheus, OpenTelemetry, custom middleware
  • Team expertise: Developers experienced with Go standard library
  • Regulatory compliance: Industries requiring strict audit trails and monitoring

Production Deployment

Fiber Production Setup

  1// run
  2package main
  3
  4import (
  5	"log"
  6	"os"
  7	"os/signal"
  8	"syscall"
  9	"time"
 10
 11	"github.com/gofiber/fiber/v2"
 12	"github.com/gofiber/fiber/v2/middleware/encryptcookie"
 13	"github.com/gofiber/fiber/v2/middleware/helmet"
 14	"github.com/gofiber/fiber/v2/middleware/limiter"
 15	"github.com/gofiber/fiber/v2/middleware/logger"
 16	"github.com/gofiber/fiber/v2/middleware/recover"
 17	"github.com/gofiber/fiber/v2/middleware/monitor"
 18)
 19
 20func setupProductionFiber() *fiber.App {
 21	app := fiber.New(fiber.Config{
 22		// Production settings
 23		AppName:                "Production API",
 24		DisableStartupMessage: true,
 25		ReadTimeout:            10 * time.Second,
 26		WriteTimeout:           30 * time.Second,
 27		IdleTimeout:            60 * time.Second,
 28		BodyLimit:              10 * 1024 * 1024, // 10MB
 29		Prefork:                true,             // Enable prefork
 30
 31		// Security settings
 32		EnableTrustedProxyCheck: true,
 33		TrustedProxies:          []string{"172.16.0.0/12", "192.168.0.0/16"},
 34	})
 35
 36	// Security middleware
 37	app.Use(helmet.New())
 38	app.Use(encryptcookie.New(encryptcookie.Config{
 39		Key: os.Getenv("COOKIE_KEY"),
 40	}))
 41
 42	// Rate limiting
 43	app.Use(limiter.New(limiter.Config{
 44		Max:        1000,
 45		Expiration: 1 * time.Minute,
 46		KeyGenerator: func(c *fiber.Ctx) string {
 47			return c.IP()
 48		},
 49		LimitReached: func(c *fiber.Ctx) error {
 50			return c.Status(fiber.StatusTooManyRequests).JSON(fiber.Map{
 51				"error": "Rate limit exceeded",
 52			})
 53		},
 54	}))
 55
 56	// Logging
 57	app.Use(logger.New(logger.Config{
 58		Format: "${time} ${status} ${method} ${path} - ${ip} - ${latency}\n",
 59		TimeFormat: "2006-01-02 15:04:05",
 60		Output:     os.Stdout,
 61	}))
 62
 63	app.Use(recover.New(recover.Config{
 64		EnableStackTrace: true,
 65		StackTraceHandler: func(c *fiber.Ctx, e any) {
 66			log.Printf("Panic recovered: %v", e)
 67		},
 68	}))
 69
 70	// Monitoring endpoint
 71	app.Get("/admin/metrics", monitor.New(monitor.Config{
 72		Title:   "API Metrics",
 73		Refresh:  3,
 74		APIOnly: true,
 75		Next: func(c *fiber.Ctx) bool {
 76			return c.Get("Authorization") == os.Getenv("ADMIN_TOKEN")
 77		},
 78	}))
 79
 80	// Application routes
 81	app.Get("/health", healthCheck)
 82	app.Get("/api/status", statusCheck)
 83
 84	return app
 85}
 86
 87func healthCheck(c *fiber.Ctx) error {
 88	return c.JSON(fiber.Map{
 89		"status":    "healthy",
 90		"timestamp": time.Now(),
 91		"version":   "1.0.0",
 92	})
 93}
 94
 95func statusCheck(c *fiber.Ctx) error {
 96	return c.JSON(fiber.Map{
 97		"uptime":    time.Since(time.Now()).String(), // Simplified
 98		"requests":  0,                               // Would track actual count
 99		"memory":    "0MB",                           // Would track actual usage
100		"version":   "1.0.0",
101	})
102}
103
104func main() {
105	app := setupProductionFiber()
106
107	// Graceful shutdown
108	go func() {
109		if err := app.Listen(":8080"); err != nil {
110			log.Printf("Server error: %v", err)
111		}
112	}()
113
114	// Wait for interrupt signal
115	quit := make(chan os.Signal, 1)
116	signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
117	<-quit
118
119	log.Println("Shutting down server...")
120
121	// Graceful shutdown with timeout
122	ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
123	defer cancel()
124
125	if err := app.ShutdownWithContext(ctx); err != nil {
126		log.Printf("Server shutdown error: %v", err)
127	}
128
129	log.Println("Server gracefully stopped")
130}

Chi Production Setup

  1// run
  2package main
  3
  4import (
  5	"context"
  6	"log"
  7	"net/http"
  8	"os"
  9	"os/signal"
 10	"syscall"
 11	"time"
 12
 13	"github.com/go-chi/chi/v5"
 14	"github.com/go-chi/chi/v5/middleware"
 15	"github.com/go-chi/httprate"
 16	"github.com/prometheus/client_golang/prometheus/promhttp"
 17)
 18
 19func setupProductionChi() *chi.Mux {
 20	r := chi.NewRouter()
 21
 22	// Standard middleware
 23	r.Use(middleware.RequestID)
 24	r.Use(middleware.RealIP)
 25	r.Use(middleware.Logger)
 26	r.Use(middleware.Recoverer)
 27	r.Use(middleware.Timeout(60 * time.Second))
 28	r.Use(middleware.AllowContentType("application/json"))
 29	r.Use(middleware.Compress(5, "application/json"))
 30
 31	// Rate limiting by IP
 32	r.Use(httprate.LimitByIP(1000, time.Minute))
 33
 34	// Health check
 35	r.Get("/health", healthHandler)
 36
 37	// Metrics endpoint
 38	r.Group(func(r chi.Router) {
 39		r.Use(func(next http.Handler) http.Handler {
 40			return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
 41				if r.Header.Get("Authorization") != os.Getenv("ADMIN_TOKEN") {
 42					http.Error(w, "Unauthorized", http.StatusUnauthorized)
 43					return
 44				}
 45				next.ServeHTTP(w, r)
 46			})
 47		})
 48		r.Handle("/admin/metrics", promhttp.Handler())
 49	})
 50
 51	// API routes
 52	r.Route("/api", func(r chi.Router) {
 53		r.Use(func(next http.Handler) http.Handler {
 54			return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
 55				w.Header().Set("Content-Type", "application/json")
 56				next.ServeHTTP(w, r)
 57			})
 58		})
 59
 60		r.Get("/status", statusHandler)
 61	})
 62
 63	return r
 64}
 65
 66func healthHandler(w http.ResponseWriter, r *http.Request) {
 67	w.WriteHeader(http.StatusOK)
 68	w.Write([]byte(`{"status":"healthy","timestamp":"` + time.Now().Format(time.RFC3339) + `"}`))
 69}
 70
 71func statusHandler(w http.ResponseWriter, r *http.Request) {
 72	uptime := time.Since(time.Now()) // Simplified
 73	w.Write([]byte(`{
 74		"uptime": "` + uptime.String() + `",
 75		"requests": 0,
 76		"memory": "0MB",
 77		"version": "1.0.0"
 78	}`))
 79}
 80
 81func main() {
 82	r := setupProductionChi()
 83
 84	srv := &http.Server{
 85		Addr:         ":8080",
 86		Handler:      r,
 87		ReadTimeout:  15 * time.Second,
 88		WriteTimeout: 30 * time.Second,
 89		IdleTimeout:  120 * time.Second,
 90	}
 91
 92	// Start server
 93	go func() {
 94		log.Printf("Server starting on %s", srv.Addr)
 95		if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
 96			log.Fatalf("Server failed to start: %v", err)
 97		}
 98	}()
 99
100	// Wait for interrupt signal
101	quit := make(chan os.Signal, 1)
102	signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
103	<-quit
104
105	log.Println("Shutting down server...")
106
107	// Graceful shutdown
108	ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
109	defer cancel()
110
111	if err := srv.Shutdown(ctx); err != nil {
112		log.Printf("Server shutdown error: %v", err)
113	}
114
115	log.Println("Server gracefully stopped")
116}

Advanced Routing Patterns

Routing is the backbone of any web application. While basic routing gets you started, production applications require sophisticated patterns for maintainability, flexibility, and performance.

Prefix-Based Routing and Route Groups

Route groups help organize related endpoints and apply shared middleware without repetition. This pattern is essential for API versioning and feature modules.

Fiber Route Groups:

  1// run
  2package main
  3
  4import (
  5	"fmt"
  6	"log"
  7	"time"
  8
  9	"github.com/gofiber/fiber/v2"
 10	"github.com/gofiber/fiber/v2/middleware/logger"
 11)
 12
 13// Middleware for API versioning
 14func APIVersion(version string) fiber.Handler {
 15	return func(c *fiber.Ctx) error {
 16		c.Set("X-API-Version", version)
 17		return c.Next()
 18	}
 19}
 20
 21// Authentication middleware
 22func RequireAuth() fiber.Handler {
 23	return func(c *fiber.Ctx) error {
 24		token := c.Get("Authorization")
 25		if token == "" {
 26			return c.Status(401).JSON(fiber.Map{
 27				"error": "unauthorized",
 28			})
 29		}
 30		// Simplified: In production, verify JWT token
 31		c.Locals("user_id", "user123")
 32		return c.Next()
 33	}
 34}
 35
 36// Admin authorization middleware
 37func RequireAdmin() fiber.Handler {
 38	return func(c *fiber.Ctx) error {
 39		// Check if user has admin role
 40		userID := c.Locals("user_id").(string)
 41		if userID != "admin123" {
 42			return c.Status(403).JSON(fiber.Map{
 43				"error": "forbidden - admin access required",
 44			})
 45		}
 46		return c.Next()
 47	}
 48}
 49
 50func main() {
 51	app := fiber.New(fiber.Config{
 52		AppName: "Advanced Routing Demo",
 53	})
 54
 55	// Global middleware
 56	app.Use(logger.New())
 57
 58	// Public routes - no authentication
 59	public := app.Group("/public")
 60	public.Get("/health", func(c *fiber.Ctx) error {
 61		return c.JSON(fiber.Map{"status": "healthy"})
 62	})
 63	public.Get("/info", func(c *fiber.Ctx) error {
 64		return c.JSON(fiber.Map{
 65			"app":     "Advanced Routing Demo",
 66			"version": "1.0.0",
 67		})
 68	})
 69
 70	// API v1 routes with authentication
 71	v1 := app.Group("/api/v1", APIVersion("1.0"))
 72	v1.Use(RequireAuth())
 73
 74	// User routes
 75	users := v1.Group("/users")
 76	users.Get("/", listUsers)
 77	users.Get("/:id", getUser)
 78	users.Post("/", createUser)
 79	users.Put("/:id", updateUser)
 80	users.Delete("/:id", deleteUser)
 81
 82	// Product routes with optional filters
 83	products := v1.Group("/products")
 84	products.Get("/", listProducts)
 85	products.Get("/:id", getProduct)
 86	products.Get("/category/:category", getProductsByCategory)
 87	products.Get("/search", searchProducts)
 88
 89	// API v2 routes with new features
 90	v2 := app.Group("/api/v2", APIVersion("2.0"))
 91	v2.Use(RequireAuth())
 92
 93	// Enhanced user routes in v2
 94	v2Users := v2.Group("/users")
 95	v2Users.Get("/", listUsersV2)
 96	v2Users.Get("/:id/profile", getUserProfile)
 97	v2Users.Get("/:id/orders", getUserOrders)
 98
 99	// Admin routes with additional authorization
100	admin := app.Group("/admin")
101	admin.Use(RequireAuth())
102	admin.Use(RequireAdmin())
103
104	admin.Get("/stats", getStats)
105	admin.Get("/users", getAllUsers)
106	admin.Delete("/users/:id", adminDeleteUser)
107	admin.Post("/maintenance", enableMaintenance)
108
109	log.Fatal(app.Listen(":3000"))
110}
111
112// Handler functions
113func listUsers(c *fiber.Ctx) error {
114	return c.JSON(fiber.Map{"users": []string{"user1", "user2"}})
115}
116
117func getUser(c *fiber.Ctx) error {
118	id := c.Params("id")
119	return c.JSON(fiber.Map{"id": id, "name": "John Doe"})
120}
121
122func createUser(c *fiber.Ctx) error {
123	return c.Status(201).JSON(fiber.Map{"message": "user created"})
124}
125
126func updateUser(c *fiber.Ctx) error {
127	id := c.Params("id")
128	return c.JSON(fiber.Map{"message": fmt.Sprintf("user %s updated", id)})
129}
130
131func deleteUser(c *fiber.Ctx) error {
132	id := c.Params("id")
133	return c.JSON(fiber.Map{"message": fmt.Sprintf("user %s deleted", id)})
134}
135
136func listProducts(c *fiber.Ctx) error {
137	return c.JSON(fiber.Map{"products": []string{"product1", "product2"}})
138}
139
140func getProduct(c *fiber.Ctx) error {
141	id := c.Params("id")
142	return c.JSON(fiber.Map{"id": id, "name": "Product Name"})
143}
144
145func getProductsByCategory(c *fiber.Ctx) error {
146	category := c.Params("category")
147	return c.JSON(fiber.Map{"category": category, "products": []string{}})
148}
149
150func searchProducts(c *fiber.Ctx) error {
151	query := c.Query("q")
152	return c.JSON(fiber.Map{"query": query, "results": []string{}})
153}
154
155func listUsersV2(c *fiber.Ctx) error {
156	return c.JSON(fiber.Map{
157		"users":   []string{"user1", "user2"},
158		"version": "2.0",
159	})
160}
161
162func getUserProfile(c *fiber.Ctx) error {
163	id := c.Params("id")
164	return c.JSON(fiber.Map{
165		"id":      id,
166		"profile": map[string]interface{}{"bio": "User bio"},
167	})
168}
169
170func getUserOrders(c *fiber.Ctx) error {
171	id := c.Params("id")
172	return c.JSON(fiber.Map{"user_id": id, "orders": []string{}})
173}
174
175func getStats(c *fiber.Ctx) error {
176	return c.JSON(fiber.Map{
177		"total_users":    1000,
178		"active_users":   750,
179		"total_products": 5000,
180	})
181}
182
183func getAllUsers(c *fiber.Ctx) error {
184	return c.JSON(fiber.Map{"users": []string{"all", "users"}})
185}
186
187func adminDeleteUser(c *fiber.Ctx) error {
188	id := c.Params("id")
189	return c.JSON(fiber.Map{"message": fmt.Sprintf("admin deleted user %s", id)})
190}
191
192func enableMaintenance(c *fiber.Ctx) error {
193	return c.JSON(fiber.Map{"message": "maintenance mode enabled"})
194}

Chi Route Groups with Subrouters:

  1// run
  2package main
  3
  4import (
  5	"encoding/json"
  6	"log"
  7	"net/http"
  8	"time"
  9
 10	"github.com/go-chi/chi/v5"
 11	"github.com/go-chi/chi/v5/middleware"
 12)
 13
 14// API versioning middleware
 15func APIVersionMiddleware(version string) func(next http.Handler) http.Handler {
 16	return func(next http.Handler) http.Handler {
 17		return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
 18			w.Header().Set("X-API-Version", version)
 19			next.ServeHTTP(w, r)
 20		})
 21	}
 22}
 23
 24// Authentication middleware
 25func AuthMiddleware(next http.Handler) http.Handler {
 26	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
 27		token := r.Header.Get("Authorization")
 28		if token == "" {
 29			w.WriteHeader(http.StatusUnauthorized)
 30			json.NewEncoder(w).Encode(map[string]string{
 31				"error": "unauthorized",
 32			})
 33			return
 34		}
 35		// Simplified: In production, verify JWT token
 36		// Store user info in context
 37		next.ServeHTTP(w, r)
 38	})
 39}
 40
 41// Admin authorization middleware
 42func AdminMiddleware(next http.Handler) http.Handler {
 43	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
 44		// Check admin role from context
 45		// Simplified for demo
 46		next.ServeHTTP(w, r)
 47	})
 48}
 49
 50func main() {
 51	r := chi.NewRouter()
 52
 53	// Global middleware
 54	r.Use(middleware.Logger)
 55	r.Use(middleware.Recoverer)
 56	r.Use(middleware.Timeout(60 * time.Second))
 57
 58	// Public routes
 59	r.Route("/public", func(r chi.Router) {
 60		r.Get("/health", func(w http.ResponseWriter, r *http.Request) {
 61			w.Header().Set("Content-Type", "application/json")
 62			json.NewEncoder(w).Encode(map[string]string{"status": "healthy"})
 63		})
 64		r.Get("/info", func(w http.ResponseWriter, r *http.Request) {
 65			w.Header().Set("Content-Type", "application/json")
 66			json.NewEncoder(w).Encode(map[string]string{
 67				"app":     "Chi Advanced Routing",
 68				"version": "1.0.0",
 69			})
 70		})
 71	})
 72
 73	// API v1 routes
 74	r.Route("/api/v1", func(r chi.Router) {
 75		r.Use(APIVersionMiddleware("1.0"))
 76		r.Use(AuthMiddleware)
 77
 78		// User routes
 79		r.Route("/users", func(r chi.Router) {
 80			r.Get("/", listUsersHandler)
 81			r.Post("/", createUserHandler)
 82
 83			// Specific user operations
 84			r.Route("/{userID}", func(r chi.Router) {
 85				r.Get("/", getUserHandler)
 86				r.Put("/", updateUserHandler)
 87				r.Delete("/", deleteUserHandler)
 88			})
 89		})
 90
 91		// Product routes
 92		r.Route("/products", func(r chi.Router) {
 93			r.Get("/", listProductsHandler)
 94			r.Get("/search", searchProductsHandler)
 95			r.Get("/{productID}", getProductHandler)
 96			r.Get("/category/{category}", getProductsByCategoryHandler)
 97		})
 98	})
 99
100	// API v2 routes with enhanced features
101	r.Route("/api/v2", func(r chi.Router) {
102		r.Use(APIVersionMiddleware("2.0"))
103		r.Use(AuthMiddleware)
104
105		r.Route("/users", func(r chi.Router) {
106			r.Get("/", listUsersV2Handler)
107
108			r.Route("/{userID}", func(r chi.Router) {
109				r.Get("/profile", getUserProfileHandler)
110				r.Get("/orders", getUserOrdersHandler)
111				r.Get("/activity", getUserActivityHandler)
112			})
113		})
114	})
115
116	// Admin routes
117	r.Route("/admin", func(r chi.Router) {
118		r.Use(AuthMiddleware)
119		r.Use(AdminMiddleware)
120
121		r.Get("/stats", getStatsHandler)
122		r.Get("/users", getAllUsersHandler)
123		r.Delete("/users/{userID}", adminDeleteUserHandler)
124		r.Post("/maintenance", enableMaintenanceHandler)
125	})
126
127	log.Println("Server starting on :3000")
128	log.Fatal(http.ListenAndServe(":3000", r))
129}
130
131// Handler implementations
132func listUsersHandler(w http.ResponseWriter, r *http.Request) {
133	w.Header().Set("Content-Type", "application/json")
134	json.NewEncoder(w).Encode(map[string]interface{}{
135		"users": []string{"user1", "user2"},
136	})
137}
138
139func createUserHandler(w http.ResponseWriter, r *http.Request) {
140	w.Header().Set("Content-Type", "application/json")
141	w.WriteHeader(http.StatusCreated)
142	json.NewEncoder(w).Encode(map[string]string{"message": "user created"})
143}
144
145func getUserHandler(w http.ResponseWriter, r *http.Request) {
146	userID := chi.URLParam(r, "userID")
147	w.Header().Set("Content-Type", "application/json")
148	json.NewEncoder(w).Encode(map[string]string{
149		"id":   userID,
150		"name": "John Doe",
151	})
152}
153
154func updateUserHandler(w http.ResponseWriter, r *http.Request) {
155	userID := chi.URLParam(r, "userID")
156	w.Header().Set("Content-Type", "application/json")
157	json.NewEncoder(w).Encode(map[string]string{
158		"message": "user " + userID + " updated",
159	})
160}
161
162func deleteUserHandler(w http.ResponseWriter, r *http.Request) {
163	userID := chi.URLParam(r, "userID")
164	w.Header().Set("Content-Type", "application/json")
165	json.NewEncoder(w).Encode(map[string]string{
166		"message": "user " + userID + " deleted",
167	})
168}
169
170func listProductsHandler(w http.ResponseWriter, r *http.Request) {
171	w.Header().Set("Content-Type", "application/json")
172	json.NewEncoder(w).Encode(map[string]interface{}{
173		"products": []string{"product1", "product2"},
174	})
175}
176
177func searchProductsHandler(w http.ResponseWriter, r *http.Request) {
178	query := r.URL.Query().Get("q")
179	w.Header().Set("Content-Type", "application/json")
180	json.NewEncoder(w).Encode(map[string]interface{}{
181		"query":   query,
182		"results": []string{},
183	})
184}
185
186func getProductHandler(w http.ResponseWriter, r *http.Request) {
187	productID := chi.URLParam(r, "productID")
188	w.Header().Set("Content-Type", "application/json")
189	json.NewEncoder(w).Encode(map[string]string{
190		"id":   productID,
191		"name": "Product Name",
192	})
193}
194
195func getProductsByCategoryHandler(w http.ResponseWriter, r *http.Request) {
196	category := chi.URLParam(r, "category")
197	w.Header().Set("Content-Type", "application/json")
198	json.NewEncoder(w).Encode(map[string]interface{}{
199		"category": category,
200		"products": []string{},
201	})
202}
203
204func listUsersV2Handler(w http.ResponseWriter, r *http.Request) {
205	w.Header().Set("Content-Type", "application/json")
206	json.NewEncoder(w).Encode(map[string]interface{}{
207		"users":   []string{"user1", "user2"},
208		"version": "2.0",
209	})
210}
211
212func getUserProfileHandler(w http.ResponseWriter, r *http.Request) {
213	userID := chi.URLParam(r, "userID")
214	w.Header().Set("Content-Type", "application/json")
215	json.NewEncoder(w).Encode(map[string]interface{}{
216		"id":      userID,
217		"profile": map[string]string{"bio": "User bio"},
218	})
219}
220
221func getUserOrdersHandler(w http.ResponseWriter, r *http.Request) {
222	userID := chi.URLParam(r, "userID")
223	w.Header().Set("Content-Type", "application/json")
224	json.NewEncoder(w).Encode(map[string]interface{}{
225		"user_id": userID,
226		"orders":  []string{},
227	})
228}
229
230func getUserActivityHandler(w http.ResponseWriter, r *http.Request) {
231	userID := chi.URLParam(r, "userID")
232	w.Header().Set("Content-Type", "application/json")
233	json.NewEncoder(w).Encode(map[string]interface{}{
234		"user_id":  userID,
235		"activity": []string{},
236	})
237}
238
239func getStatsHandler(w http.ResponseWriter, r *http.Request) {
240	w.Header().Set("Content-Type", "application/json")
241	json.NewEncoder(w).Encode(map[string]interface{}{
242		"total_users":    1000,
243		"active_users":   750,
244		"total_products": 5000,
245	})
246}
247
248func getAllUsersHandler(w http.ResponseWriter, r *http.Request) {
249	w.Header().Set("Content-Type", "application/json")
250	json.NewEncoder(w).Encode(map[string]interface{}{
251		"users": []string{"all", "users"},
252	})
253}
254
255func adminDeleteUserHandler(w http.ResponseWriter, r *http.Request) {
256	userID := chi.URLParam(r, "userID")
257	w.Header().Set("Content-Type", "application/json")
258	json.NewEncoder(w).Encode(map[string]string{
259		"message": "admin deleted user " + userID,
260	})
261}
262
263func enableMaintenanceHandler(w http.ResponseWriter, r *http.Request) {
264	w.Header().Set("Content-Type", "application/json")
265	json.NewEncoder(w).Encode(map[string]string{
266		"message": "maintenance mode enabled",
267	})
268}

Regex-Based and Custom Route Matchers

For complex routing requirements, regex patterns and custom matchers provide fine-grained control over request matching.

Fiber with Custom Route Matching:

  1// run
  2package main
  3
  4import (
  5	"log"
  6	"regexp"
  7	"strings"
  8
  9	"github.com/gofiber/fiber/v2"
 10)
 11
 12// Custom route constraint for UUID validation
 13func IsValidUUID(uuid string) bool {
 14	pattern := `^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$`
 15	matched, _ := regexp.MatchString(pattern, uuid)
 16	return matched
 17}
 18
 19// Middleware to validate UUID parameters
 20func ValidateUUID(paramName string) fiber.Handler {
 21	return func(c *fiber.Ctx) error {
 22		uuid := c.Params(paramName)
 23		if !IsValidUUID(uuid) {
 24			return c.Status(400).JSON(fiber.Map{
 25				"error": "invalid UUID format",
 26				"param": paramName,
 27			})
 28		}
 29		return c.Next()
 30	}
 31}
 32
 33// Custom slug validator
 34func IsValidSlug(slug string) bool {
 35	pattern := `^[a-z0-9]+(?:-[a-z0-9]+)*$`
 36	matched, _ := regexp.MatchString(pattern, slug)
 37	return matched && len(slug) >= 3 && len(slug) <= 100
 38}
 39
 40// Middleware to validate slug format
 41func ValidateSlug(paramName string) fiber.Handler {
 42	return func(c *fiber.Ctx) error {
 43		slug := c.Params(paramName)
 44		if !IsValidSlug(slug) {
 45			return c.Status(400).JSON(fiber.Map{
 46				"error": "invalid slug format",
 47				"param": paramName,
 48				"rules": "lowercase letters, numbers, and hyphens only",
 49			})
 50		}
 51		return c.Next()
 52	}
 53}
 54
 55// Custom date format validator (YYYY-MM-DD)
 56func ValidateDateParam(paramName string) fiber.Handler {
 57	datePattern := regexp.MustCompile(`^\d{4}-\d{2}-\d{2}$`)
 58
 59	return func(c *fiber.Ctx) error {
 60		date := c.Params(paramName)
 61		if !datePattern.MatchString(date) {
 62			return c.Status(400).JSON(fiber.Map{
 63				"error":  "invalid date format",
 64				"param":  paramName,
 65				"format": "YYYY-MM-DD",
 66			})
 67		}
 68		return c.Next()
 69	}
 70}
 71
 72func main() {
 73	app := fiber.New()
 74
 75	// Routes with UUID validation
 76	app.Get("/users/:id", ValidateUUID("id"), func(c *fiber.Ctx) error {
 77		return c.JSON(fiber.Map{
 78			"user_id": c.Params("id"),
 79			"message": "Valid UUID",
 80		})
 81	})
 82
 83	app.Get("/orders/:orderId", ValidateUUID("orderId"), func(c *fiber.Ctx) error {
 84		return c.JSON(fiber.Map{
 85			"order_id": c.Params("orderId"),
 86			"status":   "processing",
 87		})
 88	})
 89
 90	// Routes with slug validation
 91	app.Get("/blog/:slug", ValidateSlug("slug"), func(c *fiber.Ctx) error {
 92		slug := c.Params("slug")
 93		return c.JSON(fiber.Map{
 94			"slug":    slug,
 95			"title":   strings.Title(strings.ReplaceAll(slug, "-", " ")),
 96			"content": "Blog post content here",
 97		})
 98	})
 99
100	app.Get("/products/:category/:slug",
101		ValidateSlug("category"),
102		ValidateSlug("slug"),
103		func(c *fiber.Ctx) error {
104			return c.JSON(fiber.Map{
105				"category": c.Params("category"),
106				"slug":     c.Params("slug"),
107				"product":  "Product details",
108			})
109		},
110	)
111
112	// Routes with date validation
113	app.Get("/reports/:date", ValidateDateParam("date"), func(c *fiber.Ctx) error {
114		return c.JSON(fiber.Map{
115			"date":   c.Params("date"),
116			"report": "Daily report data",
117		})
118	})
119
120	app.Get("/analytics/:startDate/:endDate",
121		ValidateDateParam("startDate"),
122		ValidateDateParam("endDate"),
123		func(c *fiber.Ctx) error {
124			return c.JSON(fiber.Map{
125				"start": c.Params("startDate"),
126				"end":   c.Params("endDate"),
127				"data":  "Analytics data",
128			})
129		},
130	)
131
132	// Custom pattern matching with regex in path
133	// Fiber supports regex constraints in the route pattern
134	app.Get("/api/:version<v\\d+>/:resource", func(c *fiber.Ctx) error {
135		return c.JSON(fiber.Map{
136			"version":  c.Params("version"),
137			"resource": c.Params("resource"),
138		})
139	})
140
141	// Numeric-only parameter validation
142	app.Get("/page/:number<\\d+>", func(c *fiber.Ctx) error {
143		return c.JSON(fiber.Map{
144			"page":    c.Params("number"),
145			"message": "Valid page number",
146		})
147	})
148
149	log.Fatal(app.Listen(":3000"))
150}

Chi with Custom Route Matchers:

  1// run
  2package main
  3
  4import (
  5	"context"
  6	"encoding/json"
  7	"log"
  8	"net/http"
  9	"regexp"
 10	"strings"
 11
 12	"github.com/go-chi/chi/v5"
 13	"github.com/go-chi/chi/v5/middleware"
 14)
 15
 16// Custom context keys
 17type contextKey string
 18
 19const (
 20	validatedParamKey contextKey = "validated_param"
 21)
 22
 23// UUID validator middleware
 24func UUIDValidator(next http.Handler) http.Handler {
 25	uuidPattern := regexp.MustCompile(`^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$`)
 26
 27	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
 28		// Check all URL params that look like UUIDs
 29		ctx := chi.RouteContext(r.Context())
 30		for _, key := range ctx.URLParams.Keys {
 31			value := chi.URLParam(r, key)
 32			if strings.Contains(key, "id") || strings.Contains(key, "ID") {
 33				if !uuidPattern.MatchString(value) {
 34					w.Header().Set("Content-Type", "application/json")
 35					w.WriteHeader(http.StatusBadRequest)
 36					json.NewEncoder(w).Encode(map[string]string{
 37						"error": "invalid UUID format",
 38						"param": key,
 39					})
 40					return
 41				}
 42			}
 43		}
 44		next.ServeHTTP(w, r)
 45	})
 46}
 47
 48// Slug validator middleware
 49func SlugValidator(paramName string) func(next http.Handler) http.Handler {
 50	slugPattern := regexp.MustCompile(`^[a-z0-9]+(?:-[a-z0-9]+)*$`)
 51
 52	return func(next http.Handler) http.Handler {
 53		return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
 54			slug := chi.URLParam(r, paramName)
 55			if !slugPattern.MatchString(slug) || len(slug) < 3 || len(slug) > 100 {
 56				w.Header().Set("Content-Type", "application/json")
 57				w.WriteHeader(http.StatusBadRequest)
 58				json.NewEncoder(w).Encode(map[string]string{
 59					"error": "invalid slug format",
 60					"param": paramName,
 61					"rules": "lowercase letters, numbers, and hyphens only",
 62				})
 63				return
 64			}
 65
 66			// Store validated value in context
 67			ctx := context.WithValue(r.Context(), validatedParamKey, slug)
 68			next.ServeHTTP(w, r.WithContext(ctx))
 69		})
 70	}
 71}
 72
 73// Date format validator
 74func DateValidator(paramName string) func(next http.Handler) http.Handler {
 75	datePattern := regexp.MustCompile(`^\d{4}-\d{2}-\d{2}$`)
 76
 77	return func(next http.Handler) http.Handler {
 78		return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
 79			date := chi.URLParam(r, paramName)
 80			if !datePattern.MatchString(date) {
 81				w.Header().Set("Content-Type", "application/json")
 82				w.WriteHeader(http.StatusBadRequest)
 83				json.NewEncoder(w).Encode(map[string]string{
 84					"error":  "invalid date format",
 85					"param":  paramName,
 86					"format": "YYYY-MM-DD",
 87				})
 88				return
 89			}
 90			next.ServeHTTP(w, r)
 91		})
 92	}
 93}
 94
 95// Custom regex route matcher
 96func RegexMatcher(pattern string) func(next http.Handler) http.Handler {
 97	regex := regexp.MustCompile(pattern)
 98
 99	return func(next http.Handler) http.Handler {
100		return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
101			if !regex.MatchString(r.URL.Path) {
102				w.WriteHeader(http.StatusNotFound)
103				return
104			}
105			next.ServeHTTP(w, r)
106		})
107	}
108}
109
110func main() {
111	r := chi.NewRouter()
112	r.Use(middleware.Logger)
113	r.Use(middleware.Recoverer)
114
115	// Routes with UUID validation
116	r.Route("/users/{userID}", func(r chi.Router) {
117		r.Use(UUIDValidator)
118
119		r.Get("/", func(w http.ResponseWriter, r *http.Request) {
120			userID := chi.URLParam(r, "userID")
121			w.Header().Set("Content-Type", "application/json")
122			json.NewEncoder(w).Encode(map[string]string{
123				"user_id": userID,
124				"message": "Valid UUID",
125			})
126		})
127
128		r.Get("/profile", func(w http.ResponseWriter, r *http.Request) {
129			userID := chi.URLParam(r, "userID")
130			w.Header().Set("Content-Type", "application/json")
131			json.NewEncoder(w).Encode(map[string]string{
132				"user_id": userID,
133				"profile": "User profile data",
134			})
135		})
136	})
137
138	// Routes with slug validation
139	r.Route("/blog/{slug}", func(r chi.Router) {
140		r.Use(SlugValidator("slug"))
141
142		r.Get("/", func(w http.ResponseWriter, r *http.Request) {
143			slug := chi.URLParam(r, "slug")
144			w.Header().Set("Content-Type", "application/json")
145			json.NewEncoder(w).Encode(map[string]interface{}{
146				"slug":    slug,
147				"title":   strings.Title(strings.ReplaceAll(slug, "-", " ")),
148				"content": "Blog post content",
149			})
150		})
151	})
152
153	// Routes with date validation
154	r.Route("/reports/{date}", func(r chi.Router) {
155		r.Use(DateValidator("date"))
156
157		r.Get("/", func(w http.ResponseWriter, r *http.Request) {
158			date := chi.URLParam(r, "date")
159			w.Header().Set("Content-Type", "application/json")
160			json.NewEncoder(w).Encode(map[string]string{
161				"date":   date,
162				"report": "Daily report data",
163			})
164		})
165	})
166
167	// Analytics with date range validation
168	r.Route("/analytics/{startDate}/{endDate}", func(r chi.Router) {
169		r.Use(DateValidator("startDate"))
170		r.Use(DateValidator("endDate"))
171
172		r.Get("/", func(w http.ResponseWriter, r *http.Request) {
173			w.Header().Set("Content-Type", "application/json")
174			json.NewEncoder(w).Encode(map[string]string{
175				"start": chi.URLParam(r, "startDate"),
176				"end":   chi.URLParam(r, "endDate"),
177				"data":  "Analytics data",
178			})
179		})
180	})
181
182	// Custom pattern matching with regex
183	r.Route("/api", func(r chi.Router) {
184		// Only match v1, v2, v3, etc.
185		r.Route("/{version:v[0-9]+}", func(r chi.Router) {
186			r.Get("/{resource}", func(w http.ResponseWriter, r *http.Request) {
187				w.Header().Set("Content-Type", "application/json")
188				json.NewEncoder(w).Encode(map[string]string{
189					"version":  chi.URLParam(r, "version"),
190					"resource": chi.URLParam(r, "resource"),
191				})
192			})
193		})
194	})
195
196	log.Println("Server starting on :3000")
197	log.Fatal(http.ListenAndServe(":3000", r))
198}

Middleware Composition and Chaining

Middleware is the glue that holds modern web applications together. Proper composition and chaining patterns enable code reuse, separation of concerns, and maintainable request processing pipelines.

Advanced Middleware Patterns

Fiber Middleware Chaining:

  1// run
  2package main
  3
  4import (
  5	"fmt"
  6	"log"
  7	"time"
  8
  9	"github.com/gofiber/fiber/v2"
 10	"github.com/gofiber/fiber/v2/middleware/logger"
 11)
 12
 13// Request ID middleware
 14func RequestID() fiber.Handler {
 15	return func(c *fiber.Ctx) error {
 16		reqID := fmt.Sprintf("req-%d", time.Now().UnixNano())
 17		c.Locals("request_id", reqID)
 18		c.Set("X-Request-ID", reqID)
 19		return c.Next()
 20	}
 21}
 22
 23// Timing middleware
 24func Timing() fiber.Handler {
 25	return func(c *fiber.Ctx) error {
 26		start := time.Now()
 27		c.Locals("start_time", start)
 28
 29		err := c.Next()
 30
 31		duration := time.Since(start)
 32		c.Set("X-Response-Time", duration.String())
 33
 34		reqID := c.Locals("request_id").(string)
 35		log.Printf("[%s] %s %s - %v",
 36			reqID,
 37			c.Method(),
 38			c.Path(),
 39			duration,
 40		)
 41
 42		return err
 43	}
 44}
 45
 46// Authentication middleware with levels
 47func RequireAuth(level string) fiber.Handler {
 48	return func(c *fiber.Ctx) error {
 49		token := c.Get("Authorization")
 50		if token == "" {
 51			return c.Status(401).JSON(fiber.Map{
 52				"error": "missing authentication token",
 53			})
 54		}
 55
 56		// Simulate token validation
 57		// In production, verify JWT and extract user info
 58		c.Locals("user_id", "user123")
 59		c.Locals("auth_level", level)
 60
 61		return c.Next()
 62	}
 63}
 64
 65// Rate limiting middleware
 66func RateLimit(maxRequests int, window time.Duration) fiber.Handler {
 67	// Simple in-memory rate limiting (use Redis in production)
 68	requests := make(map[string][]time.Time)
 69
 70	return func(c *fiber.Ctx) error {
 71		clientIP := c.IP()
 72		now := time.Now()
 73
 74		// Clean old requests
 75		if times, exists := requests[clientIP]; exists {
 76			var validTimes []time.Time
 77			for _, t := range times {
 78				if now.Sub(t) < window {
 79					validTimes = append(validTimes, t)
 80				}
 81			}
 82			requests[clientIP] = validTimes
 83		}
 84
 85		// Check rate limit
 86		if len(requests[clientIP]) >= maxRequests {
 87			return c.Status(429).JSON(fiber.Map{
 88				"error":       "rate limit exceeded",
 89				"retry_after": window.Seconds(),
 90			})
 91		}
 92
 93		// Record request
 94		requests[clientIP] = append(requests[clientIP], now)
 95
 96		return c.Next()
 97	}
 98}
 99
100// Caching middleware
101func CacheControl(maxAge int) fiber.Handler {
102	return func(c *fiber.Ctx) error {
103		c.Set("Cache-Control", fmt.Sprintf("public, max-age=%d", maxAge))
104		return c.Next()
105	}
106}
107
108// Content type validation middleware
109func RequireJSON() fiber.Handler {
110	return func(c *fiber.Ctx) error {
111		contentType := c.Get("Content-Type")
112		if c.Method() != "GET" && contentType != "application/json" {
113			return c.Status(415).JSON(fiber.Map{
114				"error": "content-type must be application/json",
115			})
116		}
117		return c.Next()
118	}
119}
120
121// Response modification middleware
122func JSONResponse() fiber.Handler {
123	return func(c *fiber.Ctx) error {
124		c.Set("Content-Type", "application/json")
125
126		err := c.Next()
127
128		// Wrap response in standard envelope
129		if c.Response().StatusCode() >= 200 && c.Response().StatusCode() < 300 {
130			reqID := c.Locals("request_id").(string)
131			c.JSON(fiber.Map{
132				"request_id": reqID,
133				"timestamp":  time.Now().Unix(),
134				"data":       string(c.Response().Body()),
135			})
136		}
137
138		return err
139	}
140}
141
142func main() {
143	app := fiber.New(fiber.Config{
144		AppName: "Middleware Composition Demo",
145	})
146
147	// Global middleware chain
148	app.Use(RequestID())
149	app.Use(Timing())
150	app.Use(logger.New())
151
152	// Public endpoints with rate limiting
153	public := app.Group("/public")
154	public.Use(RateLimit(10, time.Minute))
155	public.Use(CacheControl(3600))
156
157	public.Get("/health", func(c *fiber.Ctx) error {
158		return c.JSON(fiber.Map{"status": "healthy"})
159	})
160
161	public.Get("/info", func(c *fiber.Ctx) error {
162		return c.JSON(fiber.Map{
163			"app":     "Middleware Demo",
164			"version": "1.0.0",
165		})
166	})
167
168	// API endpoints with authentication and JSON validation
169	api := app.Group("/api")
170	api.Use(RequireAuth("user"))
171	api.Use(RequireJSON())
172	api.Use(RateLimit(100, time.Minute))
173
174	api.Get("/users", func(c *fiber.Ctx) error {
175		return c.JSON(fiber.Map{
176			"users":   []string{"user1", "user2"},
177			"user_id": c.Locals("user_id"),
178		})
179	})
180
181	api.Post("/users", func(c *fiber.Ctx) error {
182		var body map[string]interface{}
183		if err := c.BodyParser(&body); err != nil {
184			return c.Status(400).JSON(fiber.Map{"error": "invalid JSON"})
185		}
186
187		return c.Status(201).JSON(fiber.Map{
188			"message": "user created",
189			"data":    body,
190		})
191	})
192
193	// Admin endpoints with stricter authentication
194	admin := app.Group("/admin")
195	admin.Use(RequireAuth("admin"))
196	admin.Use(RequireJSON())
197	admin.Use(RateLimit(50, time.Minute))
198
199	admin.Get("/stats", func(c *fiber.Ctx) error {
200		return c.JSON(fiber.Map{
201			"total_users": 1000,
202			"total_requests": 50000,
203		})
204	})
205
206	admin.Delete("/users/:id", func(c *fiber.Ctx) error {
207		id := c.Params("id")
208		return c.JSON(fiber.Map{
209			"message": fmt.Sprintf("user %s deleted", id),
210		})
211	})
212
213	log.Fatal(app.Listen(":3000"))
214}

Chi Middleware Composition:

  1// run
  2package main
  3
  4import (
  5	"context"
  6	"encoding/json"
  7	"fmt"
  8	"log"
  9	"net/http"
 10	"time"
 11
 12	"github.com/go-chi/chi/v5"
 13	"github.com/go-chi/chi/v5/middleware"
 14)
 15
 16// Context keys
 17type contextKey string
 18
 19const (
 20	requestIDKey  contextKey = "request_id"
 21	startTimeKey  contextKey = "start_time"
 22	userIDKey     contextKey = "user_id"
 23	authLevelKey  contextKey = "auth_level"
 24)
 25
 26// Request ID middleware
 27func RequestIDMiddleware(next http.Handler) http.Handler {
 28	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
 29		reqID := fmt.Sprintf("req-%d", time.Now().UnixNano())
 30		ctx := context.WithValue(r.Context(), requestIDKey, reqID)
 31		w.Header().Set("X-Request-ID", reqID)
 32		next.ServeHTTP(w, r.WithContext(ctx))
 33	})
 34}
 35
 36// Timing middleware
 37func TimingMiddleware(next http.Handler) http.Handler {
 38	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
 39		start := time.Now()
 40		ctx := context.WithValue(r.Context(), startTimeKey, start)
 41
 42		// Wrap ResponseWriter to capture status code
 43		ww := middleware.NewWrapResponseWriter(w, r.ProtoMajor)
 44
 45		next.ServeHTTP(ww, r.WithContext(ctx))
 46
 47		duration := time.Since(start)
 48		w.Header().Set("X-Response-Time", duration.String())
 49
 50		reqID := r.Context().Value(requestIDKey).(string)
 51		log.Printf("[%s] %s %s - %d - %v",
 52			reqID,
 53			r.Method,
 54			r.URL.Path,
 55			ww.Status(),
 56			duration,
 57		)
 58	})
 59}
 60
 61// Authentication middleware
 62func AuthMiddleware(level string) func(next http.Handler) http.Handler {
 63	return func(next http.Handler) http.Handler {
 64		return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
 65			token := r.Header.Get("Authorization")
 66			if token == "" {
 67				w.Header().Set("Content-Type", "application/json")
 68				w.WriteHeader(http.StatusUnauthorized)
 69				json.NewEncoder(w).Encode(map[string]string{
 70					"error": "missing authentication token",
 71				})
 72				return
 73			}
 74
 75			// Simulate token validation
 76			ctx := r.Context()
 77			ctx = context.WithValue(ctx, userIDKey, "user123")
 78			ctx = context.WithValue(ctx, authLevelKey, level)
 79
 80			next.ServeHTTP(w, r.WithContext(ctx))
 81		})
 82	}
 83}
 84
 85// Rate limiting middleware
 86func RateLimitMiddleware(maxRequests int, window time.Duration) func(next http.Handler) http.Handler {
 87	requests := make(map[string][]time.Time)
 88
 89	return func(next http.Handler) http.Handler {
 90		return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
 91			clientIP := r.RemoteAddr
 92			now := time.Now()
 93
 94			// Clean old requests
 95			if times, exists := requests[clientIP]; exists {
 96				var validTimes []time.Time
 97				for _, t := range times {
 98					if now.Sub(t) < window {
 99						validTimes = append(validTimes, t)
100					}
101				}
102				requests[clientIP] = validTimes
103			}
104
105			// Check rate limit
106			if len(requests[clientIP]) >= maxRequests {
107				w.Header().Set("Content-Type", "application/json")
108				w.WriteHeader(http.StatusTooManyRequests)
109				json.NewEncoder(w).Encode(map[string]interface{}{
110					"error":       "rate limit exceeded",
111					"retry_after": window.Seconds(),
112				})
113				return
114			}
115
116			// Record request
117			requests[clientIP] = append(requests[clientIP], now)
118
119			next.ServeHTTP(w, r)
120		})
121	}
122}
123
124// Cache control middleware
125func CacheControlMiddleware(maxAge int) func(next http.Handler) http.Handler {
126	return func(next http.Handler) http.Handler {
127		return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
128			w.Header().Set("Cache-Control", fmt.Sprintf("public, max-age=%d", maxAge))
129			next.ServeHTTP(w, r)
130		})
131	}
132}
133
134// JSON content type middleware
135func RequireJSONMiddleware(next http.Handler) http.Handler {
136	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
137		if r.Method != "GET" && r.Header.Get("Content-Type") != "application/json" {
138			w.Header().Set("Content-Type", "application/json")
139			w.WriteHeader(http.StatusUnsupportedMediaType)
140			json.NewEncoder(w).Encode(map[string]string{
141				"error": "content-type must be application/json",
142			})
143			return
144		}
145		next.ServeHTTP(w, r)
146	})
147}
148
149func main() {
150	r := chi.NewRouter()
151
152	// Global middleware chain
153	r.Use(RequestIDMiddleware)
154	r.Use(TimingMiddleware)
155	r.Use(middleware.Logger)
156	r.Use(middleware.Recoverer)
157
158	// Public routes with rate limiting and caching
159	r.Route("/public", func(r chi.Router) {
160		r.Use(RateLimitMiddleware(10, time.Minute))
161		r.Use(CacheControlMiddleware(3600))
162
163		r.Get("/health", func(w http.ResponseWriter, r *http.Request) {
164			w.Header().Set("Content-Type", "application/json")
165			json.NewEncoder(w).Encode(map[string]string{
166				"status": "healthy",
167			})
168		})
169
170		r.Get("/info", func(w http.ResponseWriter, r *http.Request) {
171			w.Header().Set("Content-Type", "application/json")
172			json.NewEncoder(w).Encode(map[string]string{
173				"app":     "Chi Middleware Demo",
174				"version": "1.0.0",
175			})
176		})
177	})
178
179	// API routes with authentication and JSON validation
180	r.Route("/api", func(r chi.Router) {
181		r.Use(AuthMiddleware("user"))
182		r.Use(RequireJSONMiddleware)
183		r.Use(RateLimitMiddleware(100, time.Minute))
184
185		r.Get("/users", func(w http.ResponseWriter, r *http.Request) {
186			userID := r.Context().Value(userIDKey).(string)
187			w.Header().Set("Content-Type", "application/json")
188			json.NewEncoder(w).Encode(map[string]interface{}{
189				"users":   []string{"user1", "user2"},
190				"user_id": userID,
191			})
192		})
193
194		r.Post("/users", func(w http.ResponseWriter, r *http.Request) {
195			var body map[string]interface{}
196			if err := json.NewDecoder(r.Body).Decode(&body); err != nil {
197				w.WriteHeader(http.StatusBadRequest)
198				json.NewEncoder(w).Encode(map[string]string{
199					"error": "invalid JSON",
200				})
201				return
202			}
203
204			w.Header().Set("Content-Type", "application/json")
205			w.WriteHeader(http.StatusCreated)
206			json.NewEncoder(w).Encode(map[string]interface{}{
207				"message": "user created",
208				"data":    body,
209			})
210		})
211	})
212
213	// Admin routes with stricter authentication
214	r.Route("/admin", func(r chi.Router) {
215		r.Use(AuthMiddleware("admin"))
216		r.Use(RequireJSONMiddleware)
217		r.Use(RateLimitMiddleware(50, time.Minute))
218
219		r.Get("/stats", func(w http.ResponseWriter, r *http.Request) {
220			w.Header().Set("Content-Type", "application/json")
221			json.NewEncoder(w).Encode(map[string]interface{}{
222				"total_users":    1000,
223				"total_requests": 50000,
224			})
225		})
226
227		r.Delete("/users/{userID}", func(w http.ResponseWriter, r *http.Request) {
228			userID := chi.URLParam(r, "userID")
229			w.Header().Set("Content-Type", "application/json")
230			json.NewEncoder(w).Encode(map[string]string{
231				"message": fmt.Sprintf("user %s deleted", userID),
232			})
233		})
234	})
235
236	log.Println("Server starting on :3000")
237	log.Fatal(http.ListenAndServe(":3000", r))
238}

WebSocket and Server-Sent Events

Real-time communication is essential for modern applications. Both Fiber and Chi support WebSockets and Server-Sent Events (SSE) for bidirectional and unidirectional real-time data streaming.

WebSocket Implementation

Fiber WebSocket Support:

  1// run
  2package main
  3
  4import (
  5	"fmt"
  6	"log"
  7	"time"
  8
  9	"github.com/gofiber/fiber/v2"
 10	"github.com/gofiber/websocket/v2"
 11)
 12
 13// Client represents a WebSocket client
 14type Client struct {
 15	ID   string
 16	Conn *websocket.Conn
 17	Pool *Pool
 18}
 19
 20// Pool manages WebSocket clients
 21type Pool struct {
 22	Clients    map[string]*Client
 23	Register   chan *Client
 24	Unregister chan *Client
 25	Broadcast  chan Message
 26}
 27
 28// Message represents a WebSocket message
 29type Message struct {
 30	Type    string      `json:"type"`
 31	From    string      `json:"from"`
 32	To      string      `json:"to"`
 33	Content interface{} `json:"content"`
 34}
 35
 36// NewPool creates a new WebSocket pool
 37func NewPool() *Pool {
 38	return &Pool{
 39		Clients:    make(map[string]*Client),
 40		Register:   make(chan *Client),
 41		Unregister: make(chan *Client),
 42		Broadcast:  make(chan Message),
 43	}
 44}
 45
 46// Start begins the pool's main loop
 47func (p *Pool) Start() {
 48	for {
 49		select {
 50		case client := <-p.Register:
 51			p.Clients[client.ID] = client
 52			log.Printf("Client registered: %s (Total: %d)", client.ID, len(p.Clients))
 53
 54			// Notify all clients
 55			p.Broadcast <- Message{
 56				Type:    "user_joined",
 57				From:    "system",
 58				Content: fmt.Sprintf("User %s joined", client.ID),
 59			}
 60
 61		case client := <-p.Unregister:
 62			if _, ok := p.Clients[client.ID]; ok {
 63				delete(p.Clients, client.ID)
 64				log.Printf("Client unregistered: %s (Total: %d)", client.ID, len(p.Clients))
 65
 66				// Notify all clients
 67				p.Broadcast <- Message{
 68					Type:    "user_left",
 69					From:    "system",
 70					Content: fmt.Sprintf("User %s left", client.ID),
 71				}
 72			}
 73
 74		case message := <-p.Broadcast:
 75			for id, client := range p.Clients {
 76				if message.To == "" || message.To == id {
 77					if err := client.Conn.WriteJSON(message); err != nil {
 78						log.Printf("Error broadcasting to %s: %v", id, err)
 79						p.Unregister <- client
 80					}
 81				}
 82			}
 83		}
 84	}
 85}
 86
 87func main() {
 88	app := fiber.New()
 89	pool := NewPool()
 90
 91	// Start the WebSocket pool
 92	go pool.Start()
 93
 94	// Serve static HTML page
 95	app.Get("/", func(c *fiber.Ctx) error {
 96		return c.SendString(`
 97<!DOCTYPE html>
 98<html>
 99<head>
100    <title>WebSocket Demo</title>
101</head>
102<body>
103    <h1>Fiber WebSocket Demo</h1>
104    <div id="status">Disconnected</div>
105    <div id="messages" style="border:1px solid #ccc; height:300px; overflow-y:auto; padding:10px;"></div>
106    <input type="text" id="messageInput" placeholder="Type message..." style="width:80%;">
107    <button onclick="sendMessage()">Send</button>
108    <script>
109        const clientId = 'user_' + Math.random().toString(36).substr(2, 9);
110        const ws = new WebSocket('ws://localhost:3000/ws/' + clientId);
111
112        ws.onopen = () => {
113            document.getElementById('status').textContent = 'Connected as ' + clientId;
114            document.getElementById('status').style.color = 'green';
115        };
116
117        ws.onmessage = (event) => {
118            const message = JSON.parse(event.data);
119            const messagesDiv = document.getElementById('messages');
120            messagesDiv.innerHTML += '<p><strong>' + message.from + ':</strong> ' + message.content + '</p>';
121            messagesDiv.scrollTop = messagesDiv.scrollHeight;
122        };
123
124        ws.onclose = () => {
125            document.getElementById('status').textContent = 'Disconnected';
126            document.getElementById('status').style.color = 'red';
127        };
128
129        function sendMessage() {
130            const input = document.getElementById('messageInput');
131            const message = {
132                type: 'chat',
133                from: clientId,
134                content: input.value
135            };
136            ws.send(JSON.stringify(message));
137            input.value = '';
138        }
139
140        document.getElementById('messageInput').addEventListener('keypress', (e) => {
141            if (e.key === 'Enter') sendMessage();
142        });
143    </script>
144</body>
145</html>
146		`)
147	})
148
149	// WebSocket upgrade endpoint
150	app.Use("/ws", func(c *fiber.Ctx) error {
151		if websocket.IsWebSocketUpgrade(c) {
152			return c.Next()
153		}
154		return fiber.ErrUpgradeRequired
155	})
156
157	// WebSocket handler
158	app.Get("/ws/:id", websocket.New(func(c *websocket.Conn) {
159		clientID := c.Params("id")
160
161		client := &Client{
162			ID:   clientID,
163			Conn: c,
164			Pool: pool,
165		}
166
167		pool.Register <- client
168		defer func() {
169			pool.Unregister <- client
170			c.Close()
171		}()
172
173		// Send welcome message
174		c.WriteJSON(Message{
175			Type:    "welcome",
176			From:    "system",
177			Content: fmt.Sprintf("Welcome %s!", clientID),
178		})
179
180		// Read messages
181		for {
182			var msg Message
183			if err := c.ReadJSON(&msg); err != nil {
184				log.Printf("Read error for %s: %v", clientID, err)
185				break
186			}
187
188			msg.From = clientID
189			pool.Broadcast <- msg
190		}
191	}))
192
193	// API endpoint to get connected clients
194	app.Get("/api/clients", func(c *fiber.Ctx) error {
195		var clientIDs []string
196		for id := range pool.Clients {
197			clientIDs = append(clientIDs, id)
198		}
199		return c.JSON(fiber.Map{
200			"count":   len(clientIDs),
201			"clients": clientIDs,
202		})
203	})
204
205	// API endpoint to broadcast message
206	app.Post("/api/broadcast", func(c *fiber.Ctx) error {
207		var msg Message
208		if err := c.BodyParser(&msg); err != nil {
209			return c.Status(400).JSON(fiber.Map{"error": "invalid message"})
210		}
211
212		msg.From = "api"
213		pool.Broadcast <- msg
214
215		return c.JSON(fiber.Map{"message": "broadcast sent"})
216	})
217
218	log.Println("Server starting on :3000")
219	log.Fatal(app.Listen(":3000"))
220}

Server-Sent Events (SSE)

Fiber SSE Implementation:

  1// run
  2package main
  3
  4import (
  5	"fmt"
  6	"log"
  7	"time"
  8
  9	"github.com/gofiber/fiber/v2"
 10)
 11
 12// Event represents an SSE event
 13type Event struct {
 14	Event string
 15	Data  string
 16	ID    string
 17}
 18
 19func main() {
 20	app := fiber.New()
 21
 22	// Serve HTML page
 23	app.Get("/", func(c *fiber.Ctx) error {
 24		return c.SendString(`
 25<!DOCTYPE html>
 26<html>
 27<head>
 28    <title>SSE Demo</title>
 29</head>
 30<body>
 31    <h1>Server-Sent Events Demo</h1>
 32    <div id="status">Disconnected</div>
 33    <div id="events" style="border:1px solid #ccc; height:400px; overflow-y:auto; padding:10px;"></div>
 34    <script>
 35        const eventSource = new EventSource('/events');
 36
 37        eventSource.onopen = () => {
 38            document.getElementById('status').textContent = 'Connected';
 39            document.getElementById('status').style.color = 'green';
 40        };
 41
 42        eventSource.onmessage = (e) => {
 43            addEvent('message', e.data);
 44        };
 45
 46        eventSource.addEventListener('time', (e) => {
 47            addEvent('time', e.data);
 48        });
 49
 50        eventSource.addEventListener('notification', (e) => {
 51            addEvent('notification', e.data);
 52        });
 53
 54        eventSource.onerror = () => {
 55            document.getElementById('status').textContent = 'Disconnected';
 56            document.getElementById('status').style.color = 'red';
 57        };
 58
 59        function addEvent(type, data) {
 60            const eventsDiv = document.getElementById('events');
 61            const time = new Date().toLocaleTimeString();
 62            eventsDiv.innerHTML += '<p><strong>[' + time + '] ' + type + ':</strong> ' + data + '</p>';
 63            eventsDiv.scrollTop = eventsDiv.scrollHeight;
 64        }
 65    </script>
 66</body>
 67</html>
 68		`)
 69	})
 70
 71	// SSE endpoint
 72	app.Get("/events", func(c *fiber.Ctx) error {
 73		c.Set("Content-Type", "text/event-stream")
 74		c.Set("Cache-Control", "no-cache")
 75		c.Set("Connection", "keep-alive")
 76		c.Set("Transfer-Encoding", "chunked")
 77
 78		c.Context().SetBodyStreamWriter(func(w *bufio.Writer) {
 79			// Send initial message
 80			fmt.Fprintf(w, "event: message\n")
 81			fmt.Fprintf(w, "data: Connected to SSE stream\n\n")
 82			w.Flush()
 83
 84			// Send periodic time updates
 85			ticker := time.NewTicker(2 * time.Second)
 86			defer ticker.Stop()
 87
 88			counter := 0
 89			for {
 90				select {
 91				case <-ticker.C:
 92					counter++
 93
 94					// Send time event
 95					fmt.Fprintf(w, "event: time\n")
 96					fmt.Fprintf(w, "data: Current time: %s\n", time.Now().Format("15:04:05"))
 97					fmt.Fprintf(w, "id: %d\n\n", counter)
 98					w.Flush()
 99
100					// Send notification every 5 updates
101					if counter%5 == 0 {
102						fmt.Fprintf(w, "event: notification\n")
103						fmt.Fprintf(w, "data: You've been connected for %d seconds\n", counter*2)
104						fmt.Fprintf(w, "id: %d\n\n", counter)
105						w.Flush()
106					}
107
108				case <-c.Context().Done():
109					log.Println("Client disconnected")
110					return
111				}
112			}
113		})
114
115		return nil
116	})
117
118	// API endpoint to trigger custom events (simulated)
119	app.Post("/api/notify", func(c *fiber.Ctx) error {
120		var body struct {
121			Message string `json:"message"`
122		}
123		if err := c.BodyParser(&body); err != nil {
124			return c.Status(400).JSON(fiber.Map{"error": "invalid request"})
125		}
126
127		// In production, you'd broadcast this to all SSE clients
128		return c.JSON(fiber.Map{
129			"message": "notification sent",
130			"content": body.Message,
131		})
132	})
133
134	log.Println("Server starting on :3000")
135	log.Fatal(app.Listen(":3000"))
136}

Chi SSE Implementation:

  1// run
  2package main
  3
  4import (
  5	"encoding/json"
  6	"fmt"
  7	"log"
  8	"net/http"
  9	"time"
 10
 11	"github.com/go-chi/chi/v5"
 12	"github.com/go-chi/chi/v5/middleware"
 13)
 14
 15func main() {
 16	r := chi.NewRouter()
 17	r.Use(middleware.Logger)
 18	r.Use(middleware.Recoverer)
 19
 20	// Serve HTML page
 21	r.Get("/", func(w http.ResponseWriter, r *http.Request) {
 22		w.Header().Set("Content-Type", "text/html")
 23		fmt.Fprint(w, `
 24<!DOCTYPE html>
 25<html>
 26<head>
 27    <title>Chi SSE Demo</title>
 28</head>
 29<body>
 30    <h1>Chi Server-Sent Events Demo</h1>
 31    <div id="status">Disconnected</div>
 32    <div id="events" style="border:1px solid #ccc; height:400px; overflow-y:auto; padding:10px;"></div>
 33    <button onclick="triggerNotification()">Trigger Custom Notification</button>
 34    <script>
 35        const eventSource = new EventSource('/events');
 36
 37        eventSource.onopen = () => {
 38            document.getElementById('status').textContent = 'Connected';
 39            document.getElementById('status').style.color = 'green';
 40        };
 41
 42        eventSource.onmessage = (e) => {
 43            addEvent('message', e.data);
 44        };
 45
 46        eventSource.addEventListener('time', (e) => {
 47            addEvent('time', e.data);
 48        });
 49
 50        eventSource.addEventListener('stats', (e) => {
 51            addEvent('stats', e.data);
 52        });
 53
 54        eventSource.onerror = () => {
 55            document.getElementById('status').textContent = 'Disconnected';
 56            document.getElementById('status').style.color = 'red';
 57        };
 58
 59        function addEvent(type, data) {
 60            const eventsDiv = document.getElementById('events');
 61            const time = new Date().toLocaleTimeString();
 62            eventsDiv.innerHTML += '<p><strong>[' + time + '] ' + type + ':</strong> ' + data + '</p>';
 63            eventsDiv.scrollTop = eventsDiv.scrollHeight;
 64        }
 65
 66        function triggerNotification() {
 67            fetch('/api/notify', {
 68                method: 'POST',
 69                headers: { 'Content-Type': 'application/json' },
 70                body: JSON.stringify({ message: 'Custom notification from button click' })
 71            });
 72        }
 73    </script>
 74</body>
 75</html>
 76		`)
 77	})
 78
 79	// SSE endpoint
 80	r.Get("/events", func(w http.ResponseWriter, r *http.Request) {
 81		// Set SSE headers
 82		w.Header().Set("Content-Type", "text/event-stream")
 83		w.Header().Set("Cache-Control", "no-cache")
 84		w.Header().Set("Connection", "keep-alive")
 85
 86		// Make sure the writer supports flushing
 87		flusher, ok := w.(http.Flusher)
 88		if !ok {
 89			http.Error(w, "Streaming unsupported", http.StatusInternalServerError)
 90			return
 91		}
 92
 93		// Send initial message
 94		fmt.Fprintf(w, "event: message\n")
 95		fmt.Fprintf(w, "data: Connected to Chi SSE stream\n\n")
 96		flusher.Flush()
 97
 98		// Create ticker for periodic updates
 99		ticker := time.NewTicker(2 * time.Second)
100		defer ticker.Stop()
101
102		counter := 0
103		for {
104			select {
105			case <-ticker.C:
106				counter++
107
108				// Send time event
109				fmt.Fprintf(w, "event: time\n")
110				fmt.Fprintf(w, "data: Current time: %s\n", time.Now().Format("15:04:05"))
111				fmt.Fprintf(w, "id: %d\n\n", counter)
112				flusher.Flush()
113
114				// Send stats every 3 updates
115				if counter%3 == 0 {
116					statsJSON, _ := json.Marshal(map[string]interface{}{
117						"uptime":  counter * 2,
118						"counter": counter,
119					})
120					fmt.Fprintf(w, "event: stats\n")
121					fmt.Fprintf(w, "data: %s\n\n", statsJSON)
122					flusher.Flush()
123				}
124
125			case <-r.Context().Done():
126				log.Println("Client disconnected from SSE")
127				return
128			}
129		}
130	})
131
132	// API endpoint for custom notifications
133	r.Post("/api/notify", func(w http.ResponseWriter, r *http.Request) {
134		var body struct {
135			Message string `json:"message"`
136		}
137		if err := json.NewDecoder(r.Body).Decode(&body); err != nil {
138			w.WriteHeader(http.StatusBadRequest)
139			json.NewEncoder(w).Encode(map[string]string{
140				"error": "invalid request",
141			})
142			return
143		}
144
145		w.Header().Set("Content-Type", "application/json")
146		json.NewEncoder(w).Encode(map[string]string{
147			"message": "notification received",
148			"content": body.Message,
149		})
150	})
151
152	log.Println("Server starting on :3000")
153	log.Fatal(http.ListenAndServe(":3000", r))
154}

Testing Strategies for Web Frameworks

Comprehensive testing ensures your web applications behave correctly under all conditions. Both frameworks support unit testing, integration testing, and end-to-end testing patterns.

Fiber Testing Patterns

  1// run
  2package main
  3
  4import (
  5	"bytes"
  6	"encoding/json"
  7	"fmt"
  8	"io"
  9	"net/http"
 10	"net/http/httptest"
 11	"testing"
 12
 13	"github.com/gofiber/fiber/v2"
 14)
 15
 16// User model
 17type User struct {
 18	ID    int    `json:"id"`
 19	Name  string `json:"name"`
 20	Email string `json:"email"`
 21}
 22
 23// UserService interface for dependency injection
 24type UserService interface {
 25	GetUser(id int) (*User, error)
 26	CreateUser(name, email string) (*User, error)
 27	ListUsers() ([]*User, error)
 28}
 29
 30// MockUserService for testing
 31type MockUserService struct {
 32	users map[int]*User
 33}
 34
 35func NewMockUserService() *MockUserService {
 36	return &MockUserService{
 37		users: map[int]*User{
 38			1: {ID: 1, Name: "Alice", Email: "alice@example.com"},
 39			2: {ID: 2, Name: "Bob", Email: "bob@example.com"},
 40		},
 41	}
 42}
 43
 44func (m *MockUserService) GetUser(id int) (*User, error) {
 45	if user, ok := m.users[id]; ok {
 46		return user, nil
 47	}
 48	return nil, fmt.Errorf("user not found")
 49}
 50
 51func (m *MockUserService) CreateUser(name, email string) (*User, error) {
 52	id := len(m.users) + 1
 53	user := &User{ID: id, Name: name, Email: email}
 54	m.users[id] = user
 55	return user, nil
 56}
 57
 58func (m *MockUserService) ListUsers() ([]*User, error) {
 59	users := make([]*User, 0, len(m.users))
 60	for _, user := range m.users {
 61		users = append(users, user)
 62	}
 63	return users, nil
 64}
 65
 66// setupApp creates a test Fiber app with dependency injection
 67func setupApp(service UserService) *fiber.App {
 68	app := fiber.New()
 69
 70	app.Get("/users", func(c *fiber.Ctx) error {
 71		users, err := service.ListUsers()
 72		if err != nil {
 73			return c.Status(500).JSON(fiber.Map{"error": err.Error()})
 74		}
 75		return c.JSON(users)
 76	})
 77
 78	app.Get("/users/:id", func(c *fiber.Ctx) error {
 79		id, err := c.ParamsInt("id")
 80		if err != nil {
 81			return c.Status(400).JSON(fiber.Map{"error": "invalid id"})
 82		}
 83
 84		user, err := service.GetUser(id)
 85		if err != nil {
 86			return c.Status(404).JSON(fiber.Map{"error": err.Error()})
 87		}
 88
 89		return c.JSON(user)
 90	})
 91
 92	app.Post("/users", func(c *fiber.Ctx) error {
 93		var body struct {
 94			Name  string `json:"name"`
 95			Email string `json:"email"`
 96		}
 97		if err := c.BodyParser(&body); err != nil {
 98			return c.Status(400).JSON(fiber.Map{"error": "invalid request"})
 99		}
100
101		user, err := service.CreateUser(body.Name, body.Email)
102		if err != nil {
103			return c.Status(500).JSON(fiber.Map{"error": err.Error()})
104		}
105
106		return c.Status(201).JSON(user)
107	})
108
109	return app
110}
111
112// Test helper function
113func performRequest(app *fiber.App, method, url string, body io.Reader) (*http.Response, error) {
114	req := httptest.NewRequest(method, url, body)
115	req.Header.Set("Content-Type", "application/json")
116	return app.Test(req, -1)
117}
118
119// Unit tests
120func TestGetUsers(t *testing.T) {
121	service := NewMockUserService()
122	app := setupApp(service)
123
124	resp, err := performRequest(app, "GET", "/users", nil)
125	if err != nil {
126		t.Fatalf("Request failed: %v", err)
127	}
128
129	if resp.StatusCode != 200 {
130		t.Errorf("Expected status 200, got %d", resp.StatusCode)
131	}
132
133	var users []*User
134	if err := json.NewDecoder(resp.Body).Decode(&users); err != nil {
135		t.Fatalf("Failed to decode response: %v", err)
136	}
137
138	if len(users) != 2 {
139		t.Errorf("Expected 2 users, got %d", len(users))
140	}
141}
142
143func TestGetUserByID(t *testing.T) {
144	service := NewMockUserService()
145	app := setupApp(service)
146
147	tests := []struct {
148		name           string
149		userID         string
150		expectedStatus int
151		expectedName   string
152	}{
153		{"Valid user", "1", 200, "Alice"},
154		{"Another valid user", "2", 200, "Bob"},
155		{"Invalid user", "999", 404, ""},
156		{"Invalid ID format", "abc", 400, ""},
157	}
158
159	for _, tt := range tests {
160		t.Run(tt.name, func(t *testing.T) {
161			resp, err := performRequest(app, "GET", "/users/"+tt.userID, nil)
162			if err != nil {
163				t.Fatalf("Request failed: %v", err)
164			}
165
166			if resp.StatusCode != tt.expectedStatus {
167				t.Errorf("Expected status %d, got %d", tt.expectedStatus, resp.StatusCode)
168			}
169
170			if tt.expectedStatus == 200 {
171				var user User
172				if err := json.NewDecoder(resp.Body).Decode(&user); err != nil {
173					t.Fatalf("Failed to decode response: %v", err)
174				}
175
176				if user.Name != tt.expectedName {
177					t.Errorf("Expected name %s, got %s", tt.expectedName, user.Name)
178				}
179			}
180		})
181	}
182}
183
184func TestCreateUser(t *testing.T) {
185	service := NewMockUserService()
186	app := setupApp(service)
187
188	newUser := map[string]string{
189		"name":  "Charlie",
190		"email": "charlie@example.com",
191	}
192
193	body, _ := json.Marshal(newUser)
194	resp, err := performRequest(app, "POST", "/users", bytes.NewReader(body))
195	if err != nil {
196		t.Fatalf("Request failed: %v", err)
197	}
198
199	if resp.StatusCode != 201 {
200		t.Errorf("Expected status 201, got %d", resp.StatusCode)
201	}
202
203	var user User
204	if err := json.NewDecoder(resp.Body).Decode(&user); err != nil {
205		t.Fatalf("Failed to decode response: %v", err)
206	}
207
208	if user.Name != newUser["name"] {
209		t.Errorf("Expected name %s, got %s", newUser["name"], user.Name)
210	}
211
212	// Verify user was actually created
213	resp2, _ := performRequest(app, "GET", fmt.Sprintf("/users/%d", user.ID), nil)
214	if resp2.StatusCode != 200 {
215		t.Error("Created user not found")
216	}
217}
218
219func TestInvalidJSON(t *testing.T) {
220	service := NewMockUserService()
221	app := setupApp(service)
222
223	invalidJSON := bytes.NewReader([]byte(`{"name": "incomplete`))
224	resp, err := performRequest(app, "POST", "/users", invalidJSON)
225	if err != nil {
226		t.Fatalf("Request failed: %v", err)
227	}
228
229	if resp.StatusCode != 400 {
230		t.Errorf("Expected status 400, got %d", resp.StatusCode)
231	}
232}
233
234func main() {
235	// Run the tests
236	testing.Main(func(pat, str string) (bool, error) {
237		return true, nil
238	}, []testing.InternalTest{
239		{"TestGetUsers", TestGetUsers},
240		{"TestGetUserByID", TestGetUserByID},
241		{"TestCreateUser", TestCreateUser},
242		{"TestInvalidJSON", TestInvalidJSON},
243	}, nil, nil)
244
245	fmt.Println("\nAll tests completed!")
246}

Chi Testing Patterns

  1// run
  2package main
  3
  4import (
  5	"bytes"
  6	"encoding/json"
  7	"fmt"
  8	"net/http"
  9	"net/http/httptest"
 10	"testing"
 11
 12	"github.com/go-chi/chi/v5"
 13)
 14
 15// User model
 16type User struct {
 17	ID    int    `json:"id"`
 18	Name  string `json:"name"`
 19	Email string `json:"email"`
 20}
 21
 22// UserService interface
 23type UserService interface {
 24	GetUser(id int) (*User, error)
 25	CreateUser(name, email string) (*User, error)
 26	ListUsers() ([]*User, error)
 27}
 28
 29// MockUserService implementation
 30type MockUserService struct {
 31	users map[int]*User
 32}
 33
 34func NewMockUserService() *MockUserService {
 35	return &MockUserService{
 36		users: map[int]*User{
 37			1: {ID: 1, Name: "Alice", Email: "alice@example.com"},
 38			2: {ID: 2, Name: "Bob", Email: "bob@example.com"},
 39		},
 40	}
 41}
 42
 43func (m *MockUserService) GetUser(id int) (*User, error) {
 44	if user, ok := m.users[id]; ok {
 45		return user, nil
 46	}
 47	return nil, fmt.Errorf("user not found")
 48}
 49
 50func (m *MockUserService) CreateUser(name, email string) (*User, error) {
 51	id := len(m.users) + 1
 52	user := &User{ID: id, Name: name, Email: email}
 53	m.users[id] = user
 54	return user, nil
 55}
 56
 57func (m *MockUserService) ListUsers() ([]*User, error) {
 58	users := make([]*User, 0, len(m.users))
 59	for _, user := range m.users {
 60		users = append(users, user)
 61	}
 62	return users, nil
 63}
 64
 65// setupRouter creates a test Chi router
 66func setupRouter(service UserService) chi.Router {
 67	r := chi.NewRouter()
 68
 69	r.Get("/users", func(w http.ResponseWriter, r *http.Request) {
 70		users, err := service.ListUsers()
 71		if err != nil {
 72			w.WriteHeader(http.StatusInternalServerError)
 73			json.NewEncoder(w).Encode(map[string]string{"error": err.Error()})
 74			return
 75		}
 76
 77		w.Header().Set("Content-Type", "application/json")
 78		json.NewEncoder(w).Encode(users)
 79	})
 80
 81	r.Get("/users/{id}", func(w http.ResponseWriter, r *http.Request) {
 82		idStr := chi.URLParam(r, "id")
 83		var id int
 84		if _, err := fmt.Sscanf(idStr, "%d", &id); err != nil {
 85			w.WriteHeader(http.StatusBadRequest)
 86			json.NewEncoder(w).Encode(map[string]string{"error": "invalid id"})
 87			return
 88		}
 89
 90		user, err := service.GetUser(id)
 91		if err != nil {
 92			w.WriteHeader(http.StatusNotFound)
 93			json.NewEncoder(w).Encode(map[string]string{"error": err.Error()})
 94			return
 95		}
 96
 97		w.Header().Set("Content-Type", "application/json")
 98		json.NewEncoder(w).Encode(user)
 99	})
100
101	r.Post("/users", func(w http.ResponseWriter, r *http.Request) {
102		var body struct {
103			Name  string `json:"name"`
104			Email string `json:"email"`
105		}
106		if err := json.NewDecoder(r.Body).Decode(&body); err != nil {
107			w.WriteHeader(http.StatusBadRequest)
108			json.NewEncoder(w).Encode(map[string]string{"error": "invalid request"})
109			return
110		}
111
112		user, err := service.CreateUser(body.Name, body.Email)
113		if err != nil {
114			w.WriteHeader(http.StatusInternalServerError)
115			json.NewEncoder(w).Encode(map[string]string{"error": err.Error()})
116			return
117		}
118
119		w.Header().Set("Content-Type", "application/json")
120		w.WriteHeader(http.StatusCreated)
121		json.NewEncoder(w).Encode(user)
122	})
123
124	return r
125}
126
127// Test functions
128func TestGetUsersHandler(t *testing.T) {
129	service := NewMockUserService()
130	router := setupRouter(service)
131
132	req := httptest.NewRequest("GET", "/users", nil)
133	rr := httptest.NewRecorder()
134
135	router.ServeHTTP(rr, req)
136
137	if rr.Code != http.StatusOK {
138		t.Errorf("Expected status 200, got %d", rr.Code)
139	}
140
141	var users []*User
142	if err := json.NewDecoder(rr.Body).Decode(&users); err != nil {
143		t.Fatalf("Failed to decode response: %v", err)
144	}
145
146	if len(users) != 2 {
147		t.Errorf("Expected 2 users, got %d", len(users))
148	}
149}
150
151func TestGetUserByIDHandler(t *testing.T) {
152	service := NewMockUserService()
153	router := setupRouter(service)
154
155	tests := []struct {
156		name           string
157		userID         string
158		expectedStatus int
159		expectedName   string
160	}{
161		{"Valid user", "1", http.StatusOK, "Alice"},
162		{"Another valid user", "2", http.StatusOK, "Bob"},
163		{"Invalid user", "999", http.StatusNotFound, ""},
164		{"Invalid ID format", "abc", http.StatusBadRequest, ""},
165	}
166
167	for _, tt := range tests {
168		t.Run(tt.name, func(t *testing.T) {
169			req := httptest.NewRequest("GET", "/users/"+tt.userID, nil)
170			rr := httptest.NewRecorder()
171
172			router.ServeHTTP(rr, req)
173
174			if rr.Code != tt.expectedStatus {
175				t.Errorf("Expected status %d, got %d", tt.expectedStatus, rr.Code)
176			}
177
178			if tt.expectedStatus == http.StatusOK {
179				var user User
180				if err := json.NewDecoder(rr.Body).Decode(&user); err != nil {
181					t.Fatalf("Failed to decode response: %v", err)
182				}
183
184				if user.Name != tt.expectedName {
185					t.Errorf("Expected name %s, got %s", tt.expectedName, user.Name)
186				}
187			}
188		})
189	}
190}
191
192func TestCreateUserHandler(t *testing.T) {
193	service := NewMockUserService()
194	router := setupRouter(service)
195
196	newUser := map[string]string{
197		"name":  "Charlie",
198		"email": "charlie@example.com",
199	}
200
201	body, _ := json.Marshal(newUser)
202	req := httptest.NewRequest("POST", "/users", bytes.NewReader(body))
203	req.Header.Set("Content-Type", "application/json")
204	rr := httptest.NewRecorder()
205
206	router.ServeHTTP(rr, req)
207
208	if rr.Code != http.StatusCreated {
209		t.Errorf("Expected status 201, got %d", rr.Code)
210	}
211
212	var user User
213	if err := json.NewDecoder(rr.Body).Decode(&user); err != nil {
214		t.Fatalf("Failed to decode response: %v", err)
215	}
216
217	if user.Name != newUser["name"] {
218		t.Errorf("Expected name %s, got %s", newUser["name"], user.Name)
219	}
220
221	// Verify user was created
222	req2 := httptest.NewRequest("GET", fmt.Sprintf("/users/%d", user.ID), nil)
223	rr2 := httptest.NewRecorder()
224	router.ServeHTTP(rr2, req2)
225
226	if rr2.Code != http.StatusOK {
227		t.Error("Created user not found")
228	}
229}
230
231func TestInvalidJSONHandler(t *testing.T) {
232	service := NewMockUserService()
233	router := setupRouter(service)
234
235	invalidJSON := bytes.NewReader([]byte(`{"name": "incomplete`))
236	req := httptest.NewRequest("POST", "/users", invalidJSON)
237	req.Header.Set("Content-Type", "application/json")
238	rr := httptest.NewRecorder()
239
240	router.ServeHTTP(rr, req)
241
242	if rr.Code != http.StatusBadRequest {
243		t.Errorf("Expected status 400, got %d", rr.Code)
244	}
245}
246
247func main() {
248	// Run tests
249	testing.Main(func(pat, str string) (bool, error) {
250		return true, nil
251	}, []testing.InternalTest{
252		{"TestGetUsersHandler", TestGetUsersHandler},
253		{"TestGetUserByIDHandler", TestGetUserByIDHandler},
254		{"TestCreateUserHandler", TestCreateUserHandler},
255		{"TestInvalidJSONHandler", TestInvalidJSONHandler},
256	}, nil, nil)
257
258	fmt.Println("\nAll Chi tests completed!")
259}

Performance Profiling and Optimization

Understanding and optimizing performance is critical for production applications. Both frameworks provide tools and patterns for profiling and optimization.

Performance Benchmarking

  1// run
  2package main
  3
  4import (
  5	"encoding/json"
  6	"fmt"
  7	"io"
  8	"net/http"
  9	"net/http/httptest"
 10	"testing"
 11	"time"
 12
 13	"github.com/go-chi/chi/v5"
 14	"github.com/gofiber/fiber/v2"
 15)
 16
 17// Benchmark data
 18type BenchmarkResult struct {
 19	Framework     string
 20	RequestsPerSec float64
 21	AvgLatency    time.Duration
 22	MemoryUsed    uint64
 23}
 24
 25// Simple handler for benchmarking
 26func simpleHandler(w http.ResponseWriter, r *http.Request) {
 27	w.Header().Set("Content-Type", "application/json")
 28	json.NewEncoder(w).Encode(map[string]string{
 29		"message": "Hello, World!",
 30		"status":  "success",
 31	})
 32}
 33
 34// Setup Chi router
 35func setupChiRouter() chi.Router {
 36	r := chi.NewRouter()
 37	r.Get("/", simpleHandler)
 38	r.Get("/users/{id}", func(w http.ResponseWriter, r *http.Request) {
 39		id := chi.URLParam(r, "id")
 40		w.Header().Set("Content-Type", "application/json")
 41		json.NewEncoder(w).Encode(map[string]string{
 42			"id":   id,
 43			"name": "User " + id,
 44		})
 45	})
 46	r.Post("/users", func(w http.ResponseWriter, r *http.Request) {
 47		var body map[string]interface{}
 48		json.NewDecoder(r.Body).Decode(&body)
 49		w.Header().Set("Content-Type", "application/json")
 50		w.WriteHeader(http.StatusCreated)
 51		json.NewEncoder(w).Encode(body)
 52	})
 53	return r
 54}
 55
 56// Setup Fiber app
 57func setupFiberApp() *fiber.App {
 58	app := fiber.New(fiber.Config{
 59		DisableStartupMessage: true,
 60	})
 61	app.Get("/", func(c *fiber.Ctx) error {
 62		return c.JSON(fiber.Map{
 63			"message": "Hello, World!",
 64			"status":  "success",
 65		})
 66	})
 67	app.Get("/users/:id", func(c *fiber.Ctx) error {
 68		return c.JSON(fiber.Map{
 69			"id":   c.Params("id"),
 70			"name": "User " + c.Params("id"),
 71		})
 72	})
 73	app.Post("/users", func(c *fiber.Ctx) error {
 74		var body map[string]interface{}
 75		c.BodyParser(&body)
 76		return c.Status(201).JSON(body)
 77	})
 78	return app
 79}
 80
 81// Benchmark Chi framework
 82func BenchmarkChiSimpleGet(b *testing.B) {
 83	router := setupChiRouter()
 84	req := httptest.NewRequest("GET", "/", nil)
 85
 86	b.ResetTimer()
 87	b.ReportAllocs()
 88
 89	for i := 0; i < b.N; i++ {
 90		rr := httptest.NewRecorder()
 91		router.ServeHTTP(rr, req)
 92	}
 93}
 94
 95func BenchmarkChiParamGet(b *testing.B) {
 96	router := setupChiRouter()
 97	req := httptest.NewRequest("GET", "/users/123", nil)
 98
 99	b.ResetTimer()
100	b.ReportAllocs()
101
102	for i := 0; i < b.N; i++ {
103		rr := httptest.NewRecorder()
104		router.ServeHTTP(rr, req)
105	}
106}
107
108func BenchmarkChiPost(b *testing.B) {
109	router := setupChiRouter()
110	body := []byte(`{"name":"John","email":"john@example.com"}`)
111
112	b.ResetTimer()
113	b.ReportAllocs()
114
115	for i := 0; i < b.N; i++ {
116		req := httptest.NewRequest("POST", "/users", nil)
117		req.Body = io.NopCloser(bytes.NewReader(body))
118		req.Header.Set("Content-Type", "application/json")
119		rr := httptest.NewRecorder()
120		router.ServeHTTP(rr, req)
121	}
122}
123
124// Benchmark Fiber framework
125func BenchmarkFiberSimpleGet(b *testing.B) {
126	app := setupFiberApp()
127	req := httptest.NewRequest("GET", "/", nil)
128
129	b.ResetTimer()
130	b.ReportAllocs()
131
132	for i := 0; i < b.N; i++ {
133		app.Test(req, -1)
134	}
135}
136
137func BenchmarkFiberParamGet(b *testing.B) {
138	app := setupFiberApp()
139	req := httptest.NewRequest("GET", "/users/123", nil)
140
141	b.ResetTimer()
142	b.ReportAllocs()
143
144	for i := 0; i < b.N; i++ {
145		app.Test(req, -1)
146	}
147}
148
149func BenchmarkFiberPost(b *testing.B) {
150	app := setupFiberApp()
151	body := []byte(`{"name":"John","email":"john@example.com"}`)
152
153	b.ResetTimer()
154	b.ReportAllocs()
155
156	for i := 0; i < b.N; i++ {
157		req := httptest.NewRequest("POST", "/users", bytes.NewReader(body))
158		req.Header.Set("Content-Type", "application/json")
159		app.Test(req, -1)
160	}
161}
162
163func main() {
164	// Run benchmarks manually for demonstration
165	fmt.Println("Running Performance Benchmarks...")
166	fmt.Println("=================================\n")
167
168	fmt.Println("To run actual benchmarks, use:")
169	fmt.Println("  go test -bench=. -benchmem -benchtime=5s")
170	fmt.Println()
171
172	fmt.Println("Expected Results (approximate):")
173	fmt.Println("--------------------------------")
174	fmt.Println("Chi Simple GET:")
175	fmt.Println("  ~500,000 ops/sec, 3-4 allocs/op, 1KB/op")
176	fmt.Println()
177	fmt.Println("Fiber Simple GET:")
178	fmt.Println("  ~800,000 ops/sec, 0-1 allocs/op, 500B/op")
179	fmt.Println()
180	fmt.Println("Chi Param GET:")
181	fmt.Println("  ~400,000 ops/sec, 4-5 allocs/op, 1.5KB/op")
182	fmt.Println()
183	fmt.Println("Fiber Param GET:")
184	fmt.Println("  ~700,000 ops/sec, 1-2 allocs/op, 700B/op")
185	fmt.Println()
186	fmt.Println("Key Takeaway:")
187	fmt.Println("  Fiber is generally 40-60% faster with 50% less memory")
188	fmt.Println("  Chi trades some performance for ecosystem compatibility")
189}

Dependency Injection Patterns

Proper dependency injection makes applications testable, maintainable, and flexible. Both frameworks support various DI patterns.

Fiber with Dependency Injection

  1// run
  2package main
  3
  4import (
  5	"context"
  6	"fmt"
  7	"log"
  8	"time"
  9
 10	"github.com/gofiber/fiber/v2"
 11)
 12
 13// Repository interfaces
 14type UserRepository interface {
 15	GetByID(ctx context.Context, id int) (*User, error)
 16	Create(ctx context.Context, user *User) error
 17	List(ctx context.Context) ([]*User, error)
 18}
 19
 20// Service interfaces
 21type UserService interface {
 22	GetUser(ctx context.Context, id int) (*User, error)
 23	CreateUser(ctx context.Context, name, email string) (*User, error)
 24	ListUsers(ctx context.Context) ([]*User, error)
 25}
 26
 27// Models
 28type User struct {
 29	ID        int       `json:"id"`
 30	Name      string    `json:"name"`
 31	Email     string    `json:"email"`
 32	CreatedAt time.Time `json:"created_at"`
 33}
 34
 35// Implementation: In-memory repository
 36type InMemoryUserRepository struct {
 37	users map[int]*User
 38	nextID int
 39}
 40
 41func NewInMemoryUserRepository() *InMemoryUserRepository {
 42	return &InMemoryUserRepository{
 43		users:  make(map[int]*User),
 44		nextID: 1,
 45	}
 46}
 47
 48func (r *InMemoryUserRepository) GetByID(ctx context.Context, id int) (*User, error) {
 49	if user, ok := r.users[id]; ok {
 50		return user, nil
 51	}
 52	return nil, fmt.Errorf("user not found")
 53}
 54
 55func (r *InMemoryUserRepository) Create(ctx context.Context, user *User) error {
 56	user.ID = r.nextID
 57	user.CreatedAt = time.Now()
 58	r.users[user.ID] = user
 59	r.nextID++
 60	return nil
 61}
 62
 63func (r *InMemoryUserRepository) List(ctx context.Context) ([]*User, error) {
 64	users := make([]*User, 0, len(r.users))
 65	for _, user := range r.users {
 66		users = append(users, user)
 67	}
 68	return users, nil
 69}
 70
 71// Implementation: User service
 72type UserServiceImpl struct {
 73	repo UserRepository
 74}
 75
 76func NewUserService(repo UserRepository) *UserServiceImpl {
 77	return &UserServiceImpl{repo: repo}
 78}
 79
 80func (s *UserServiceImpl) GetUser(ctx context.Context, id int) (*User, error) {
 81	return s.repo.GetByID(ctx, id)
 82}
 83
 84func (s *UserServiceImpl) CreateUser(ctx context.Context, name, email string) (*User, error) {
 85	user := &User{
 86		Name:  name,
 87		Email: email,
 88	}
 89
 90	if err := s.repo.Create(ctx, user); err != nil {
 91		return nil, err
 92	}
 93
 94	return user, nil
 95}
 96
 97func (s *UserServiceImpl) ListUsers(ctx context.Context) ([]*User, error) {
 98	return s.repo.List(ctx)
 99}
100
101// Handlers with dependency injection
102type UserHandler struct {
103	service UserService
104}
105
106func NewUserHandler(service UserService) *UserHandler {
107	return &UserHandler{service: service}
108}
109
110func (h *UserHandler) GetUser(c *fiber.Ctx) error {
111	id, err := c.ParamsInt("id")
112	if err != nil {
113		return c.Status(400).JSON(fiber.Map{"error": "invalid id"})
114	}
115
116	ctx := c.Context()
117	user, err := h.service.GetUser(ctx, id)
118	if err != nil {
119		return c.Status(404).JSON(fiber.Map{"error": err.Error()})
120	}
121
122	return c.JSON(user)
123}
124
125func (h *UserHandler) CreateUser(c *fiber.Ctx) error {
126	var body struct {
127		Name  string `json:"name"`
128		Email string `json:"email"`
129	}
130
131	if err := c.BodyParser(&body); err != nil {
132		return c.Status(400).JSON(fiber.Map{"error": "invalid request"})
133	}
134
135	ctx := c.Context()
136	user, err := h.service.CreateUser(ctx, body.Name, body.Email)
137	if err != nil {
138		return c.Status(500).JSON(fiber.Map{"error": err.Error()})
139	}
140
141	return c.Status(201).JSON(user)
142}
143
144func (h *UserHandler) ListUsers(c *fiber.Ctx) error {
145	ctx := c.Context()
146	users, err := h.service.ListUsers(ctx)
147	if err != nil {
148		return c.Status(500).JSON(fiber.Map{"error": err.Error()})
149	}
150
151	return c.JSON(users)
152}
153
154// Application container for dependency injection
155type Container struct {
156	userRepo    UserRepository
157	userService UserService
158	userHandler *UserHandler
159}
160
161func NewContainer() *Container {
162	// Build dependency graph
163	userRepo := NewInMemoryUserRepository()
164	userService := NewUserService(userRepo)
165	userHandler := NewUserHandler(userService)
166
167	return &Container{
168		userRepo:    userRepo,
169		userService: userService,
170		userHandler: userHandler,
171	}
172}
173
174func main() {
175	// Initialize dependency injection container
176	container := NewContainer()
177
178	// Create Fiber app
179	app := fiber.New(fiber.Config{
180		AppName: "DI Example",
181	})
182
183	// Register routes with injected handlers
184	app.Get("/users", container.userHandler.ListUsers)
185	app.Get("/users/:id", container.userHandler.GetUser)
186	app.Post("/users", container.userHandler.CreateUser)
187
188	// Health check
189	app.Get("/health", func(c *fiber.Ctx) error {
190		return c.JSON(fiber.Map{
191			"status": "healthy",
192			"time":   time.Now(),
193		})
194	})
195
196	log.Fatal(app.Listen(":3000"))
197}

Chi with Dependency Injection

  1// run
  2package main
  3
  4import (
  5	"context"
  6	"encoding/json"
  7	"fmt"
  8	"log"
  9	"net/http"
 10	"strconv"
 11	"time"
 12
 13	"github.com/go-chi/chi/v5"
 14	"github.com/go-chi/chi/v5/middleware"
 15)
 16
 17// Repository interfaces
 18type UserRepository interface {
 19	GetByID(ctx context.Context, id int) (*User, error)
 20	Create(ctx context.Context, user *User) error
 21	List(ctx context.Context) ([]*User, error)
 22}
 23
 24// Service interfaces
 25type UserService interface {
 26	GetUser(ctx context.Context, id int) (*User, error)
 27	CreateUser(ctx context.Context, name, email string) (*User, error)
 28	ListUsers(ctx context.Context) ([]*User, error)
 29}
 30
 31// Models
 32type User struct {
 33	ID        int       `json:"id"`
 34	Name      string    `json:"name"`
 35	Email     string    `json:"email"`
 36	CreatedAt time.Time `json:"created_at"`
 37}
 38
 39// Implementation: In-memory repository
 40type InMemoryUserRepository struct {
 41	users  map[int]*User
 42	nextID int
 43}
 44
 45func NewInMemoryUserRepository() *InMemoryUserRepository {
 46	return &InMemoryUserRepository{
 47		users:  make(map[int]*User),
 48		nextID: 1,
 49	}
 50}
 51
 52func (r *InMemoryUserRepository) GetByID(ctx context.Context, id int) (*User, error) {
 53	if user, ok := r.users[id]; ok {
 54		return user, nil
 55	}
 56	return nil, fmt.Errorf("user not found")
 57}
 58
 59func (r *InMemoryUserRepository) Create(ctx context.Context, user *User) error {
 60	user.ID = r.nextID
 61	user.CreatedAt = time.Now()
 62	r.users[user.ID] = user
 63	r.nextID++
 64	return nil
 65}
 66
 67func (r *InMemoryUserRepository) List(ctx context.Context) ([]*User, error) {
 68	users := make([]*User, 0, len(r.users))
 69	for _, user := range r.users {
 70		users = append(users, user)
 71	}
 72	return users, nil
 73}
 74
 75// Implementation: User service
 76type UserServiceImpl struct {
 77	repo UserRepository
 78}
 79
 80func NewUserService(repo UserRepository) *UserServiceImpl {
 81	return &UserServiceImpl{repo: repo}
 82}
 83
 84func (s *UserServiceImpl) GetUser(ctx context.Context, id int) (*User, error) {
 85	return s.repo.GetByID(ctx, id)
 86}
 87
 88func (s *UserServiceImpl) CreateUser(ctx context.Context, name, email string) (*User, error) {
 89	user := &User{
 90		Name:  name,
 91		Email: email,
 92	}
 93
 94	if err := s.repo.Create(ctx, user); err != nil {
 95		return nil, err
 96	}
 97
 98	return user, nil
 99}
100
101func (s *UserServiceImpl) ListUsers(ctx context.Context) ([]*User, error) {
102	return s.repo.List(ctx)
103}
104
105// Handlers with dependency injection
106type UserHandler struct {
107	service UserService
108}
109
110func NewUserHandler(service UserService) *UserHandler {
111	return &UserHandler{service: service}
112}
113
114func (h *UserHandler) GetUser(w http.ResponseWriter, r *http.Request) {
115	idStr := chi.URLParam(r, "id")
116	id, err := strconv.Atoi(idStr)
117	if err != nil {
118		w.WriteHeader(http.StatusBadRequest)
119		json.NewEncoder(w).Encode(map[string]string{"error": "invalid id"})
120		return
121	}
122
123	user, err := h.service.GetUser(r.Context(), id)
124	if err != nil {
125		w.WriteHeader(http.StatusNotFound)
126		json.NewEncoder(w).Encode(map[string]string{"error": err.Error()})
127		return
128	}
129
130	w.Header().Set("Content-Type", "application/json")
131	json.NewEncoder(w).Encode(user)
132}
133
134func (h *UserHandler) CreateUser(w http.ResponseWriter, r *http.Request) {
135	var body struct {
136		Name  string `json:"name"`
137		Email string `json:"email"`
138	}
139
140	if err := json.NewDecoder(r.Body).Decode(&body); err != nil {
141		w.WriteHeader(http.StatusBadRequest)
142		json.NewEncoder(w).Encode(map[string]string{"error": "invalid request"})
143		return
144	}
145
146	user, err := h.service.CreateUser(r.Context(), body.Name, body.Email)
147	if err != nil {
148		w.WriteHeader(http.StatusInternalServerError)
149		json.NewEncoder(w).Encode(map[string]string{"error": err.Error()})
150		return
151	}
152
153	w.Header().Set("Content-Type", "application/json")
154	w.WriteHeader(http.StatusCreated)
155	json.NewEncoder(w).Encode(user)
156}
157
158func (h *UserHandler) ListUsers(w http.ResponseWriter, r *http.Request) {
159	users, err := h.service.ListUsers(r.Context())
160	if err != nil {
161		w.WriteHeader(http.StatusInternalServerError)
162		json.NewEncoder(w).Encode(map[string]string{"error": err.Error()})
163		return
164	}
165
166	w.Header().Set("Content-Type", "application/json")
167	json.NewEncoder(w).Encode(users)
168}
169
170// Application container
171type Container struct {
172	userRepo    UserRepository
173	userService UserService
174	userHandler *UserHandler
175}
176
177func NewContainer() *Container {
178	userRepo := NewInMemoryUserRepository()
179	userService := NewUserService(userRepo)
180	userHandler := NewUserHandler(userService)
181
182	return &Container{
183		userRepo:    userRepo,
184		userService: userService,
185		userHandler: userHandler,
186	}
187}
188
189func main() {
190	// Initialize dependency injection container
191	container := NewContainer()
192
193	// Create Chi router
194	r := chi.NewRouter()
195	r.Use(middleware.Logger)
196	r.Use(middleware.Recoverer)
197
198	// Register routes
199	r.Get("/users", container.userHandler.ListUsers)
200	r.Get("/users/{id}", container.userHandler.GetUser)
201	r.Post("/users", container.userHandler.CreateUser)
202
203	// Health check
204	r.Get("/health", func(w http.ResponseWriter, r *http.Request) {
205		w.Header().Set("Content-Type", "application/json")
206		json.NewEncoder(w).Encode(map[string]interface{}{
207			"status": "healthy",
208			"time":   time.Now(),
209		})
210	})
211
212	log.Println("Server starting on :3000")
213	log.Fatal(http.ListenAndServe(":3000", r))
214}

Best Practices Summary

Fiber Best Practices

  1. Performance Optimization: Enable prefork, configure timeouts, and use in-memory caching
  2. Memory Management: Set body limits, use connection pooling, and enable compression
  3. Security: Use helmet middleware, encrypt cookies, and implement rate limiting
  4. Monitoring: Use Fiber's built-in monitoring middleware and custom metrics
  5. Deployment: Enable graceful shutdown and configure production settings

Chi Best Practices

  1. Ecosystem Integration: Leverage standard library middleware and third-party tools
  2. Context Management: Use Chi's context patterns for request-scoped data
  3. Middleware Composition: Build reusable middleware using standard patterns
  4. Observability: Integrate with Prometheus, OpenTelemetry, and standard logging
  5. Migration Strategy: Use Chi for gradual migration from standard library

Cross-Framework Considerations

  1. Testing: Write framework-agnostic business logic and framework-specific handlers
  2. Configuration: Use environment variables and configuration files
  3. Error Handling: Implement consistent error responses across frameworks
  4. Documentation: Use OpenAPI/Swagger for API documentation
  5. Security: Implement authentication, authorization, and security headers

Further Reading

Practice Exercises

Exercise 1: High-Performance API Development and Comparison

🎯 Learning Objectives:

  • Build identical REST APIs using both Fiber and Chi frameworks
  • Understand performance differences between fasthttp and net/http ecosystems
  • Implement authentication, rate limiting, and request logging
  • Conduct comprehensive performance benchmarking with realistic scenarios
  • Analyze memory usage, response times, and throughput characteristics

⏱️ Time Estimate: 90-120 minutes
📊 Difficulty: Intermediate
🌍 Real-World Context: A gaming company needs to choose between Fiber and Chi for their real-time multiplayer API. Performance differences directly impact game responsiveness and player experience, making thorough comparison essential.

Task: Develop a comprehensive user management API using both frameworks to understand the performance trade-offs and implementation differences between fasthttp and net/http approaches.

Core Requirements:

  • Complete User CRUD operations with proper validation and error handling
  • JWT-based authentication middleware with token refresh
  • Advanced rate limiting with Redis backend and per-user limits
  • Structured request/response logging with correlation IDs
  • Comprehensive health checks with database connectivity tests
  • Graceful shutdown with connection draining
  • Metrics collection for performance monitoring
  • CORS configuration for web client integration
  • Input validation and sanitization
  • Pagination and filtering for list endpoints

Performance Testing Scenarios:

  • Concurrent Load Testing: 1K, 10K, and 100K concurrent connections
  • Response Size Analysis: Small JSON, medium payloads, and large data responses
  • Route Matching Performance: Simple vs complex route patterns
  • Memory Allocation Patterns: Memory usage under sustained load
  • Middleware Overhead Impact: Performance impact of authentication and logging
  • Database Integration Performance: Query execution under different loads

API Endpoints to Implement:

  • POST /api/auth/register - User registration with email validation
  • POST /api/auth/login - Authentication with JWT tokens
  • POST /api/auth/refresh - Token refresh mechanism
  • GET /api/users - List users with pagination, filtering, and sorting
  • GET /api/users/:id - Get user profile with authorization
  • PUT /api/users/:id - Update user with validation
  • DELETE /api/users/:id - Soft delete user
  • POST /api/users/:id/avatar - File upload handling
  • GET /api/health - System health with dependency checks
  • GET /api/metrics - Performance metrics endpoint
Click to see solution

Fiber Implementation:

  1// fiber_server.go
  2package main
  3
  4import (
  5	"github.com/gofiber/fiber/v2"
  6	"github.com/gofiber/fiber/v2/middleware/logger"
  7	"github.com/gofiber/fiber/v2/middleware/recover"
  8	"github.com/gofiber/fiber/v2/middleware/limiter"
  9	"time"
 10)
 11
 12type User struct {
 13	ID    string `json:"id"`
 14	Name  string `json:"name"`
 15	Email string `json:"email"`
 16}
 17
 18var users = make(map[string]User)
 19var nextID = 1
 20
 21func main() {
 22	app := fiber.New()
 23
 24	// Middleware
 25	app.Use(logger.New())
 26	app.Use(recover.New())
 27	app.Use(limiter.New(limiter.Config{
 28		Max:        100,
 29		Expiration: time.Minute,
 30	}))
 31
 32	// Routes
 33	app.Post("/users", createUser)
 34	app.Get("/users", getUsers)
 35	app.Get("/users/:id", getUser)
 36	app.Put("/users/:id", updateUser)
 37	app.Delete("/users/:id", deleteUser)
 38	app.Get("/health", health)
 39
 40	app.Listen(":8080")
 41}
 42
 43func createUser(c *fiber.Ctx) error {
 44	var user User
 45	if err := c.BodyParser(&user); err != nil {
 46		return err
 47	}
 48
 49	user.ID = string(rune(nextID))
 50	users[user.ID] = user
 51	nextID++
 52
 53	return c.Status(201).JSON(user)
 54}
 55
 56func getUsers(c *fiber.Ctx) error {
 57	var userList []User
 58	for _, user := range users {
 59		userList = append(userList, user)
 60	}
 61	return c.JSON(userList)
 62}
 63
 64func getUser(c *fiber.Ctx) error {
 65	id := c.Params("id")
 66	user, exists := users[id]
 67	if !exists {
 68		return c.Status(404).JSON(fiber.Map{"error": "User not found"})
 69	}
 70	return c.JSON(user)
 71}
 72
 73func updateUser(c *fiber.Ctx) error {
 74	id := c.Params("id")
 75	user, exists := users[id]
 76	if !exists {
 77		return c.Status(404).JSON(fiber.Map{"error": "User not found"})
 78	}
 79
 80	var updates User
 81	if err := c.BodyParser(&updates); err != nil {
 82		return err
 83	}
 84
 85	user.Name = updates.Name
 86	user.Email = updates.Email
 87	users[id] = user
 88
 89	return c.JSON(user)
 90}
 91
 92func deleteUser(c *fiber.Ctx) error {
 93	id := c.Params("id")
 94	delete(users, id)
 95	return c.SendStatus(204)
 96}
 97
 98func health(c *fiber.Ctx) error {
 99	return c.JSON(fiber.Map{
100		"status": "healthy",
101		"time":   time.Now(),
102	})
103}

Chi Implementation:

  1// chi_server.go
  2package main
  3
  4import (
  5	"encoding/json"
  6	"net/http"
  7	"time"
  8
  9	"github.com/go-chi/chi/v5"
 10	"github.com/go-chi/chi/v5/middleware"
 11)
 12
 13type User struct {
 14	ID    string `json:"id"`
 15	Name  string `json:"name"`
 16	Email string `json:"email"`
 17}
 18
 19var users = make(map[string]User)
 20var nextID = 1
 21
 22func main() {
 23	r := chi.NewRouter()
 24
 25	// Middleware
 26	r.Use(middleware.Logger)
 27	r.Use(middleware.Recoverer)
 28	r.Use(middleware.Timeout(60 * time.Second))
 29
 30	// Routes
 31	r.Post("/users", createUser)
 32	r.Get("/users", getUsers)
 33	r.Get("/users/{id}", getUser)
 34	r.Put("/users/{id}", updateUser)
 35	r.Delete("/users/{id}", deleteUser)
 36	r.Get("/health", health)
 37
 38	http.ListenAndServe(":8081", r)
 39}
 40
 41func createUser(w http.ResponseWriter, r *http.Request) {
 42	var user User
 43	if err := json.NewDecoder(r.Body).Decode(&user); err != nil {
 44		http.Error(w, err.Error(), 400)
 45		return
 46	}
 47
 48	user.ID = string(rune(nextID))
 49	users[user.ID] = user
 50	nextID++
 51
 52	w.Header().Set("Content-Type", "application/json")
 53	w.WriteHeader(201)
 54	json.NewEncoder(w).Encode(user)
 55}
 56
 57func getUsers(w http.ResponseWriter, r *http.Request) {
 58	var userList []User
 59	for _, user := range users {
 60		userList = append(userList, user)
 61	}
 62
 63	w.Header().Set("Content-Type", "application/json")
 64	json.NewEncoder(w).Encode(userList)
 65}
 66
 67func getUser(w http.ResponseWriter, r *http.Request) {
 68	id := chi.URLParam(r, "id")
 69	user, exists := users[id]
 70	if !exists {
 71		http.Error(w, "User not found", 404)
 72		return
 73	}
 74
 75	w.Header().Set("Content-Type", "application/json")
 76	json.NewEncoder(w).Encode(user)
 77}
 78
 79func updateUser(w http.ResponseWriter, r *http.Request) {
 80	id := chi.URLParam(r, "id")
 81	user, exists := users[id]
 82	if !exists {
 83		http.Error(w, "User not found", 404)
 84		return
 85	}
 86
 87	var updates User
 88	if err := json.NewDecoder(r.Body).Decode(&updates); err != nil {
 89		http.Error(w, err.Error(), 400)
 90		return
 91	}
 92
 93	user.Name = updates.Name
 94	user.Email = updates.Email
 95	users[id] = user
 96
 97	w.Header().Set("Content-Type", "application/json")
 98	json.NewEncoder(w).Encode(user)
 99}
100
101func deleteUser(w http.ResponseWriter, r *http.Request) {
102	id := chi.URLParam(r, "id")
103	delete(users, id)
104	w.WriteHeader(204)
105}
106
107func health(w http.ResponseWriter, r *http.Request) {
108	w.Header().Set("Content-Type", "application/json")
109	json.NewEncoder(w).Encode(map[string]interface{}{
110		"status": "healthy",
111		"time":   time.Now(),
112	})
113}

Exercise 2: Framework-Agnostic Architecture and Migration Strategy

🎯 Learning Objectives:

  • Design framework-agnostic architecture using dependency injection
  • Create adapter patterns for HTTP framework abstraction
  • Implement clean separation between business logic and transport layer
  • Develop comprehensive migration strategies between frameworks
  • Evaluate trade-offs between performance and ecosystem compatibility

⏱️ Time Estimate: 90-120 minutes
📊 Difficulty: Advanced
🌍 Real-World Context: An enterprise company needs to migrate from Chi to Fiber for performance reasons while maintaining all existing functionality. A well-designed abstraction layer allows gradual migration without disrupting services.

Task: Build a complete framework abstraction layer that enables switching between Fiber and Chi without changing business logic, demonstrating advanced architectural patterns and migration strategies.

Architecture Components:

  • HTTP Abstraction Layer: Framework-independent request/response interfaces
  • Dependency Injection Container: Manage service dependencies and lifecycle
  • Repository Pattern: Data access abstraction with framework independence
  • Service Layer: Business logic completely separated from HTTP concerns
  • Middleware Abstraction: Framework-agnostic middleware implementation
  • Configuration Management: Environment-based configuration with validation
  • Testing Framework: Framework-independent testing strategies

Migration Strategy:

  • Phase 1: Implement abstraction layer alongside existing Chi implementation
  • Phase 2: Migrate services to use abstract interfaces
  • Phase 3: Implement Fiber adapter using same business logic
  • Phase 4: Gradual traffic migration with feature flags
  • Phase 5: Complete migration and cleanup

Advanced Features:

  • Request context propagation across service boundaries
  • Error handling with consistent response formats
  • Input validation using framework-agnostic validators
  • Authentication and authorization abstraction
  • Logging and metrics collection with framework independence
  • Database transaction management across different HTTP contexts
Click to see solution

Abstraction Layer:

  1// framework_adapter.go
  2package main
  3
  4import (
  5	"context"
  6	"encoding/json"
  7	"net/http"
  8)
  9
 10// HTTP response abstraction
 11type Response struct {
 12	StatusCode int
 13	Headers    map[string]string
 14	Body       interface{}
 15}
 16
 17// HTTP request abstraction
 18type Request struct {
 19	Headers    map[string]string
 20	PathParams map[string]string
 21	Query      map[string]string
 22	Body       interface{}
 23}
 24
 25// Framework interface
 26type HTTPFramework interface {
 27	Handle(method, path string, handler func(Request) Response)
 28	Start(addr string) error
 29}
 30
 31// Business logic handlers
 32type UserService struct {
 33	users map[string]User
 34	nextID int
 35}
 36
 37func NewUserService() *UserService {
 38	return &UserService{
 39		users: make(map[string]User),
 40		nextID: 1,
 41	}
 42}
 43
 44func Create(req Request) Response {
 45	var user User
 46	if err := json.Unmarshal([]byte(req.Body.(string)), &user); err != nil {
 47		return Response{
 48			StatusCode: 400,
 49			Body:       map[string]string{"error": err.Error()},
 50		}
 51	}
 52
 53	user.ID = string(rune(s.nextID))
 54	s.users[user.ID] = user
 55	s.nextID++
 56
 57	return Response{
 58		StatusCode: 201,
 59		Body:       user,
 60	}
 61}
 62
 63func Get(req Request) Response {
 64	id := req.PathParams["id"]
 65	user, exists := s.users[id]
 66	if !exists {
 67		return Response{
 68			StatusCode: 404,
 69			Body:       map[string]string{"error": "User not found"},
 70		}
 71	}
 72
 73	return Response{
 74		StatusCode: 200,
 75		Body:       user,
 76	}
 77}
 78
 79// Chi adapter
 80type ChiAdapter struct {
 81	router *chi.Mux
 82}
 83
 84func NewChiAdapter() *ChiAdapter {
 85	return &ChiAdapter{
 86		router: chi.NewRouter(),
 87	}
 88}
 89
 90func Handle(method, path string, handler func(Request) Response) {
 91	a.router.Method(method, path, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
 92		req := Request{
 93			Headers:    make(map[string]string),
 94			PathParams: make(map[string]string),
 95			Query:      make(map[string]string),
 96		}
 97
 98		// Convert chi request to abstract request
 99		for k, v := range r.Header {
100			if len(v) > 0 {
101				req.Headers[k] = v[0]
102			}
103		}
104
105		ctx := chi.RouteContext(r.Context())
106		for _, param := range ctx.URLParams {
107			req.PathParams[param.Key] = param.Value
108		}
109
110		// Handle the request
111		resp := handler(req)
112
113		// Convert abstract response to chi response
114		for k, v := range resp.Headers {
115			w.Header().Set(k, v)
116		}
117
118		w.Header().Set("Content-Type", "application/json")
119		w.WriteHeader(resp.StatusCode)
120		json.NewEncoder(w).Encode(resp.Body)
121	}))
122}
123
124func Start(addr string) error {
125	return http.ListenAndServe(addr, a.router)
126}
127
128// Fiber adapter
129type FiberAdapter struct {
130	app *fiber.App
131}
132
133func NewFiberAdapter() *FiberAdapter {
134	return &FiberAdapter{
135		app: fiber.New(),
136	}
137}
138
139func Handle(method, path string, handler func(Request) Response) {
140	// Similar implementation for Fiber
141}
142
143func Start(addr string) error {
144	return a.app.Listen(addr)
145}

Exercise 3: Real-Time WebSocket Integration

🎯 Learning Objectives:

  • Implement real-time communication using WebSockets in both frameworks
  • Design scalable WebSocket architecture with connection management
  • Handle WebSocket authentication and authorization
  • Implement message routing and room management
  • Monitor WebSocket performance and handle connection failures

⏱️ Time Estimate: 90-120 minutes
📊 Difficulty: Advanced
🌍 Real-World Context: A live streaming platform needs real-time chat and notifications for millions of concurrent users. WebSocket performance and reliability directly impact user engagement and platform success.

Task: Build a comprehensive real-time communication system using WebSockets that showcases the differences in WebSocket implementation between Fiber and Chi frameworks.

Core Features:

  • WebSocket Connection Management: Handle thousands of concurrent connections
  • Authentication: JWT-based WebSocket authentication with token refresh
  • Room/Channel System: Multi-room chat with dynamic joining/leaving
  • Message Broadcasting: Efficient message distribution to subscribers
  • Presence Management: Track online users and their status
  • Message History: Store and retrieve chat history with pagination
  • Push Notifications: Real-time notifications for offline users
  • Rate Limiting: Prevent message flooding and spam

Advanced Patterns:

  • Connection pooling and load balancing
  • Horizontal scaling with Redis pub/sub
  • Message persistence with database integration
  • Client reconnection strategies with exponential backoff
  • Performance monitoring with connection metrics
  • Security measures for WebSocket connections
Click to see solution

Fiber WebSocket Implementation:

  1// internal/websocket/fiber_websocket.go
  2package websocket
  3
  4import (
  5	"encoding/json"
  6	"log/slog"
  7	"sync"
  8	"time"
  9
 10	"github.com/gofiber/fiber/v2"
 11	"github.com/gofiber/fiber/v2/middleware/websocket"
 12	"github.com/gofiber/websocket/v2"
 13)
 14
 15type Hub struct {
 16	clients    map[*Client]bool
 17	broadcast  chan []byte
 18	register   chan *Client
 19	unregister chan *Client
 20	rooms      map[string]map[*Client]bool
 21	mutex      sync.RWMutex
 22}
 23
 24type Client struct {
 25	hub    *Hub
 26	conn   *websocket.Conn
 27	send   chan []byte
 28	roomID string
 29	userID string
 30}
 31
 32type Message struct {
 33	Type      string      `json:"type"`
 34	RoomID    string      `json:"room_id"`
 35	UserID    string      `json:"user_id"`
 36	Content   string      `json:"content"`
 37	Timestamp time.Time   `json:"timestamp"`
 38	Data      interface{} `json:"data,omitempty"`
 39}
 40
 41func NewHub() *Hub {
 42	return &Hub{
 43		clients:    make(map[*Client]bool),
 44		broadcast:  make(chan []byte),
 45		register:   make(chan *Client),
 46		unregister: make(chan *Client),
 47		rooms:      make(map[string]map[*Client]bool),
 48	}
 49}
 50
 51func Run() {
 52	for {
 53		select {
 54		case client := <-h.register:
 55			h.mutex.Lock()
 56			h.clients[client] = true
 57			if h.rooms[client.roomID] == nil {
 58				h.rooms[client.roomID] = make(map[*Client]bool)
 59			}
 60			h.rooms[client.roomID][client] = true
 61			h.mutex.Unlock()
 62
 63		case client := <-h.unregister:
 64			h.mutex.Lock()
 65			if _, ok := h.clients[client]; ok {
 66				delete(h.clients, client)
 67				delete(h.rooms[client.roomID], client)
 68				close(client.send)
 69			}
 70			h.mutex.Unlock()
 71
 72		case message := <-h.broadcast:
 73			h.mutex.RLock()
 74			var msg Message
 75			json.Unmarshal(message, &msg)
 76
 77			// Send to specific room
 78			if room, ok := h.rooms[msg.RoomID]; ok {
 79				for client := range room {
 80					select {
 81					case client.send <- message:
 82					default:
 83						delete(h.clients, client)
 84						delete(h.rooms[msg.RoomID], client)
 85						close(client.send)
 86					}
 87				}
 88			}
 89			h.mutex.RUnlock()
 90		}
 91	}
 92}
 93
 94// Fiber WebSocket handler
 95func SetupWebSocketFiber(app *fiber.App, hub *Hub, logger *slog.Logger) {
 96	app.Use("/ws", websocket.New(websocket.Config{
 97		HandshakeTimeout: 10 * time.Second,
 98	}))
 99
100	app.Get("/ws/:roomID", websocket.New(func(c *websocket.Conn) {
101		roomID := c.Params("roomID")
102		userID := c.Query("user_id")
103		token := c.Query("token")
104
105		// Validate JWT token
106		if !validateToken(token, userID) {
107			logger.Warn("WebSocket authentication failed", "user_id", userID)
108			c.Close()
109			return
110		}
111
112		client := &Client{
113			hub:    hub,
114			conn:   c,
115			send:   make(chan []byte, 256),
116			roomID: roomID,
117			userID: userID,
118		}
119
120		hub.register <- client
121
122		go client.writePump()
123		go client.readPump()
124	}))
125}

Chi WebSocket Implementation:

 1// internal/websocket/chi_websocket.go
 2package websocket
 3
 4import (
 5	"encoding/json"
 6	"log/slog"
 7	"net/http"
 8	"sync"
 9	"time"
10
11	"github.com/go-chi/chi/v5"
12	"github.com/gorilla/websocket"
13)
14
15type ChiHub struct {
16	clients    map[*ChiClient]bool
17	broadcast  chan []byte
18	register   chan *ChiClient
19	unregister chan *ChiClient
20	rooms      map[string]map[*ChiClient]bool
21	mutex      sync.RWMutex
22	upgrader   websocket.Upgrader
23}
24
25type ChiClient struct {
26	hub    *ChiHub
27	conn   *websocket.Conn
28	send   chan []byte
29	roomID string
30	userID string
31}
32
33func NewChiHub() *ChiHub {
34	return &ChiHub{
35		clients:   make(map[*ChiClient]bool),
36		broadcast: make(chan []byte),
37		register:  make(chan *ChiClient),
38		unregister: make(chan *ChiClient),
39		rooms:     make(map[string]map[*ChiClient]bool),
40		upgrader: websocket.Upgrader{
41			CheckOrigin: func(r *http.Request) bool {
42				return true // Configure for production
43			},
44		},
45	}
46}
47
48// Chi WebSocket handler
49func HandleWebSocket(w http.ResponseWriter, r *http.Request) {
50	roomID := chi.URLParam(r, "roomID")
51	userID := r.URL.Query().Get("user_id")
52	token := r.URL.Query().Get("token")
53
54	// Validate JWT token
55	if !validateToken(token, userID) {
56		http.Error(w, "Unauthorized", http.StatusUnauthorized)
57		return
58	}
59
60	conn, err := h.upgrader.Upgrade(w, r, nil)
61	if err != nil {
62		return
63	}
64
65	client := &ChiClient{
66		hub:    h,
67		conn:   conn,
68		send:   make(chan []byte, 256),
69		roomID: roomID,
70		userID: userID,
71	}
72
73	h.register <- client
74
75	go client.writePump()
76	go client.readPump()
77}

Exercise 4: Advanced Middleware Ecosystem

🎯 Learning Objectives:

  • Develop comprehensive middleware suites for both frameworks
  • Implement middleware composition and dependency injection
  • Create performance-optimized middleware with minimal overhead
  • Design middleware that works across different use cases
  • Test middleware performance under various conditions

⏱️ Time Estimate: 75-90 minutes
📊 Difficulty: Intermediate
🌍 Real-World Context: A SaaS platform needs a robust middleware ecosystem for security, monitoring, and cross-cutting concerns. Middleware performance directly impacts overall application responsiveness and scalability.

Task: Build a comprehensive middleware suite that addresses common production concerns while showcasing framework-specific optimizations and patterns.

Middleware Components:

  • Request Correlation: Distributed tracing with correlation IDs
  • Authentication & Authorization: JWT, OAuth2, and RBAC implementation
  • Rate Limiting: Advanced rate limiting with multiple algorithms
  • Caching: Intelligent caching with cache invalidation strategies
  • Security Headers: OWASP security headers and CSP implementation
  • Compression: Smart compression with content-based optimization
  • Logging: Structured logging with sampling and filtering
  • Metrics: Prometheus metrics with custom collectors
  • CORS: Dynamic CORS with origin validation

Advanced Patterns:

  • Conditional middleware execution based on routes
  • Middleware composition pipelines with dependency resolution
  • Performance monitoring with middleware overhead measurement
  • Request transformation and validation pipelines
  • Circuit breaker integration for external service calls
  • Request timeout and cancellation handling
Click to see solution

Fiber Middleware Implementation:

  1// middleware/fiber_middleware.go
  2package middleware
  3
  4import (
  5	"context"
  6	"fmt"
  7	"time"
  8
  9	"github.com/gofiber/fiber/v2"
 10	"github.com/gofiber/fiber/v2/middleware/cors"
 11	"github.com/gofiber/fiber/v2/middleware/compress"
 12	"github.com/gofiber/fiber/v2/middleware/limiter"
 13	"github.com/gofiber/fiber/v2/middleware/logger"
 14	"github.com/gofiber/fiber/v2/middleware/recover"
 15	"github.com/google/uuid"
 16	"github.com/prometheus/client_golang/prometheus"
 17)
 18
 19// Request correlation middleware
 20func CorrelationID() fiber.Handler {
 21	return func(c *fiber.Ctx) error {
 22		correlationID := c.Get("X-Correlation-ID")
 23		if correlationID == "" {
 24			correlationID = uuid.New().String()
 25		}
 26
 27		c.Set("X-Correlation-ID", correlationID)
 28		c.Locals("correlation_id", correlationID)
 29		return c.Next()
 30	}
 31}
 32
 33// Advanced rate limiting with Redis backend
 34func AdvancedRateLimit(redisClient *redis.Client) fiber.Handler {
 35	return limiter.New(limiter.Config{
 36		Max:        100,
 37		Expiration: 1 * time.Minute,
 38		KeyGenerator: func(c *fiber.Ctx) string {
 39			return c.IP() + ":" + c.Path()
 40		},
 41		LimitReached: func(c *fiber.Ctx) error {
 42			return c.Status(fiber.StatusTooManyRequests).JSON(fiber.Map{
 43				"error": "Rate limit exceeded",
 44				"retry_after": "60s",
 45			})
 46		},
 47		Storage: redis.New(redisClient),
 48	})
 49}
 50
 51// Performance metrics middleware
 52func MetricsMiddleware() fiber.Handler {
 53	var (
 54		requestsTotal = prometheus.NewCounterVec(
 55			prometheus.CounterOpts{
 56				Name: "http_requests_total",
 57				Help: "Total number of HTTP requests",
 58			},
 59			[]string{"method", "path", "status"},
 60		)
 61
 62		requestDuration = prometheus.NewHistogramVec(
 63			prometheus.HistogramOpts{
 64				Name:    "http_request_duration_seconds",
 65				Help:    "HTTP request duration in seconds",
 66				Buckets: prometheus.DefBuckets,
 67			},
 68			[]string{"method", "path"},
 69		)
 70	)
 71
 72	prometheus.MustRegister(requestsTotal, requestDuration)
 73
 74	return func(c *fiber.Ctx) error {
 75		start := time.Now()
 76
 77		// Process request
 78		err := c.Next()
 79
 80		// Record metrics
 81		duration := time.Since(start).Seconds()
 82		status := fmt.Sprintf("%d", c.Response().StatusCode())
 83
 84		requestsTotal.WithLabelValues(c.Method(), c.Path(), status).Inc()
 85		requestDuration.WithLabelValues(c.Method(), c.Path()).Observe(duration)
 86
 87		return err
 88	}
 89}
 90
 91// Smart compression middleware
 92func SmartCompression() fiber.Handler {
 93	return compress.New(compress.Config{
 94		Level: compress.LevelBestSpeed,
 95		Next: func(c *fiber.Ctx) bool {
 96			// Don't compress already compressed content
 97			compressibleTypes := map[string]bool{
 98				"application/json": true,
 99				"text/html":        true,
100				"text/css":         true,
101				"text/javascript":  true,
102				"application/xml":  true,
103			}
104
105			contentType := c.Get("Content-Type")
106			return !compressibleTypes[contentType]
107		},
108	})
109}

Chi Middleware Implementation:

 1// middleware/chi_middleware.go
 2package middleware
 3
 4import (
 5	"context"
 6	"net/http"
 7	"time"
 8
 9	"github.com/go-chi/chi/v5/middleware"
10	"github.com/prometheus/client_golang/prometheus"
11	"github.com/google/uuid"
12)
13
14// Request correlation middleware for Chi
15func CorrelationID(next http.Handler) http.Handler {
16	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
17		correlationID := r.Header.Get("X-Correlation-ID")
18		if correlationID == "" {
19			correlationID = uuid.New().String()
20		}
21
22		ctx := context.WithValue(r.Context(), "correlation_id", correlationID)
23		w.Header().Set("X-Correlation-ID", correlationID)
24
25		next.ServeHTTP(w, r.WithContext(ctx))
26	})
27}
28
29// Custom metrics middleware for Chi
30func MetricsMiddleware() func(next http.Handler) http.Handler {
31	var (
32		requestsTotal = prometheus.NewCounterVec(
33			prometheus.CounterOpts{
34				Name: "http_requests_total",
35				Help: "Total number of HTTP requests",
36			},
37			[]string{"method", "path", "status"},
38		)
39
40		requestDuration = prometheus.NewHistogramVec(
41			prometheus.HistogramOpts{
42				Name:    "http_request_duration_seconds",
43				Help:    "HTTP request duration in seconds",
44				Buckets: prometheus.DefBuckets,
45			},
46			[]string{"method", "path"},
47		)
48	)
49
50	prometheus.MustRegister(requestsTotal, requestDuration)
51
52	return func(next http.Handler) http.Handler {
53		return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
54			start := time.Now()
55
56			// Create response writer wrapper to capture status code
57			wrapped := &responseWriter{ResponseWriter: w}
58
59			// Process request
60			next.ServeHTTP(wrapped, r)
61
62			// Record metrics
63			duration := time.Since(start).Seconds()
64			route := chi.RouteContext(r.Context()).RoutePattern()
65
66			requestsTotal.WithLabelValues(r.Method, route, http.StatusText(wrapped.statusCode)).Inc()
67			requestDuration.WithLabelValues(r.Method, route).Observe(duration)
68		})
69	}
70}
71
72type responseWriter struct {
73	http.ResponseWriter
74	statusCode int
75}
76
77func WriteHeader(code int) {
78	rw.statusCode = code
79	rw.ResponseWriter.WriteHeader(code)
80}

Exercise 5: Production-Ready Deployment and Monitoring

🎯 Learning Objectives:

  • Containerize applications with optimization for both frameworks
  • Implement comprehensive health checks and graceful shutdown
  • Set up observability with distributed tracing and metrics
  • Configure production-grade security and hardening
  • Deploy with Kubernetes and manage lifecycle operations

⏱️ Time Estimate: 90-120 minutes
📊 Difficulty: Advanced
🌍 Real-World Context: A DevOps team needs to deploy Fiber and Chi applications to production with proper monitoring, security, and operational readiness. Production deployment requires careful consideration of framework-specific optimizations and requirements.

Task: Create a complete production deployment setup for both Fiber and Chi applications with comprehensive monitoring, security, and operational best practices.

Production Components:

  • Docker Optimization: Multi-stage builds with framework-specific optimizations
  • Health Checks: Comprehensive health endpoints with dependency validation
  • Graceful Shutdown: Proper connection draining and cleanup
  • Security Hardening: Container security, network policies, and runtime protection
  • Monitoring Stack: Prometheus, Grafana, and custom metrics
  • Distributed Tracing: Jaeger integration for request tracing
  • Log Management: Structured logging with aggregation and analysis
  • CI/CD Pipeline: Automated testing, building, and deployment

Kubernetes Deployment:

  • Custom resource definitions for application management
  • Horizontal pod autoscaling with custom metrics
  • ConfigMaps and Secrets management
  • Service mesh integration with Istio
  • Rolling updates and blue-green deployments
  • Backup and disaster recovery procedures
Click to see solution

Fiber Production Dockerfile:

 1# Dockerfile.fiber
 2FROM golang:1.21-alpine AS builder
 3
 4# Install build dependencies
 5RUN apk add --no-cache git ca-certificates tzdata
 6
 7WORKDIR /app
 8COPY go.mod go.sum ./
 9RUN go mod download && go mod verify
10
11# Build with optimizations for Fiber
12RUN CGO_ENABLED=0 GOOS=linux go build \
13    -ldflags="-w -s -X main.version=$(git describe --tags)" \
14    -gcflags="-l=4" \
15    -o fiber-server ./cmd/fiber
16
17# Runtime image
18FROM alpine:latest
19
20# Security hardening
21RUN addgroup -g 1001 -S appgroup && \
22    adduser -u 1001 -S appuser -G appgroup && \
23    apk --no-cache add ca-certificates tzdata curl
24
25WORKDIR /app
26
27# Copy binary and set permissions
28COPY --from=builder /app/fiber-server .
29RUN chmod +x fiber-server && chown appuser:appgroup fiber-server
30
31# Create directories with proper permissions
32RUN mkdir -p /app/logs /app/tmp && \
33    chown -R appuser:appgroup /app && \
34    chmod -R 755 /app
35
36USER appuser
37
38EXPOSE 8080
39
40# Comprehensive health check
41HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \
42    CMD curl -f http://localhost:8080/health || exit 1
43
44CMD ["./fiber-server"]

Chi Production Dockerfile:

 1# Dockerfile.chi
 2FROM golang:1.21-alpine AS builder
 3
 4RUN apk add --no-cache git ca-certificates tzdata
 5
 6WORKDIR /app
 7COPY go.mod go.sum ./
 8RUN go mod download && go mod verify
 9
10# Build with optimizations for Chi
11RUN CGO_ENABLED=0 GOOS=linux go build \
12    -ldflags="-w -s -X main.version=$(git describe --tags)" \
13    -gcflags="-l=4" \
14    -o chi-server ./cmd/chi
15
16FROM alpine:latest
17
18RUN addgroup -g 1001 -S appgroup && \
19    adduser -u 1001 -S appuser -G appgroup && \
20    apk --no-cache add ca-certificates tzdata curl
21
22WORKDIR /app
23
24COPY --from=builder /app/chi-server .
25RUN chmod +x chi-server && chown appuser:appgroup chi-server
26
27RUN mkdir -p /app/logs /app/tmp && \
28    chown -R appuser:appgroup /app && \
29    chmod -R 755 /app
30
31USER appuser
32
33EXPOSE 8080
34
35HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \
36    CMD curl -f http://localhost:8080/health || exit 1
37
38CMD ["./chi-server"]

Kubernetes Deployment Manifest:

 1# k8s/deployment.yaml
 2apiVersion: apps/v1
 3kind: Deployment
 4metadata:
 5  name: fiber-app
 6  labels:
 7    app: fiber-app
 8    version: v1
 9spec:
10  replicas: 3
11  selector:
12    matchLabels:
13      app: fiber-app
14  template:
15    metadata:
16      labels:
17        app: fiber-app
18        version: v1
19      annotations:
20        prometheus.io/scrape: "true"
21        prometheus.io/port: "9090"
22        prometheus.io/path: "/metrics"
23    spec:
24      securityContext:
25        runAsNonRoot: true
26        runAsUser: 1001
27        runAsGroup: 1001
28        fsGroup: 1001
29      containers:
30      - name: fiber-app
31        image: your-registry/fiber-app:latest
32        ports:
33        - containerPort: 8080
34          name: http
35        - containerPort: 9090
36          name: metrics
37        env:
38        - name: GIN_MODE
39          value: "release"
40        - name: LOG_LEVEL
41          value: "info"
42        - name: REDIS_URL
43          valueFrom:
44            secretKeyRef:
45              name: app-secrets
46              key: redis-url
47        resources:
48          requests:
49            memory: "64Mi"
50            cpu: "50m"
51          limits:
52            memory: "256Mi"
53            cpu: "500m"
54        livenessProbe:
55          httpGet:
56            path: /health
57            port: 8080
58          initialDelaySeconds: 30
59          periodSeconds: 10
60        readinessProbe:
61          httpGet:
62            path: /ready
63            port: 8080
64          initialDelaySeconds: 5
65          periodSeconds: 5
66        lifecycle:
67          preStop:
68            exec:
69              command: ["/bin/sh", "-c", "sleep 15"]

This comprehensive guide to Fiber and Chi frameworks demonstrates the trade-offs between performance optimization and ecosystem compatibility. Fiber excels in high-performance scenarios with simple requirements, while Chi shines in enterprise environments where long-term maintainability and ecosystem integration are paramount.

For framework selection guidance and to learn about other frameworks, see Web Frameworks Basics and Gin & Echo Frameworks.

Summary

Framework Comparison at a Glance

Feature Fiber Chi
Performance ⭐⭐⭐⭐⭐ ⭐⭐⭐
Compatibility ⭐⭐⭐⭐⭐
Learning Curve ⭐⭐ ⭐⭐⭐⭐
Ecosystem ⭐⭐⭐⭐⭐
Memory Usage 40-60% less Standard
Middleware Fiber-specific Universal net/http
Monitoring Custom solutions Standard tools
Deployment Simple Enterprise-ready

Decision Matrix

Choose Fiber when:

  • ✅ Performance is the absolute critical requirement
  • ✅ Building high-throughput APIs
  • ✅ Team familiar with Express.js syntax
  • ✅ Rapid prototyping and iteration needed
  • ✅ Simple, focused microservices
  • ✅ Startup environment with flexibility

Choose Chi when:

  • ✅ Ecosystem integration is more important than raw speed
  • ✅ Enterprise applications with long maintenance cycles
  • ✅ Need standard monitoring
  • ✅ Regulatory compliance requires standard tooling
  • ✅ Team values idiomatic Go patterns
  • ✅ Gradual migration from existing net/http code

Key Technical Differences

Fiber Architecture:

Request → FastHTTP Engine → Zero-Allocation Router → Express-like API
             

Chi Architecture:

Request → net/http Server → Context-Based Router → Standard Handlers
             

Core Concepts Recap

Fiber's Performance Advantages:

  • FastHTTP Foundation: 2-3x faster than net/http
  • Zero-Allocation Routing: Reuses memory pools
  • Prefork Mode: Automatic multi-core utilization
  • Express.js Syntax: Familiar to Node.js developers
  • Built-in Middleware: Minimal external dependencies

Chi's Compatibility Advantages:

  • 100% net/http: Works with ALL standard middleware
  • Zero Dependencies: Pure Go standard library
  • Universal Tools: Prometheus, OpenTelemetry, etc.
  • Gradual Migration: Drop-in replacement for ServeMux
  • Context-First: Built on Go's context patterns

Middleware Patterns

Fiber Middleware:

 1func Logger() fiber.Handler {
 2    return func(c *fiber.Ctx) error {
 3        start := time.Now()
 4        err := c.Next()
 5        log.Printf("%s %s %v", c.Method(), c.Path(), time.Since(start))
 6        return err
 7    }
 8}
 9
10app.Use(Logger())

Chi Middleware:

1func Logger(next http.Handler) http.Handler {
2    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
3        start := time.Now()
4        next.ServeHTTP(w, r)
5        log.Printf("%s %s %v", r.Method, r.URL.Path, time.Since(start))
6    })
7}
8
9r.Use(Logger)

Performance Benchmarks

Typical Workload:

Fiber:   200,000+ req/sec  |  Memory: 50MB
Chi:      80,000+ req/sec  |  Memory: 80MB
net/http: 70,000+ req/sec  |  Memory: 90MB

But remember: In production, database, network, and business logic are usually the bottleneck, not the framework.

Production Deployment Checklist

Both Frameworks:

  • ✅ Set proper timeouts
  • ✅ Implement graceful shutdown
  • ✅ Add health check endpoints
  • ✅ Configure logging
  • ✅ Set up error recovery middleware
  • ✅ Enable CORS if needed
  • ✅ Implement rate limiting
  • ✅ Use HTTPS in production

Fiber-Specific:

  • ✅ Disable Prefork in containerized environments
  • ✅ Use Fiber's built-in monitoring
  • ✅ Configure custom error handlers
  • ✅ Set BodyLimit for file uploads

Chi-Specific:

  • ✅ Add Prometheus metrics middleware
  • ✅ Integrate OpenTelemetry tracing
  • ✅ Use standard http.Server configuration
  • ✅ Leverage existing net/http tools

Common Gotchas

Fiber:

  • ⚠️ NOT net/http compatible - Can't use standard middleware
  • ⚠️ Context pooling - Don't store *fiber.Ctx in goroutines
  • ⚠️ Body reuse - Request bodies are recycled
  • ⚠️ Limited ecosystem - Fewer third-party packages
  • ⚠️ FastHTTP quirks - Different from net/http behavior

Chi:

  • ⚠️ More verbose - Requires more boilerplate code
  • ⚠️ Manual JSON - No built-in JSON helpers
  • ⚠️ Performance - Slower than specialized frameworks
  • ⚠️ Context keys - Must use proper types for context values

Migration Strategies

From net/http to Chi:

1// Before: Standard ServeMux
2http.HandleFunc("/users", handleUsers)
3
4// After: Chi router
5r := chi.NewRouter()
6r.Get("/users", handleUsers)  // Same handler signature!

From Chi to Fiber:

1// Chi handler
2func handler(w http.ResponseWriter, r *http.Request) {
3    json.NewEncoder(w).Encode(data)
4}
5
6// Fiber handler
7func handler(c *fiber.Ctx) error {
8    return c.JSON(data)
9}

Real-World Use Cases

Fiber Success Stories:

  • Gaming platforms: Real-time leaderboards, 100K+ concurrent WebSocket connections
  • Analytics dashboards: High-throughput data ingestion
  • IoT platforms: Low-latency device communication
  • Startup MVPs: Rapid development with familiar syntax

Chi Success Stories:

  • Enterprise APIs: Banks, healthcare, regulated industries
  • Monitoring systems: Deep Prometheus/Grafana integration
  • Legacy migrations: Gradual transitions from net/http
  • Long-term projects: 5+ year maintenance horizons

Quick Start Templates

Fiber Production Template:

 1app := fiber.New(fiber.Config{
 2    ErrorHandler: customErrorHandler,
 3    ReadTimeout: 10 * time.Second,
 4    WriteTimeout: 10 * time.Second,
 5})
 6
 7app.Use(logger.New())
 8app.Use(recover.New())
 9app.Use(cors.New())
10app.Use(limiter.New())
11
12app.Get("/health", healthCheck)
13app.Listen(":8080")

Chi Production Template:

 1r := chi.NewRouter()
 2r.Use(middleware.Logger)
 3r.Use(middleware.Recoverer)
 4r.Use(middleware.Timeout(60 * time.Second))
 5
 6r.Get("/health", healthCheck)
 7
 8srv := &http.Server{
 9    Addr: ":8080",
10    Handler: r,
11    ReadTimeout: 10 * time.Second,
12    WriteTimeout: 10 * time.Second,
13}
14srv.ListenAndServe()

Performance Optimization Tips

Fiber:

  • Enable Prefork for multi-core utilization
  • Use c.Fasthttp.Request.Body() for raw speed
  • Disable unnecessary features in Config
  • Use connection pooling for database
  • Consider caching with fiber-cache

Chi:

  • Use custom http.Server with tuned parameters
  • Implement connection pooling in Transport
  • Add response compression middleware
  • Cache compiled templates
  • Use sync.Pool for frequently allocated objects

Next Steps in Your Go Journey

Related Frameworks:

  • Web Frameworks Basics - Framework fundamentals
  • Gin & Echo Frameworks - Alternative lightweight frameworks

Advanced Patterns:

  • Microservices - Building distributed systems
  • Event-Driven Systems - Async communication patterns
  • gRPC Services - High-performance RPC

Deployment & Operations:

  • Kubernetes Operators - Container orchestration
  • Docker Optimization - Container best practices
  • API Gateway - Request routing and management

Remember: Choose Based on Your Constraints

The "best" framework depends on your specific constraints:

Performance-Critical Path:

  • Gaming, real-time analytics, high-frequency trading
  • Systems where microseconds matter
  • Short development cycles with quick iteration

Ecosystem-Critical Path:

  • Enterprise applications, regulated industries
  • Long-term maintenance
  • Deep integration with existing Go tooling

The Hybrid Approach:
Many teams use both: Fiber for performance-critical microservices, Chi for services requiring ecosystem integration.

The frameworks you've mastered here power everything from startup MVPs to enterprise platforms handling millions of requests per second. Choose wisely based on your constraints, and you'll build web services that scale beautifully