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:
- Performance Configuration: Prefork mode, memory optimization, and proper timeouts
- Comprehensive Middleware: Recovery, logging, CORS, rate limiting, and custom metrics
- Error Handling: Custom error handler with proper HTTP status codes
- Input Validation: Parameter validation with meaningful error messages
- File Upload Handling: Secure file upload with type and size validation
- Business Logic: Real-world validation rules and data management
- 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:
- Smart Compression: Content-aware compression that skips already compressed files
- Intelligent Caching: In-memory cache with TTL and cleanup
- WebSocket Support: Real-time communication with room management
- Security Headers: Cookie encryption and timeout handling
- Performance Monitoring: Cache hit/miss tracking and request timing
- 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.Handlerorhttp.HandlerFuncwithout 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:
fiber.New()creates a new Fiber instance with configurationfiber.Ctxis Fiber's context that combines request and response functionalityc.Params("id")extracts URL path parametersc.Query()andc.QueryInt()extract query parametersc.JSON()automatically sets content-type and encodes JSON responsec.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:
chi.NewRouter()creates a new router that implementshttp.Handlerr.Use()adds standard net/http middlewarechi.URLParam(r, "id")extracts URL parameters stored in request context- Route Groups`) create sub-routers with shared middleware
- Standard Response Writing: Uses
http.ResponseWriterdirectly 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
- Performance Optimization: Enable prefork, configure timeouts, and use in-memory caching
- Memory Management: Set body limits, use connection pooling, and enable compression
- Security: Use helmet middleware, encrypt cookies, and implement rate limiting
- Monitoring: Use Fiber's built-in monitoring middleware and custom metrics
- Deployment: Enable graceful shutdown and configure production settings
Chi Best Practices
- Ecosystem Integration: Leverage standard library middleware and third-party tools
- Context Management: Use Chi's context patterns for request-scoped data
- Middleware Composition: Build reusable middleware using standard patterns
- Observability: Integrate with Prometheus, OpenTelemetry, and standard logging
- Migration Strategy: Use Chi for gradual migration from standard library
Cross-Framework Considerations
- Testing: Write framework-agnostic business logic and framework-specific handlers
- Configuration: Use environment variables and configuration files
- Error Handling: Implement consistent error responses across frameworks
- Documentation: Use OpenAPI/Swagger for API documentation
- Security: Implement authentication, authorization, and security headers
Further Reading
- Web Frameworks Basics - Selection Guide
- Gin & Echo Frameworks - Complete Guide
- REST API Design Patterns
- Middleware and Request Processing
- Performance Optimization Techniques
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 validationPOST /api/auth/login- Authentication with JWT tokensPOST /api/auth/refresh- Token refresh mechanismGET /api/users- List users with pagination, filtering, and sortingGET /api/users/:id- Get user profile with authorizationPUT /api/users/:id- Update user with validationDELETE /api/users/:id- Soft delete userPOST /api/users/:id/avatar- File upload handlingGET /api/health- System health with dependency checksGET /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.Ctxin 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
Preforkfor 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.Serverwith tuned parameters - Implement connection pooling in Transport
- Add response compression middleware
- Cache compiled templates
- Use
sync.Poolfor 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