# Gin Framework Development Guide **Library**: `github.com/gin-gonic/gin` **Used In**: MVP - HTTP API server **Purpose**: HTTP web framework for building RESTful APIs --- ## Where It's Used - **Primary HTTP framework** for API endpoints - Business registration, resource flow CRUD operations - Match retrieval endpoints - Authentication middleware --- ## Official Documentation - **GitHub**: https://github.com/gin-gonic/gin - **Official Docs**: https://gin-gonic.com/docs/ - **GoDoc**: https://pkg.go.dev/github.com/gin-gonic/gin - **Examples**: https://github.com/gin-gonic/gin/tree/master/examples --- ## Installation ```bash go get github.com/gin-gonic/gin ``` --- ## Key Concepts ### 1. Basic Server Setup ```go package main import "github.com/gin-gonic/gin" func main() { r := gin.Default() // Includes Logger and Recovery middleware // Or minimal setup // r := gin.New() // r.Use(gin.Logger()) // r.Use(gin.Recovery()) r.GET("/health", func(c *gin.Context) { c.JSON(200, gin.H{"status": "ok"}) }) r.Run(":8080") } ``` ### 2. Routes and Handlers ```go // GET request r.GET("/api/businesses/:id", getBusiness) // POST request r.POST("/api/businesses", createBusiness) // PUT request r.PUT("/api/businesses/:id", updateBusiness) // DELETE request r.DELETE("/api/businesses/:id", deleteBusiness) // Handler function func getBusiness(c *gin.Context) { id := c.Param("id") // Get path parameter // ... business logic c.JSON(200, business) } ``` ### 3. Request Binding ```go // JSON binding type BusinessRequest struct { Name string `json:"name" binding:"required"` Email string `json:"email" binding:"required,email"` } func createBusiness(c *gin.Context) { var req BusinessRequest if err := c.ShouldBindJSON(&req); err != nil { c.JSON(400, gin.H{"error": err.Error()}) return } // ... process request c.JSON(201, business) } // Query parameters func searchBusinesses(c *gin.Context) { name := c.Query("name") // ?name=... page := c.DefaultQuery("page", "1") // ?page=... (default: "1") // ... } // Path parameters func getBusiness(c *gin.Context) { id := c.Param("id") // /businesses/:id // ... } ``` ### 4. Middleware ```go // Global middleware r.Use(corsMiddleware()) r.Use(authMiddleware()) // Route-specific middleware r.GET("/protected", authMiddleware(), protectedHandler) // Custom middleware func authMiddleware() gin.HandlerFunc { return func(c *gin.Context) { token := c.GetHeader("Authorization") if token == "" { c.JSON(401, gin.H{"error": "Unauthorized"}) c.Abort() return } // Validate token c.Set("userID", userID) // Store in context c.Next() // Continue to next handler } } // Logging middleware (built-in) r.Use(gin.LoggerWithFormatter(func(param gin.LogFormatterParams) string { return fmt.Sprintf("%s - [%s] \"%s %s %s %d %s \"%s\" %s\"\n", param.ClientIP, param.TimeStamp.Format(time.RFC1123), param.Method, param.Path, param.Request.Proto, param.StatusCode, param.Latency, param.Request.UserAgent(), param.ErrorMessage, ) })) ``` ### 5. Error Handling ```go func handleBusiness(c *gin.Context) { business, err := service.GetBusiness(c.Param("id")) if err != nil { if err == ErrNotFound { c.JSON(404, gin.H{"error": "Business not found"}) return } c.JSON(500, gin.H{"error": "Internal server error"}) return } c.JSON(200, business) } // Custom error handler func errorHandler() gin.HandlerFunc { return func(c *gin.Context) { c.Next() if len(c.Errors) > 0 { c.JSON(500, gin.H{"errors": c.Errors}) } } } ``` ### 6. Grouping Routes ```go api := r.Group("/api") { // All routes prefixed with /api api.GET("/businesses", listBusinesses) // Nested groups v1 := api.Group("/v1") { v1.GET("/businesses", listBusinessesV1) v1.POST("/businesses", createBusinessV1) } v2 := api.Group("/v2") { v2.GET("/businesses", listBusinessesV2) } } // With middleware authenticated := r.Group("/api") authenticated.Use(authMiddleware()) { authenticated.GET("/profile", getProfile) authenticated.POST("/resources", createResource) } ``` ### 7. Context Usage ```go // Get values from context (set by middleware) userID := c.MustGet("userID").(string) // Set value in context c.Set("businessID", businessID) // Get with default page, exists := c.Get("page") if !exists { page = 1 } // Request context (for cancellation, timeouts) ctx := c.Request.Context() ``` ### 8. File Upload ```go func uploadFile(c *gin.Context) { file, err := c.FormFile("file") if err != nil { c.JSON(400, gin.H{"error": err.Error()}) return } // Save file if err := c.SaveUploadedFile(file, "/uploads/"+file.Filename); err != nil { c.JSON(500, gin.H{"error": err.Error()}) return } c.JSON(200, gin.H{"message": "File uploaded"}) } ``` --- ## MVP-Specific Patterns ### Resource Flow Handler Example ```go type ResourceFlowHandler struct { service *ResourceFlowService } func (h *ResourceFlowHandler) Create(c *gin.Context) { var req CreateResourceFlowRequest if err := c.ShouldBindJSON(&req); err != nil { c.JSON(400, gin.H{"error": err.Error()}) return } // Validate business belongs to user userID := c.MustGet("userID").(string) if !h.service.ValidateOwnership(req.BusinessID, userID) { c.JSON(403, gin.H{"error": "Forbidden"}) return } flow, err := h.service.Create(c.Request.Context(), req) if err != nil { c.JSON(500, gin.H{"error": err.Error()}) return } c.JSON(201, flow) } func (h *ResourceFlowHandler) FindMatches(c *gin.Context) { flowID := c.Param("id") matches, err := h.service.FindMatches(c.Request.Context(), flowID) if err != nil { c.JSON(500, gin.H{"error": err.Error()}) return } c.JSON(200, gin.H{"matches": matches}) } // Register routes func setupRoutes(r *gin.Engine, handlers *Handlers) { api := r.Group("/api/v1") resources := api.Group("/resource-flows") resources.Use(authMiddleware()) { resources.POST("", handlers.ResourceFlow.Create) resources.GET("/:id/matches", handlers.ResourceFlow.FindMatches) } } ``` ### Middleware for Our MVP ```go // CORS middleware func corsMiddleware() gin.HandlerFunc { return func(c *gin.Context) { c.Header("Access-Control-Allow-Origin", "*") c.Header("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS") c.Header("Access-Control-Allow-Headers", "Content-Type, Authorization") if c.Request.Method == "OPTIONS" { c.AbortWithStatus(204) return } c.Next() } } // Rate limiting (simple in-memory) func rateLimitMiddleware() gin.HandlerFunc { // Use token bucket or sliding window return func(c *gin.Context) { // Check rate limit c.Next() } } ``` --- ## Performance Tips 1. **Use `gin.New()` instead of `gin.Default()`** if you don't need logger/recovery 2. **Bind only what you need** - avoid binding large structs 3. **Use context for cancellation** - `c.Request.Context()` for timeouts 4. **Reuse gin.Engine** - don't create new engine per request 5. **Use `c.Set()` and `c.Get()`** for request-scoped values --- ## Tutorials & Resources - **Official Examples**: https://github.com/gin-gonic/gin/tree/master/examples - **Go by Example - Gin**: https://gobyexample.com/web-servers (Gin section) - **Building REST APIs**: https://www.alexedwards.net/blog/go-rest-api-structure - **Testing Gin Handlers**: https://gin-gonic.com/docs/testing/ --- ## Common Patterns ### Graceful Shutdown ```go func main() { r := gin.Default() // ... setup routes ... srv := &http.Server{ Addr: ":8080", Handler: r, } go func() { if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed { log.Fatalf("listen: %s\n", err) } }() // Wait for interrupt signal quit := make(chan os.Signal, 1) signal.Notify(quit, os.Interrupt) <-quit ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() if err := srv.Shutdown(ctx); err != nil { log.Fatal("Server forced to shutdown:", err) } } ``` ### Structured Logging with Gin ```go import "github.com/rs/zerolog/log" func loggerMiddleware() gin.HandlerFunc { return func(c *gin.Context) { start := time.Now() path := c.Request.URL.Path c.Next() log.Info(). Str("method", c.Request.Method). Str("path", path). Int("status", c.Writer.Status()). Dur("latency", time.Since(start)). Msg("HTTP request") } } ``` --- ## Best Practices 1. **Don't use global variables** - pass dependencies via handlers 2. **Use dependency injection** - inject services into handlers 3. **Validate early** - use binding validation 4. **Handle errors consistently** - create error response helpers 5. **Use middleware for cross-cutting concerns** - auth, logging, CORS 6. **Version your API** - use route groups (`/api/v1`, `/api/v2`) 7. **Document your API** - consider OpenAPI/Swagger integration