#golang /
Go Gin Framework in Practice: High-Performance Web API Development Guide
From Gin framework basics to production-grade project architecture, covering routing, middleware, database integration, graceful shutdown, and other core topics for building high-performance Go Web APIs.
Goal
Go is renowned for its high performance and concise concurrency model. Gin is one of the most popular Web frameworks in the Go ecosystem. This article covers practical topics from Gin framework core features including routing design, middleware development, database integration, and error handling to help developers build maintainable, production-grade API services.
Background
Why Choose Gin
In the Go Web framework ecosystem, Gin's advantages include:
- High performance: Based on httprouter, with extremely fast route matching
- Middleware mechanism: Onion model, flexible and composable
- Error recovery: Built-in panic recovery middleware
- JSON validation: Built-in struct validator
- Active community: Rich documentation, mature ecosystem
Project Architecture
my-api/
├── cmd/
│ └── server/
│ └── main.go # Entry point
├── internal/
│ ├── handler/ # HTTP handlers
│ │ ├── user.go
│ │ └── product.go
│ ├── service/ # Business logic
│ │ ├── user.go
│ │ └── product.go
│ ├── repository/ # Data access layer
│ │ ├── user.go
│ │ └── product.go
│ ├── model/ # Data models
│ │ ├── user.go
│ │ └── product.go
│ ├── middleware/ # Custom middleware
│ │ ├── auth.go
│ │ ├── cors.go
│ │ └── logger.go
│ └── config/ # Configuration management
│ └── config.go
├── pkg/ # Public utility packages
│ └── response/
│ └── response.go
├── go.mod
└── go.sum
Core Implementation
Entry File
// cmd/server/main.go
package main
import (
"context"
"log"
"net/http"
"os"
"os/signal"
"syscall"
"time"
"github.com/gin-gonic/gin"
"my-api/internal/config"
"my-api/internal/handler"
"my-api/internal/middleware"
"my-api/internal/repository"
)
func main() {
// Load configuration
cfg := config.Load()
// Initialize database
db, err := repository.NewDB(cfg.DatabaseURL)
if err != nil {
log.Fatalf("Failed to connect to database: %v", err)
}
defer db.Close()
// Initialize repositories
userRepo := repository.NewUserRepository(db)
productRepo := repository.NewProductRepository(db)
// Initialize handlers
userHandler := handler.NewUserHandler(userRepo)
productHandler := handler.NewProductHandler(productRepo)
// Setup routes
router := setupRouter(userHandler, productHandler)
// Create HTTP server
srv := &http.Server{
Addr: ":" + cfg.Port,
Handler: router,
ReadTimeout: 15 * time.Second,
WriteTimeout: 15 * time.Second,
IdleTimeout: 60 * time.Second,
}
// Graceful shutdown
go func() {
log.Printf("Server starting on port %s", cfg.Port)
if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
log.Fatalf("Server failed: %v", err)
}
}()
// Wait for interrupt signal
quit := make(chan os.Signal, 1)
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
<-quit
log.Println("Shutting down server...")
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
if err := srv.Shutdown(ctx); err != nil {
log.Fatalf("Server forced to shutdown: %v", err)
}
log.Println("Server exited gracefully")
}
func setupRouter(
userHandler *handler.UserHandler,
productHandler *handler.ProductHandler,
) *gin.Engine {
r := gin.New()
// Global middleware
r.Use(middleware.Logger())
middleware.Recovery()
r.Use(middleware.CORS())
r.Use(middleware.RequestID())
// Health check
r.GET("/health", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{"status": "ok"})
})
// API route groups
api := r.Group("/api/v1")
{
// User routes
users := api.Group("/users")
{
users.POST("", userHandler.Create)
users.GET("", userHandler.List)
users.GET("/:id", userHandler.GetByID)
users.PUT("/:id", userHandler.Update)
users.DELETE("/:id", userHandler.Delete)
}
// Product routes
products := api.Group("/products")
{
products.POST("", productHandler.Create)
products.GET("", productHandler.List)
products.GET("/:id", productHandler.GetByID)
}
}
return r
}
Middleware Development
// internal/middleware/auth.go
package middleware
import (
"net/http"
"strings"
"github.com/gin-gonic/gin"
)
func Auth() gin.HandlerFunc {
return func(c *gin.Context) {
authHeader := c.GetHeader("Authorization")
if authHeader == "" {
c.JSON(http.StatusUnauthorized, gin.H{"error": "Authorization header required"})
c.Abort()
return
}
parts := strings.SplitN(authHeader, " ", 2)
if len(parts) != 2 || parts[0] != "Bearer" {
c.JSON(http.StatusUnauthorized, gin.H{"error": "Invalid authorization format"})
c.Abort()
return
}
token := parts[1]
userID, err := ValidateToken(token)
if err != nil {
c.JSON(http.StatusUnauthorized, gin.H{"error": "Invalid token"})
c.Abort()
return
}
// Store user info in context
c.Set("userID", userID)
c.Next()
}
}
// internal/middleware/cors.go
func CORS() 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", "Origin, Content-Type, Authorization")
c.Header("Access-Control-Max-Age", "86400")
if c.Request.Method == "OPTIONS" {
c.AbortWithStatus(http.StatusNoContent)
return
}
c.Next()
}
}
// internal/middleware/logger.go
func Logger() gin.HandlerFunc {
return func(c *gin.Context) {
start := time.Now()
path := c.Request.URL.Path
method := c.Request.Method
c.Next()
latency := time.Since(start)
status := c.Writer.Status()
clientIP := c.ClientIP()
log.Printf("[%s] %s %s %d %v %s",
clientIP, method, path, status, latency, c.Errors.String())
}
}
Handler Layer
// internal/handler/user.go
package handler
import (
"net/http"
"strconv"
"github.com/gin-gonic/gin"
"my-api/internal/model"
"my-api/internal/repository"
"my-api/pkg/response"
)
type UserHandler struct {
repo *repository.UserRepository
}
func NewUserHandler(repo *repository.UserRepository) *UserHandler {
return &UserHandler{repo: repo}
}
type CreateUserRequest struct {
Name string `json:"name" binding:"required,min=2,max=50"`
Email string `json:"email" binding:"required,email"`
Age int `json:"age" binding:"required,gte=0,lte=150"`
}
func (h *UserHandler) Create(c *gin.Context) {
var req CreateUserRequest
if err := c.ShouldBindJSON(&req); err != nil {
response.Error(c, http.StatusBadRequest, "Invalid request", err.Error())
return
}
user := &model.User{
Name: req.Name,
Email: req.Email,
Age: req.Age,
}
if err := h.repo.Create(user); err != nil {
response.Error(c, http.StatusInternalServerError, "Failed to create user", err.Error())
return
}
response.Success(c, http.StatusCreated, user)
}
func (h *UserHandler) GetByID(c *gin.Context) {
id, err := strconv.ParseUint(c.Param("id"), 10, 32)
if err != nil {
response.Error(c, http.StatusBadRequest, "Invalid user ID", err.Error())
return
}
user, err := h.repo.FindByID(uint(id))
if err != nil {
response.Error(c, http.StatusNotFound, "User not found", err.Error())
return
}
response.Success(c, http.StatusOK, user)
}
func (h *UserHandler) List(c *gin.Context) {
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
pageSize, _ := strconv.Atoi(c.DefaultQuery("page_size", "20"))
users, total, err := h.repo.FindAll(page, pageSize)
if err != nil {
response.Error(c, http.StatusInternalServerError, "Failed to list users", err.Error())
return
}
response.Success(c, http.StatusOK, gin.H{
"items": users,
"total": total,
"page": page,
"size": pageSize,
})
}
Unified Response Format
// pkg/response/response.go
package response
import (
"net/http"
"github.com/gin-gonic/gin"
)
type Response struct {
Code int `json:"code"`
Message string `json:"message"`
Data interface{} `json:"data,omitempty"`
Error interface{} `json:"error,omitempty"`
}
func Success(c *gin.Context, statusCode int, data interface{}) {
c.JSON(statusCode, Response{
Code: 0,
Message: "success",
Data: data,
})
}
func Error(c *gin.Context, statusCode int, message string, err interface{}) {
c.JSON(statusCode, Response{
Code: statusCode,
Message: message,
Error: err,
})
}
Database Integration
GORM Integration
// internal/repository/db.go
package repository
import (
"gorm.io/driver/mysql"
"gorm.io/gorm"
"gorm.io/gorm/logger"
)
func NewDB(dsn string) (*gorm.DB, error) {
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{
Logger: logger.Default.LogMode(logger.Info),
})
if err != nil {
return nil, err
}
sqlDB, err := db.DB()
if err != nil {
return nil, err
}
// Connection pool configuration
sqlDB.SetMaxIdleConns(10)
sqlDB.SetMaxOpenConns(100)
sqlDB.SetConnMaxLifetime(time.Hour)
return db, nil
}
// internal/repository/user.go
type UserRepository struct {
db *gorm.DB
}
func NewUserRepository(db *gorm.DB) *UserRepository {
return &UserRepository{db: db}
}
func (r *UserRepository) Create(user *model.User) error {
return r.db.Create(user).Error
}
func (r *UserRepository) FindByID(id uint) (*model.User, error) {
var user model.User
err := r.db.First(&user, id).Error
if err != nil {
return nil, err
}
return &user, nil
}
func (r *UserRepository) FindAll(page, pageSize int) ([]model.User, int64, error) {
var users []model.User
var total int64
r.db.Model(&model.User{}).Count(&total)
err := r.db.Offset((page - 1) * pageSize).Limit(pageSize).Find(&users).Error
if err != nil {
return nil, 0, err
}
return users, total, nil
}
Configuration Management
// internal/config/config.go
package config
import (
"os"
)
type Config struct {
Port string
DatabaseURL string
JWTSecret string
RedisURL string
}
func Load() *Config {
return &Config{
Port: getEnv("PORT", "8080"),
DatabaseURL: getEnv("DATABASE_URL", "root:password@tcp(localhost:3306)/mydb?charset=utf8mb4&parseTime=True&loc=Local"),
JWTSecret: getEnv("JWT_SECRET", "your-secret-key"),
RedisURL: getEnv("REDIS_URL", "localhost:6379"),
}
}
func getEnv(key, fallback string) string {
if value, ok := os.LookupEnv(key); ok {
return value
}
return fallback
}
Testing
// internal/handler/user_test.go
package handler
import (
"bytes"
"encoding/json"
"net/http"
"net/http/httptest"
"testing"
"github.com/gin-gonic/gin"
)
func TestCreateUser(t *testing.T) {
gin.SetMode(gin.TestMode)
router := gin.New()
router.POST("/users", userHandler.Create)
body := CreateUserRequest{
Name: "John",
Email: "john@example.com",
Age: 25,
}
jsonBody, _ := json.Marshal(body)
req, _ := http.NewRequest("POST", "/users", bytes.NewBuffer(jsonBody))
req.Header.Set("Content-Type", "application/json")
w := httptest.NewRecorder()
router.ServeHTTP(w, req)
if w.Code != http.StatusCreated {
t.Errorf("Expected status 201, got %d", w.Code)
}
}
Conclusion
The Gin framework provides high-performance and flexible infrastructure for Go Web development. Key takeaways:
- Layered architecture: Handler -> Service -> Repository with clear responsibilities
- Middleware design: Authentication, logging, CORS, etc. handled uniformly through middleware
- Graceful shutdown: Listen for system signals, wait for requests to complete before shutting down
- Configuration management: Environment variables + default values, supporting different environments
- Test-driven: Use httptest for API testing
Gin's concise API and high-performance characteristics make it an ideal choice for building microservices and RESTful APIs. Mastering these core patterns enables rapid construction of maintainable Go backend services.