gqlgen — Go 用 schema 先写好再让编译器生成 GraphQL server
是什么
gqlgen 是一个 Go 语言 GraphQL 服务器框架。日常类比:像让裁缝先看你画的衣服图纸(schema),再去裁布料缝衣服(生成代码)——而不是裁完一堆通用布条让你运行时拼。
GraphQL 是 Facebook 2015 开源的查询语言,前端可以一次性问 “给我用户名 + 他的所有帖子标题”,后端根据查询返回正好这些字段。Go 写 GraphQL server 历来有两条路:用 反射(graphql-go)每次请求时去字符串匹配字段名,慢且容易在运行时崩;或者全手写 type assertion,啰嗦到让人放弃。
gqlgen 走第三条路:先写 .graphqls schema,跑一次 go generate,工具读 schema 生成一堆 Go struct 和 resolver 接口骨架。你只需要实现这些接口方法。编译器从此就能在编译期发现”你 schema 改了字段但 resolver 没跟上”,运行时几乎零反射。
为什么重要
不理解 gqlgen 的 codegen 套路,下面这些事都没法解释:
- 为什么 Go 圈推 GraphQL 服务时几乎只剩 gqlgen 这一个名字(其它老库都不维护了)
- 为什么 schema-first 比 code-first(用 Go struct 反推 schema)更受 Go 社区欢迎
- 为什么 N+1 查询是用 GraphQL 必踩的坑,不论用什么语言都要配 dataloader
- 为什么 gRPC 用户切到 GraphQL 时会很自然——两者都是 schema 驱动 + codegen 流派
核心要点
gqlgen 的工作流可以拆成 三步:
-
写 schema:用 GraphQL SDL 写
.graphqls文件,定义类型 / 查询 / 突变。类比:先画建筑图纸,告诉施工队每面墙在哪。 -
跑 codegen:
go run github.com/99designs/gqlgen generate,工具读 schema +gqlgen.yml配置,生成generated.go(一万行类型安全胶水)+resolver.go(空方法骨架)。类比:图纸进 CNC 机床,自动切出所有零件。 -
填 resolver:你打开
resolver.go,每个方法填业务逻辑(查数据库、调外部 API)。schema 和 resolver 类型完全对齐,少一个字段、参数错一个类型,编译都过不了。
整套理念叫 schema-first + code generation,对立面是 nestjs/graphql 那种”用装饰器写 Go struct 反推 schema”的 code-first。
实践案例
案例 1:最小 hello world
写一个 schema.graphqls:
type Query { hello(name: String!): String!}跑 go run github.com/99designs/gqlgen init,生成 resolver.go 骨架。打开它实现:
func (r *queryResolver) Hello(ctx context.Context, name string) (string, error) { return "Hello, " + name, nil}启动 server,浏览器开 localhost:8080,输 { hello(name: "Jason") },返回 {"data":{"hello":"Hello, Jason"}}。整个过程你没碰任何反射 / map[string]interface{}。
案例 2:嵌套字段 + N+1 陷阱
schema 加上:
type User { id: ID!, friends: [User!]! }type Query { users: [User!]! }resolver 朴素写法:
func (r *userResolver) Friends(ctx context.Context, u *User) ([]*User, error) { return db.QueryFriends(u.ID) // 每个 user 触发一次 SQL}查 100 个用户,会发 101 次 SQL(1 次查 users + 100 次查 friends)。修复用 dataloader:把同一 tick 内的 ID 攒一批,一次 WHERE id IN (...) 批量查。这是 GraphQL 必备配套。
案例 3:自定义 scalar UUID
GraphQL 内置只有 Int / Float / String / Boolean / ID。要塞 UUID,在 gqlgen.yml:
models: UUID: model: github.com/google/uuid.UUID然后写 MarshalGQL / UnmarshalGQL 两个方法告诉 gqlgen 怎么把 UUID 序列化进 JSON。新人常偷懒直接用 String,结果 schema 里看不出哪些字段必须是 UUID 格式,类型安全的好处折一半。
踩过的坑
-
N+1 查询:嵌套 resolver 默认逐条触发,查列表必踩,必须接 dataloader 批量预取,否则数据库直接被打爆。
-
改 schema 忘跑 generate:手改
.graphqls后没跑go generate,老的 generated.go 还在,编译居然过——但运行时字段对不上。把 generate 加到 CI 是唯一保险。 -
自定义 scalar 没配:UUID / Time 这种业务常用类型直接用
String凑合,schema 看不出格式约束,前端拿到一坨字符串自己解析,类型安全名存实亡。 -
generated.go 太大:大型项目 schema 一两千行,generated.go 上万行,IDE goimports / gopls 卡顿。可拆成 federation 子图,把单服务 schema 控制在 500 行内。
适用 vs 不适用场景
适用:
- 中大型 Go 后端要对外暴露灵活查询接口(前端 / 移动端按需取字段)
- 已用 GraphQL 但当前 server 是 graphql-go 反射版,性能瓶颈想升级
- 团队已习惯 protobuf / OpenAPI 这类 schema-first + codegen 流派
- 多服务架构想做 GraphQL Federation(Apollo Federation 子图)
不适用:
- 内部 RPC 调用(用 grpc-go 更直接,schema + codegen 但二进制更省)
- 极简 CRUD(用 REST + fiber / echo / gin 更轻)
- 团队不愿意把 codegen 加到 build pipeline(schema-first 的硬门槛)
- Node.js 技术栈(直接用 apollo-server / graphql-yoga,生态更大)
历史小故事(可跳过)
- 2012 年:Facebook 内部开发 GraphQL,解决 News Feed 在弱网下”取太多 / 太少字段”的问题。
- 2015 年:GraphQL spec 开源;Go 圈 neelance 写出 graphql-go,用反射实现。
- 2018 年:99designs(设计众包公司)把内部用的 codegen 版本开源,命名 gqlgen,schema-first + 生成代码两大特点直击 graphql-go 痛点。
- 2020 年:加入 GraphQL Foundation 孵化项目,成为 Go 圈事实标准。
- 2023 年:支持 Apollo Federation v2,多服务 GraphQL 架构在 Go 圈站稳。
之后社区其它 Go GraphQL 库基本停滞,gqlgen 一家独大。
学到什么
- schema-first + codegen 是 GraphQL / gRPC / OpenAPI 这类强 schema 协议的共同套路——先定契约,再让工具替你生成胶水
- 反射换编译期检查 是性能 + 类型安全双赢;代价是构建链多一步
go generate - N+1 查询是 GraphQL 的原罪,dataloader 是配套必须品,不是可选
- 选框架时看”对立面”——
gqlgen与 graphql-go 的对比就能告诉你 codegen 派 vs reflection 派各自的取舍
延伸阅读
- 官方文档:gqlgen Getting Started(半小时跑完最小 demo)
- 视频教程:Building GraphQL APIs in Go
- 源码入口:generated.go 模板
- 对比文章:gqlgen vs graphql-go: 2024 benchmarks
- graphql-yoga —— Node.js 圈的 GraphQL server
- apollo-server —— GraphQL 老牌全家桶
- grpc-go —— 同样 schema-first + codegen,但是二进制 RPC
关联
- apollo-server —— Node.js 圈最流行的 GraphQL server,对照 gqlgen 看出 schema-first vs code-first 之争
- graphql-yoga —— 轻量 Node.js GraphQL server,gqlgen 在 Go 圈的对应
- grpc-go —— 同流派:schema-first + codegen,但走二进制协议
- trpc —— 极致简化 schema,TS 端到端类型推导,GraphQL 的另一种解题
- go-zero —— Go 微服务全家桶,常和 gqlgen 一起做对外 GraphQL 网关
- fiber —— Go 高性能 HTTP 框架,可作为 gqlgen 的传输层底座
- prisma —— 数据库层 schema-first + codegen,理念上跟 gqlgen 同源
反向链接
- apollo-server —— Apollo Server — Node 端 GraphQL 服务端的事实标准
- echo —— Echo — 极简高性能 Go 框架,5 行起服务
- fiber —— Fiber — 把 Express 写法搬到 Go 上的高性能 web 框架
- gin —— Gin — Go 写 web API 的事实标准框架
- go-zero —— go-zero — 一份契约文件生成整套 Go 微服务
- graphql-yoga —— GraphQL Yoga — 跨运行时的轻量 GraphQL 服务器
- grpc-go —— gRPC-Go — Google RPC 框架的官方 Go 实现
- hot-chocolate —— Hot Chocolate — .NET 里 code-first 写 GraphQL 服务器
- prisma —— Prisma — 类型安全 ORM
- trpc —— tRPC — TS 端到端类型安全 RPC