Building Web APIs with Gin
Set up Gin, routing groups and parameters, middleware, JSON binding and validation, connecting MySQL with GORM, and graceful deployment of a Go web service.
Routing and Parameters
package main
import (
"net/http"
"github.com/gin-gonic/gin"
)
func main() {
r := gin.Default() // Logger + Recovery 中间件
// 路由分组
api := r.Group("/api/v1")
{
api.GET("/users/:id", getUser) // 路径参数
api.POST("/users", createUser)
}
r.Run(":8080") // 启动 HTTP 服务
}
func getUser(c *gin.Context) {
id := c.Param("id") // 路径参数
q := c.Query("q") // 查询参数 ?q=go
page := c.DefaultQuery("page", "1")
c.JSON(http.StatusOK, gin.H{"id": id, "q": q, "page": page})
}
Binding and Validation
type UserCreate struct {
Name string `json:"name" binding:"required,min=2"`
Email string `json:"email" binding:"required,email"`
Age int `json:"age" binding:"gte=0,lte=150"`
}
func createUser(c *gin.Context) {
var body UserCreate
// ShouldBind 根据 Content-Type 自动解析 JSON/Form
if err := c.ShouldBindJSON(&body); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusCreated, body)
}
Middleware
// 自定义中间件:计时
func timing() gin.HandlerFunc {
return func(c *gin.Context) {
start := time.Now()
c.Next() // 执行后续 handler
fmt.Printf("%s %s %v\n", c.Request.Method, c.Request.URL.Path, time.Since(start))
}
}
r.Use(timing()) // 全局
api.Use(authRequired()) // 仅 /api/v1
// JWT 鉴权示例(伪代码)
func authRequired() gin.HandlerFunc {
return func(c *gin.Context) {
token := c.GetHeader("Authorization")
claims, err := parseJWT(token)
if err != nil {
c.AbortWithStatusJSON(401, gin.H{"error": "unauthorized"})
return
}
c.Set("uid", claims.UID) // 在 handler 中 c.MustGet("uid")
c.Next()
}
}
GORM and MySQL
import "gorm.io/gorm"
import "gorm.io/driver/mysql"
type Product struct {
gorm.Model // ID, CreatedAt, UpdatedAt, DeletedAt
Name string `gorm:"size:128;not null"`
Price float64
}
db, err := gorm.Open(mysql.Open(
"user:pass@tcp(127.0.0.1:3306)/shop?charset=utf8mb4&parseTime=True"),
&gorm.Config{})
db.AutoMigrate(&Product{}) // 建表/迁移
db.Create(&Product{Name: "Go Book", Price: 59})
var p Product
db.First(&p, 1) // 主键查询
db.Model(&p).Update("price", 49)
db.Where("price > ?", 50).Find(&list)
Graceful Shutdown
srv := &http.Server{Addr: ":8080", Handler: r}
go func() {
if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
log.Fatal(err)
}
}()
quit := make(chan os.Signal, 1)
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
<-quit // 等待退出信号
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
srv.Shutdown(ctx) // 处理完已有连接再退出
Compile with CGO_ENABLED=0 go build -ldflags='-s -w' to get a small static binary (often 10-20MB) that runs on any Linux — no runtime installation needed.
路由与参数
package main
import (
"net/http"
"github.com/gin-gonic/gin"
)
func main() {
r := gin.Default() // Logger + Recovery 中间件
// 路由分组
api := r.Group("/api/v1")
{
api.GET("/users/:id", getUser) // 路径参数
api.POST("/users", createUser)
}
r.Run(":8080") // 启动 HTTP 服务
}
func getUser(c *gin.Context) {
id := c.Param("id") // 路径参数
q := c.Query("q") // 查询参数 ?q=go
page := c.DefaultQuery("page", "1")
c.JSON(http.StatusOK, gin.H{"id": id, "q": q, "page": page})
}
绑定与校验
type UserCreate struct {
Name string `json:"name" binding:"required,min=2"`
Email string `json:"email" binding:"required,email"`
Age int `json:"age" binding:"gte=0,lte=150"`
}
func createUser(c *gin.Context) {
var body UserCreate
// ShouldBind 根据 Content-Type 自动解析 JSON/Form
if err := c.ShouldBindJSON(&body); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusCreated, body)
}
中间件
// 自定义中间件:计时
func timing() gin.HandlerFunc {
return func(c *gin.Context) {
start := time.Now()
c.Next() // 执行后续 handler
fmt.Printf("%s %s %v\n", c.Request.Method, c.Request.URL.Path, time.Since(start))
}
}
r.Use(timing()) // 全局
api.Use(authRequired()) // 仅 /api/v1
// JWT 鉴权示例(伪代码)
func authRequired() gin.HandlerFunc {
return func(c *gin.Context) {
token := c.GetHeader("Authorization")
claims, err := parseJWT(token)
if err != nil {
c.AbortWithStatusJSON(401, gin.H{"error": "unauthorized"})
return
}
c.Set("uid", claims.UID) // 在 handler 中 c.MustGet("uid")
c.Next()
}
}
GORM 与 MySQL
import "gorm.io/gorm"
import "gorm.io/driver/mysql"
type Product struct {
gorm.Model // ID, CreatedAt, UpdatedAt, DeletedAt
Name string `gorm:"size:128;not null"`
Price float64
}
db, err := gorm.Open(mysql.Open(
"user:pass@tcp(127.0.0.1:3306)/shop?charset=utf8mb4&parseTime=True"),
&gorm.Config{})
db.AutoMigrate(&Product{}) // 建表/迁移
db.Create(&Product{Name: "Go Book", Price: 59})
var p Product
db.First(&p, 1) // 主键查询
db.Model(&p).Update("price", 49)
db.Where("price > ?", 50).Find(&list)
优雅关闭
srv := &http.Server{Addr: ":8080", Handler: r}
go func() {
if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
log.Fatal(err)
}
}()
quit := make(chan os.Signal, 1)
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
<-quit // 等待退出信号
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
srv.Shutdown(ctx) // 处理完已有连接再退出
用 CGO_ENABLED=0 go build -ldflags='-s -w' 编译出的小静态二进制(通常 10-20MB)可在任意 Linux 直接运行,无需安装运行时。