REST 一个资源一个端点,前端要拼好几个接口才能凑齐页面数据。GraphQL 一个 /query 端点,前端自己声明要哪些字段,后端按 schema 返回。这一篇在已有 Gin 项目旁挂一个 GraphQL 服务,用 gqlgen 走 schema-first。
1. 为什么接 GraphQL
前端按需取字段,减少过度拉取和多次往返。强类型 schema 是前后端契约,改字段有编译期保障。它不是 REST 的替代品,文件上传、内部服务间调用仍适合 REST。
2. 定义 schema
gqlgen 是 schema-first:先写 .graphqls 再生成代码。
type Article { id: ID! title: String! views: Int! }
type Query { article(id: ID!): Article }
type Mutation { incViews(id: ID!): Article }
3. 生成代码
go.mod 加 github.com/99designs/gqlgen,放 gqlgen.yml:
schema: - schema.graphqls exec: filename: graph/generated/generated.go model: filename: graph/model/models.go resolver: filename: graph/resolver.go type: Resolver
跑 go run github.com/99designs/gqlgen generate,生成 resolver 骨架和绑定代码,你只填业务逻辑。
4. 实现 resolver
生成的 Resolver 是空结构体,业务依赖通过构造函数注入:
type Resolver struct{ svc ArticleService } func (r *mutationResolver) IncViews(ctx context.Context, id string) (*model.Article, error) { return r.svc.IncViews(ctx, id) } func (r *queryResolver) Article(ctx context.Context, id string) (*model.Article, error) { return r.svc.Get(ctx, id) }
ArticleService 复用第 5 篇的分层,不用为 GraphQL 重写数据层。
5. 挂到 Gin
gqlgen 自带 handler,包一层挂到路由:
import "github.com/99designs/gqlgen/graphql/handler" r.POST("/query", func(c *gin.Context) { h := handler.NewDefaultServer(graph.NewExecutableSchema(graph.Config{Resolvers: &resolver})) h.ServeHTTP(c.Writer, c.Request) })
也可以独立端口跑 GraphQL,Gin 只管 REST 和鉴权。
6. 把登录态传给 resolver
GraphQL 没有 per-field 路由,鉴权在中间件里做,uid 写进 context:
r.POST("/query", AuthRequired(), func(c *gin.Context) { ctx := context.WithValue(c.Request.Context(), "uid", c.MustGet("uid")) h.ServeHTTP(c.Writer, c.Request.WithContext(ctx)) })
resolver 里 ctx.Value("uid") 取当前用户。字段级权限在 resolver 内判断。
7. 解决 N+1
一个 query 拉文章列表再各拉作者,会打 N 次库。gqlgen 配 dataloader,按 id 批量查:
// loader 在 resolver 间共享,收集本请求内的 id 一次性查 authors, _ := dataloader.For(ctx).Article.LoadMany(ids)
一次查询代替 N 次,列表接口延迟从 O(N) 掉到常数级。
8. 防查询滥用
GraphQL 一个请求能嵌套很深,恶意查询拖垮服务。限制查询深度和复杂度:
h := handler.NewDefaultServer(exec) h.Use(extension.FixedComplexityLimit(1000)) // 超复杂度直接拒
配合第 2 篇的限流,双保险。
下一篇讲限流,给这套服务加一道流量闸门。