字节笔记本
2026年8月30日
go-fastapi:用 Go 快速出带 Swagger 的 API
在 gin 上写接口时,最烦的两件事之一是 handler 里一遍遍 BindJSON,另一件是 Swagger 要手写或靠一堆 struct tag。 sashabaranov/go-fastapi 把这两步收成约定:handler 用结构体进、结构体出,运行时反射绑定 JSON,顺手吐一份 OpenAPI 2.0。GitHub 大约 202 星,Apache-2.0,包文档在 pkg.go.dev。
它很小。仓库最后一次代码提交是 2021 年 12 月 31 日,go.mod 锁的是 Go 1.17、gin v1.7.7。拿来做内部小接口或原型够用,别当成长期维护的框架。
它实际替你做了什么
库一共两个源文件:fastapi.go 管路由和请求,emit_openapi.go 管文档。
AddCall(path, handler)用反射检查函数签名,通过就把 handler 放进 map- gin 接到请求后,从通配参数里取出 path,反序列化 JSON 成入参结构体,调用 handler
- 成功时响应固定包一层:
{"response": <出参>} EmitOpenAPIDefinition()根据入参、出参的字段和jsontag 生成 Swagger 2.0
没有中间件体系,没有 GET/PUT/DELETE,也没有 query / path 参数绑定。输入只认 JSON body,方法只认 POST。
安装
需要本机有 Go 工具链(模块声明是 Go 1.17)。在你的模块里:
go get github.com/sashabaranov/go-fastapipkg.go.dev 上的伪版本是 v0.0.0-20211231174335-50b05b1379e1,对应那次最后提交。没有打 semver tag,go get 拉的就是这个提交。
直接依赖只有两个:
- gin v1.7.7
- go-openapi/spec v0.20.4
新项目如果已经用了更高版本的 gin,Go 会按最小版本选择去调和。gin.Context 的 BindJSON / JSON / Param 这些接口这些年没变,一般能编过;但依赖审计时会看到一份 2021 年的 gin,这点要自己权衡。
最小例子
package main
import (
"github.com/gin-gonic/gin"
"github.com/sashabaranov/go-fastapi"
)
type EchoInput struct {
Phrase string `json:"phrase"`
}
type EchoOutput struct {
OriginalInput EchoInput `json:"original_input"`
}
func EchoHandler(ctx *gin.Context, in EchoInput) (out EchoOutput, err error) {
out.OriginalInput = in
return
}
func main() {
r := gin.Default()
myRouter := fastapi.NewRouter()
myRouter.AddCall("/echo", EchoHandler)
// 必须带 *path,GinHandler 靠 c.Param("path") 找 handler
r.POST("/api/*path", myRouter.GinHandler)
r.Run()
}另开一个终端:
curl -H "Content-Type: application/json" \
-X POST --data '{"phrase": "hello"}' \
localhost:8080/api/echo成功时是:
{"response":{"original_input":{"phrase":"hello"}}}注意外层那个 response。它不是你结构体上的字段,是 GinHandler 写死的包装。对接前端或 SDK 时按这个形状来,别指望直接拿到 EchoOutput。
handler 必须长这样
AddCall 会在注册时 panic,不通过就起不来。源码里的检查是:
- 两个入参:第一个能转成
*gin.Context,第二个必须是 struct(不能是指针、map、基本类型) - 两个返回值:第一个是 struct,第二个实现
error
仓库测试里列了会 panic 的写法:少一个参数、返回值不是 error、第二个入参是 string、出参是 string、第一个入参不是 *gin.Context。正确形态就是 README 那种:
func Xxx(ctx *gin.Context, in SomeStruct) (out OtherStruct, err error)业务错误从 err 返回。非 nil 时库回 500,body 是 {"error": <error 值>}。JSON 解不开回 400:{"error":"invalid request"}。path 没注册过回 404:{"error":"handler not found"}。
ctx 还在,所以鉴权、读 header、写 cookie 仍然走 gin 自己的办法。库不管这些。
通配路径很容易踩
gin 路由必须写成带 *path 的形式,例如 POST /api/*path。请求 POST /api/echo 时,gin 填进 c.Param("path") 的值带前导斜杠,是 /echo。所以 AddCall 的第一个参数也要写成 "/echo",少了斜杠会 404。
一个 GinHandler 挂在这一条通配路由上,底下所有 call 都走同一条。再加接口:
myRouter.AddCall("/echo", EchoHandler)
myRouter.AddCall("/ping", PingHandler)两边都是 POST。想给健康检查做 GET,这条库帮不上,用 gin 自己再挂一条。
每次请求 GinHandler 还会 log.Print 一次 path。开发时能看见命中了哪条,生产环境如果日志量敏感,要自己包一层或者接受它。
吐出 Swagger
注册完 call 之后:
swagger := myRouter.EmitOpenAPIDefinition()
swagger.Info.Title = "My awesome API"
swagger.Info.Version = "1.0"默认 title 是 API generated with go-fastapi,version 1.0,规范是 Swagger 2.0("swagger": "2.0"),不是 OpenAPI 3。每条 path 只填 post,body 参数名固定叫 body,成功响应只声明 200,schema 指到 #/definitions/<结构体名>。
字段名优先用 json tag。json:"-" 的字段不会进 properties。嵌套 struct 会单独进 definitions,测试里 In2 内嵌了 InnerStruct 且 tag 是 json:"-",InnerStruct 仍然出现在 definitions 里,只是父结构的 properties 里看不到它。
类型映射大致是:
- bool / string / 各类 int 和 uint / float32 / float64
- slice、array 变成 array
- map 变成 additionalProperties
- 嵌套 struct 变成
$ref
指针、接口、time.Time 这类没有单独分支,生成时会被跳过。结构体比较扁平时文档能看,复杂模型别指望它覆盖全。
生成结果是 github.com/go-openapi/spec 的 Swagger 对象。可以 json.Marshal 打到标准输出,也可以再挂一条 gin 路由把 JSON 提供出去,给 Swagger UI 或 Redoc 去渲染。库本身不带 UI。
什么时候用,什么时候别用
适合:
- 已经在用 gin,想先把十来个 POST JSON 接口和一份 Swagger 跑起来
- handler 输入输出都是结构体,不想在每个函数里手写 BindJSON
- 内部工具、hackathon、给同事看的草稿 API
不适合:
- 需要 GET、路径参数、query、文件上传、SSE
- 要 OpenAPI 3、鉴权声明、按 tag 分组
- 准备长期升级依赖(仓库停在 2021,gin 也停在 v1.7.7)
- 对反射热路径或
log.Print每条请求有洁癖
同类思路后来有 danielgtaylor/huma、go-chi 配 oapi-codegen、gin 自己的 swagger 生成器(swaggo)等。go-fastapi 的卖点就是短:两个文件,约定大于配置。
仓库:github.com/sashabaranov/go-fastapi,包文档:pkg.go.dev/github.com/sashabaranov/go-fastapi。