Skip to main content
โšก Calmops

Gin Framework: Routing, Middleware, and Handlers

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

  1. Router: Maps HTTP requests to handlers
  2. Middleware: Processes requests before handlers
  3. Handlers: Functions that process requests
  4. 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

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