Gin 是 Go 生态里最常用的一层 Web 框架:把路由、参数解析、中间件串起来,不替你决定 ORM、配置、部署方式。这一篇带你从空目录走到第一个能返回 JSON 的接口,跑通之后后面七篇都是在它上面加东西。
环境要求 Go 1.22 以上。下面所有命令都能直接复制执行。
1. 初始化项目
新建目录并初始化模块,模块名用你自己的仓库路径,本地练习写 demo 也行:
mkdir gin-demo && cd gin-demo go mod init demo go get github.com/gin-gonic/gin@v1.10.0
go get 会把下面的依赖写进 go.mod。如果拉取慢,先设国内代理:go env -w GOPROXY=https://goproxy.cn,direct。
2. 写第一个 handler
建 main.go:
package main import "github.com/gin-gonic/gin" func main() { r := gin.Default() r.GET("/ping", func(c *gin.Context) { c.JSON(200, gin.H{ "message": "pong", }) }) // 默认监听 :8080 r.Run() }
gin.Default() 返回一个带 Logger 和 Recovery 中间件的引擎,新手直接用它最省心。gin.H 是 map[string]any 的缩写,用来快速拼 JSON。r.Run() 不填参数时监听 0.0.0.0:8080。
跑起来:
go run main.go
另开一个终端验证:
curl http://127.0.0.1:8080/ping # {"message":"pong"}
3. 拿到 URL 里的参数
真实接口几乎都要读路径参数和查询参数。Gin 用 c.Param 取路径段,用 c.Query 取问号后的字段:
r.GET("/users/:id", func(c *gin.Context) { id := c.Param("id") // 路径 /users/42 里的 42 page := c.DefaultQuery("page", "1") // /users/42?page=2,缺省给 "1" q := c.Query("q") // 没有就返回空串 c.JSON(200, gin.H{ "id": id, "page": page, "q": q, }) })
:id 是单段通配,遇到 / 就截止。要匹配一整段含斜杠的路径用 *action,比如 /files/*action 能吃掉 /files/a/b/c。
4. 接收 JSON 请求体
写接口时前端一般 POST 一段 JSON。用结构体承接,再 ShouldBindJSON 解析:
type LoginReq struct { Username string `json:"username" binding:"required"` Password string `json:"password" binding:"required"` } r.POST("/login", func(c *gin.Context) { var req LoginReq if err := c.ShouldBindJSON(&req); err != nil { c.JSON(400, gin.H{"error": err.Error()}) return } c.JSON(200, gin.H{"username": req.Username}) })
binding:"required" 让 Gin 在字段缺失时直接返回错误。这一块的完整校验规则放到第 2 篇细讲。
5. 路由分组
接口一多就需要统一前缀和中间件。用 Group 把一批路由归到 /api/v1 下:
v1 := r.Group("/api/v1") { v1.GET("/articles", listArticles) v1.POST("/articles", createArticle) }
花括号只是代码块,没有语法含义,纯粹让分组更醒目。
6. 热重载
每次改代码都 go run 太烦。装 air 监听文件变化自动重启:
go install github.com/air-verse/air@latest
在项目根目录建 .air.toml:
[build] cmd = "go run main.go" bin = "tmp/main" include_ext = ["go", "html", "toml"] exclude_dir = ["tmp", "vendor"]
然后直接敲 air,改完代码保存就自动重启。
7. 推荐的目录雏形
单文件能跑通,但文章一多就会乱。先把结构立起来,后面第 5 篇会扩展成完整分层:
gin-demo/
├── main.go # 启动入口
├── router/
│ └── router.go # 路由注册
├── handler/
│ └── user.go # 具体接口实现
└── go.mod
把 r.GET(...) 里的匿名函数抽到 handler/user.go,在 router.go 里注册,main 只负责装配。这样每加一个模块,改动都收敛在一两个文件里。
下一篇讲路由的四种方法、参数绑定的全部写法,以及 binding 标签能做哪些校验。