Gin Framework: Routing, Middleware, and Handlers
Introduction
Gin is a lightweight, high-performance web framework for Go. It provides a simple API for building web applications with excellent routing, middleware support, and request handling. This guide covers routing, middleware, handlers, and practical patterns for building production-ready applications with Gin.
Core Concepts
What is Gin?
Gin is a web framework that:
- Provides fast HTTP routing
- Supports middleware chains
- Offers built-in validation
- Includes JSON binding
- Has excellent error handling
Key Components
- Router: Maps HTTP requests to handlers
- Middleware: Processes requests before handlers
- Handlers: Functions that process requests
- Context: Request/response data container
Good: Basic Routing
Setting Up Gin
package main
import (
"github.com/gin-gonic/gin"
"net/http"
)
// โ
GOOD: Basic Gin setup
func main() {
// Create router
router := gin.Default()
// Define routes
router.GET("/", handleHome)
router.GET("/users/:id", handleGetUser)
router.POST("/users", handleCreateUser)
router.PUT("/users/:id", handleUpdateUser)
router.DELETE("/users/:id", handleDeleteUser)
// Start server
router.Run(":8080")
}
func handleHome(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"message": "Welcome to Gin",
})
}
func handleGetUser(c *gin.Context) {
id := c.Param("id")
c.JSON(http.StatusOK, gin.H{
"id": id,
})
}
func handleCreateUser(c *gin.Context) {
var user struct {
Name string `json:"name"`
Email string `json:"email"`
}
if err := c.ShouldBindJSON(&user); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusCreated, user)
}
func handleUpdateUser(c *gin.Context) {
id := c.Param("id")
c.JSON(http.StatusOK, gin.H{
"id": id,
"updated": true,
})
}
func handleDeleteUser(c *gin.Context) {
id := c.Param("id")
c.JSON(http.StatusOK, gin.H{
"id": id,
"deleted": true,
})
}
Route Groups
package main
import (
"github.com/gin-gonic/gin"
"net/http"
)
// โ
GOOD: Route grouping
func setupRoutes(router *gin.Engine) {
// Public routes
public := router.Group("/api/v1")
{
public.GET("/health", handleHealth)
public.POST("/login", handleLogin)
}
// Protected routes
protected := router.Group("/api/v1")
protected.Use(authMiddleware())
{
protected.GET("/users", handleListUsers)
protected.GET("/users/:id", handleGetUser)
protected.POST("/users", handleCreateUser)
}
// Admin routes
admin := router.Group("/api/v1/admin")
admin.Use(authMiddleware(), adminMiddleware())
{
admin.GET("/stats", handleStats)
admin.DELETE("/users/:id", handleDeleteUser)
}
}
func handleHealth(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{"status": "ok"})
}
func handleLogin(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{"token": "jwt-token"})
}
func handleListUsers(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{"users": []string{}})
}
func handleGetUser(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{"id": c.Param("id")})
}
func handleCreateUser(c *gin.Context) {
c.JSON(http.StatusCreated, gin.H{"created": true})
}
func handleDeleteUser(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{"deleted": true})
}
func handleStats(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{"stats": "data"})
}
func authMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
// Check auth
c.Next()
}
}
func adminMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
// Check admin role
c.Next()
}
}
Good: Middleware
Creating Middleware
package main
import (
"fmt"
"github.com/gin-gonic/gin"
"log"
"time"
)
// โ
GOOD: Logging middleware
func loggingMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
start := time.Now()
// Process request
c.Next()
// Log after request
duration := time.Since(start)
log.Printf("%s %s %d %v",
c.Request.Method,
c.Request.URL.Path,
c.Writer.Status(),
duration,
)
}
}
// โ
GOOD: Error handling middleware
func errorHandlingMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
defer func() {
if err := recover(); err != nil {
log.Printf("Panic: %v", err)
c.JSON(500, gin.H{"error": "Internal server error"})
}
}()
c.Next()
}
}
// โ
GOOD: CORS middleware
func corsMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
c.Writer.Header().Set("Access-Control-Allow-Origin", "*")
c.Writer.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE")
c.Writer.Header().Set("Access-Control-Allow-Headers", "Content-Type")
if c.Request.Method == "OPTIONS" {
c.AbortWithStatus(204)
return
}
c.Next()
}
}
// โ
GOOD: Rate limiting middleware
func rateLimitMiddleware(limit int) gin.HandlerFunc {
return func(c *gin.Context) {
// Implement rate limiting logic
c.Next()
}
}
// โ
GOOD: Using middleware
func setupMiddleware(router *gin.Engine) {
router.Use(loggingMiddleware())
router.Use(errorHandlingMiddleware())
router.Use(corsMiddleware())
}
Good: Handlers and Context
Working with Context
package main
import (
"github.com/gin-gonic/gin"
"net/http"
)
// โ
GOOD: Extract query parameters
func handleSearch(c *gin.Context) {
query := c.Query("q")
limit := c.DefaultQuery("limit", "10")
c.JSON(http.StatusOK, gin.H{
"query": query,
"limit": limit,
})
}
// โ
GOOD: Extract form data
func handleForm(c *gin.Context) {
name := c.PostForm("name")
email := c.PostForm("email")
c.JSON(http.StatusOK, gin.H{
"name": name,
"email": email,
})
}
// โ
GOOD: Bind JSON
func handleJSON(c *gin.Context) {
var user struct {
Name string `json:"name" binding:"required"`
Email string `json:"email" binding:"required,email"`
Age int `json:"age" binding:"min=0,max=150"`
}
if err := c.ShouldBindJSON(&user); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, user)
}
// โ
GOOD: Set response headers
func handleHeaders(c *gin.Context) {
c.Header("X-Custom-Header", "value")
c.JSON(http.StatusOK, gin.H{"message": "ok"})
}
// โ
GOOD: Set cookies
func handleCookies(c *gin.Context) {
c.SetCookie("session", "abc123", 3600, "/", "localhost", false, true)
c.JSON(http.StatusOK, gin.H{"message": "cookie set"})
}
// โ
GOOD: Store data in context
func handleContextData(c *gin.Context) {
c.Set("user_id", 123)
c.Set("role", "admin")
// Retrieve in middleware
userID, _ := c.Get("user_id")
c.JSON(http.StatusOK, gin.H{"user_id": userID})
}
Advanced Patterns
Custom Error Handling
package main
import (
"github.com/gin-gonic/gin"
"net/http"
)
// โ
GOOD: Custom error response
type ErrorResponse struct {
Code int `json:"code"`
Message string `json:"message"`
Details string `json:"details,omitempty"`
}
func handleError(c *gin.Context, statusCode int, message string, details string) {
c.JSON(statusCode, ErrorResponse{
Code: statusCode,
Message: message,
Details: details,
})
}
// โ
GOOD: Error handling in handlers
func handleUserWithError(c *gin.Context) {
id := c.Param("id")
// Validate ID
if id == "" {
handleError(c, http.StatusBadRequest, "Invalid user ID", "")
return
}
// Fetch user
user, err := getUser(id)
if err != nil {
handleError(c, http.StatusNotFound, "User not found", err.Error())
return
}
c.JSON(http.StatusOK, user)
}
func getUser(id string) (interface{}, error) {
return nil, nil
}
File Upload Handling
package main
import (
"github.com/gin-gonic/gin"
"net/http"
)
// โ
GOOD: Handle file upload
func handleFileUpload(c *gin.Context) {
file, err := c.FormFile("file")
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "No file"})
return
}
// Save file
if err := c.SaveUploadedFile(file, "./uploads/"+file.Filename); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "Upload failed"})
return
}
c.JSON(http.StatusOK, gin.H{
"filename": file.Filename,
"size": file.Size,
})
}
// โ
GOOD: Handle multiple files
func handleMultipleFiles(c *gin.Context) {
form, _ := c.MultipartForm()
files := form.File["files"]
for _, file := range files {
c.SaveUploadedFile(file, "./uploads/"+file.Filename)
}
c.JSON(http.StatusOK, gin.H{
"count": len(files),
})
}
Best Practices
1. Use Route Groups
// โ
GOOD: Organize routes with groups
api := router.Group("/api/v1")
{
api.GET("/users", listUsers)
api.POST("/users", createUser)
}
// โ BAD: Flat route structure
router.GET("/api/v1/users", listUsers)
router.POST("/api/v1/users", createUser)
2. Validate Input
// โ
GOOD: Validate with binding tags
type User struct {
Name string `json:"name" binding:"required,min=3,max=50"`
Email string `json:"email" binding:"required,email"`
}
// โ BAD: No validation
type BadUser struct {
Name string
Email string
}
3. Use Middleware for Cross-Cutting Concerns
// โ
GOOD: Middleware for auth
router.Use(authMiddleware())
// โ BAD: Auth in every handler
func handler(c *gin.Context) {
// Check auth
// Do work
}
Resources
- Gin Documentation: https://gin-gonic.com/
- Gin GitHub: https://github.com/gin-gonic/gin
- HTTP Status Codes: https://httpwg.org/specs/rfc7231.html#status.codes
Summary
Gin is a powerful, lightweight web framework for Go. Use route groups to organize endpoints, middleware for cross-cutting concerns, and proper error handling for robust applications. Gin’s simplicity and performance make it ideal for building REST APIs and web services.
Comments