云计算百科
云计算领域专业知识百科平台

gqlgen 入门实战:用 Go 构建类型安全的 GraphQL 服务器

  • 后端
  • GraphQL
  • 代码生成

【免费下载链接】gqlgen

go generate based graphql server library

项目地址:
https://gitcode.com/gh_mirrors/gq/gqlgen

点击查看 免费下载

本篇教程基于 gqlgen 官方 Getting Started 指南展开,完整演示如何从零搭建一个具备"查询待办列表、创建待办、标记完成"能力的 GraphQL 服务器。你将掌握 gqlgen 的 schema-first 开发流程:项目初始化、schema 定义、resolver 生成与实现、autobind 模型绑定、字段级 resolver 懒加载,以及用 go generate 固化代码生成流程——这些正是 gqlgen 仓库中 _examples/todo 示例所对应的完整实战路径。

为什么用 schema-first 方式开发

gqlgen 是一个基于 go generate 的 GraphQL 服务器库:你先用 GraphQL Schema Definition Language(SDL)描述 API,再由工具生成类型安全的 Go 代码。这与 code-first(先写代码再推导 schema)的思路正好相反。

这种 schema-first 模式带来几个关键收益:

  • 生成的 resolver 签名带有完整的上下文(context.Context)和类型安全的输入/输出,编译期即可发现类型错误;
  • 生成的执行层代码(executor)与业务实现解耦,业务代码只关注 resolver 内部逻辑;
  • schema 文件即 API 契约,前后端可以并行开发。

gqlgen 初始化时默认生成的 schema 位于 graph/schema.graphqls(模板见 init-templates/schema.graphqls),内容如下:

type Todo {
id: ID!
text: String!
done: Boolean!
user: User!
}

type User {
id: ID!
name: String!
}

type Query {
todos: [Todo!]!
}

input NewTodo {
text: String!
userId: String!
}

type Mutation {
createTodo(input: NewTodo!): Todo!
}

你可以把 schema 拆分成任意多个 .graphqls 文件,gqlgen 会通过配置中的 glob 模式统一收集。

第一步:初始化项目

创建目录并初始化 Go Module

mkdir gqlgen-todos
cd gqlgen-todos
go mod init github.com/[username]/gqlgen-todos

将 gqlgen 添加为工具依赖

gqlgen 以工具依赖(tool dependency)的方式引入,使用 Go 1.24+ 的 go get -tool 语法:

go get -tool github.com/99designs/gqlgen

默认会安装最新版本;如果想锁定特定版本,可显式指定版本号:

go get -tool github.com/99designs/gqlgen@VERSION

把 VERSION 替换为你需要的版本即可。之后即可通过 go tool gqlgen 调用(等价于 go run github.com/99designs/gqlgen)。

第二步:生成项目骨架

执行 init 命令

go tool gqlgen init

该命令会在当前目录创建 gqlgen 推荐的包布局(路径可在 gqlgen.yml 中修改):

├── go.mod
├── go.sum
├── gqlgen.yml – gqlgen 配置文件,控制生成代码的各种开关
├── graph
│ ├── generated – 只包含生成运行时的包
│ │ └── generated.go
│ ├── model – 存放所有 GraphQL 模型(生成或手写)的包
│ │ └── models_gen.go
│ ├── resolver.go – 根 resolver 类型,此文件不会被重新生成
│ ├── schema.graphqls – schema 文件,可拆分为任意多个 graphql 文件
│ └── schema.resolvers.go – 对应 schema.graphql 的 resolver 实现
└── server.go – 应用入口,可按需自定义

从源码看,init 子命令由 main.go 中的 initCmd 实现:它会检查 gqlgen.yml、schema、server 文件是否已存在,然后写入配置模板(init-templates/gqlgen.yml.gotmpl)与 schema 模板,再调用 api.Generate 完成首次代码生成。值得注意的是,它还支持几个命令行开关:

参数说明默认值
–config, -c 配置文件路径 gqlgen.yml
–server server 存根文件的写入位置 server.go
–schema schema 存根文件的写入位置 graph/schema.graphqls
–verbose, -v 输出详细日志 关闭

同时,init 要求项目根目录存在 go.mod(通过向上查找最近的 go.mod 判定模块根),否则会直接报错退出。

认识 init 生成的默认配置

gqlgen init 生成的 gqlgen.yml 是理解 gqlgen 的钥匙,核心段落如下:

# 所有 schema 文件的位置,支持 glob,例如 src/**/*.graphqls
schema:
– graph/*.graphqls

# 生成的服务器执行代码位置
exec:
package: graph
layout: single-file # 另一种选项是 "follow-schema",即多文件

# 仅 single-file 布局生效:
filename: graph/generated.go

# 仅 follow-schema 布局生效:
# dir: graph
# filename_template: "{name}.generated.go"

# 生成的模型代码位置
model:
filename: graph/model/models_gen.go
package: model

# resolver 实现代码位置
resolver:
package: graph
layout: follow-schema # 另一种选项是 "single-file"

# 仅 single-file 布局生效:
# filename: graph/resolver.go

# 仅 follow-schema 布局生效:
dir: graph
filename_template: "{name}.resolvers.go"

关于布局方式:exec 采用 single-file(生成到 graph/generated.go)或 follow-schema(按 schema 文件拆分生成多个 .generated.go);resolver 默认采用 follow-schema(按 schema 文件生成 {name}.resolvers.go),也可切换为 single-file。

第三步:实现 resolver

查看 init 生成的待办项

init 运行时已经对照 schema(graph/schema.graphqls)与模型目录 graph/model/* 做了一次绑定:凡是能直接匹配到 Go 模型的字段直接绑定,匹配不上的则生成 resolver 存根。打开 graph/schema.resolvers.go,可以看到两处需要实现的存根:

func (r *mutationResolver) CreateTodo(ctx context.Context, input model.NewTodo) (*model.Todo, error) {
panic(fmt.Errorf("not implemented"))
}

func (r *queryResolver) Todos(ctx context.Context) ([]*model.Todo, error) {
panic(fmt.Errorf("not implemented"))
}

只需实现这两个方法,服务器即可工作。

在 Resolver 中保存状态

graph/resolver.go 用于声明应用的依赖(比如数据库连接、内存切片等),它在 server.go 创建 graph 时被初始化一次。先加入内存存储:

type Resolver struct{
todos []*model.Todo
}

实现 CreateTodo 与 Todos

回到 graph/schema.resolvers.go 填充函数体。CreateTodo 用 crypto/rand 生成随机 ID 并存入内存列表(真实项目中这里通常换成数据库或其他后端服务):

func (r *mutationResolver) CreateTodo(ctx context.Context, input model.NewTodo) (*model.Todo, error) {
randNumber, _ := rand.Int(rand.Reader, big.NewInt(100))
todo := &model.Todo{
Text: input.Text,
ID: fmt.Sprintf("T%d", randNumber),
User: &model.User{ID: input.UserID, Name: "user " + input.UserID},
}
r.todos = append(r.todos, todo)
return todo, nil
}

func (r *queryResolver) Todos(ctx context.Context) ([]*model.Todo, error) {
return r.todos, nil
}

启动服务器并验证

go run server.go

浏览器打开 http://localhost:8080(gqlgen 会同时挂载 GraphQL Playground),先执行创建 mutation:

mutation createTodo {
createTodo(input: { text: "todo", userId: "1" }) {
user {
id
}
text
done
}
}

再执行查询:

query findTodos {
todos {
text
done
user {
name
}
}
}

第四步:按需懒加载 User(字段级 resolver)

上面的示例能跑,但现实世界"取对象"通常是昂贵的操作:只要客户端没请求 user 字段,就不应该去加载 User。为此,我们把自动生成的 Todo 模型替换成更贴近真实场景的版本。

开启 autobind

在 gqlgen.yml 中取消 autobind 配置的注释,让 gqlgen 优先使用你自定义的模型,找不到时才自动生成:

# gqlgen 会在这些 go 包中查找 schema 中出现的类型名
# 匹配则直接使用,否则自动生成
autobind:
– "github.com/[username]/gqlgen-todos/graph/model"

注意 init 生成的模板里 autobind 写的是 {{.}}/graph/model(见 init-templates/gqlgen.yml.gotmpl),其中 {{.}} 会在 init 时被替换为当前模块的导入路径。

为 user 字段开启 resolver

在 gqlgen.yml 的 models 段声明:Todo.user 字段需要生成 resolver。models 段负责声明 GraphQL 类型与 Go 类型系统的映射——每项第一行用于 resolver 参数与 modelgen 的默认绑定,其余行允许用于字段绑定:

models:
ID:
model:
– github.com/99designs/gqlgen/graphql.ID
– github.com/99designs/gqlgen/graphql.Int
– github.com/99designs/gqlgen/graphql.Int64
– github.com/99designs/gqlgen/graphql.Int32
Int:
model:
– github.com/99designs/gqlgen/graphql.Int32
Todo:
fields:
user:
resolver: true

手写 Todo 模型

新建 graph/model/todo.go:

package model

type Todo struct {
ID string `json:"id"`
Text string `json:"text"`
Done bool `json:"done"`
UserID string `json:"userId"`
User *User `json:"user"`
}

然后重新生成代码:

go tool gqlgen generate

现在 graph/schema.resolvers.go 中会多出一个 todoResolver.User。实现它,并修正 CreateTodo 只保存 UserID:

func (r *mutationResolver) CreateTodo(ctx context.Context, input model.NewTodo) (*model.Todo, error) {
randNumber, _ := rand.Int(rand.Reader, big.NewInt(100))
todo := &model.Todo{
Text: input.Text,
ID: fmt.Sprintf("T%d", randNumber),
UserID: input.UserID,
}
r.todos = append(r.todos, todo)
return todo, nil
}

func (r *todoResolver) User(ctx context.Context, obj *model.Todo) (*model.User, error) {
return &model.User{ID: obj.UserID, Name: "user " + obj.UserID}, nil
}

这样 user 字段只有被客户端真正请求时才执行对应 resolver,实现按需加载。

第五步:用 go:generate 固化生成流程

在 graph/resolver.go 的 package 与 import 之间加入魔法注释:

//go:generate go tool gqlgen generate

此后只需一条命令即可对整个项目递归执行代码生成:

go generate ./…

把生成步骤固化进源码后,任何协作者 clone 项目都能一键重建全部生成代码,无需记忆命令。

仓库中的完整参考实现

如果教程中的示例想直接对照成品代码,仓库的 _examples/todo 提供了完整可运行版本:

  • schema.graphql:包含自定义 directive(@goField、@hasRole、@user)、自定义 scalar Map,以及将根类型重命名为 MyQuery/MyMutation 的示例;
  • models.go:手写的 Todo/User 模型,Todo 通过私有字段 owner 隐藏归属关系,并实现了 Ownable 接口供权限 directive 使用;Number 类型演示了如何通过 UnmarshalGQLContext/MarshalGQLContext 实现自定义标量的上下文感知编解码;
  • todo.go:resolver 实现,演示内存存储、基于 context 的用户身份注入(getUserId)以及自定义 directive(HasRole、User)的注册方式;
  • gqlgen.yml:展示了 models 映射的另一种典型写法——把 Todo 绑定到外部包模型,并把 ID 的默认 marshaller 覆盖为 graphql.IntID(整数 ID);
  • server/server.go:展示如何用 graphql/handler 创建服务器、注册 GET/POST transport、设置 panic 恢复函数,并挂载 Playground。

运行该示例:

go run ./_examples/todo/server/server.go

然后访问 http://localhost:8081 即可体验(注意与教程默认端口 8080 不同,该示例固定监听 8081)。

常见疑问与要点小结

  • gqlgen 会重写我的 resolver 实现吗? 不会。graph/resolver.go 这类根文件与已实现的 resolver 函数体会被保留,generate 只负责补齐缺失的存根与同步签名。
  • 模型是手写还是生成? 开启 autobind 后,gqlgen 优先复用你手写的模型(可附带自定义方法、私有字段、特殊 tag),找不到的类型仍会自动生成到 graph/model/models_gen.go。
  • 字段级 resolver 的意义? 用 models.Todo.fields.user.resolver: true 声明后,user 字段的加载被延迟到实际被请求时,这是控制 N+1 查询、按需组装数据的基本手段。
  • 版本管理:通过 go get -tool github.com/99designs/gqlgen@VERSION 锁定版本,配合 go generate ./… 即可让整个团队使用完全一致的生成器。

至此,你已经走通了 gqlgen 的完整主流程:初始化骨架 → 定义 schema → 生成并实现 resolver → 按需加载字段 → 固化生成命令。接下来可以进一步阅读仓库中的 _examples 目录(dataloader、federation、subscription 等示例),或参考 docs/content/config.md 深入了解 gqlgen.yml 的全部配置项,包括 exec/resolver 布局、性能优化开关与内建 directive(@goModel、@goField、@goTag 等)。

赞

分享

  • 后端
  • GraphQL
  • 代码生成

【免费下载链接】gqlgen

go generate based graphql server library

项目地址:
https://gitcode.com/gh_mirrors/gq/gqlgen

点击查看 免费下载

上一篇:
如何用这款神器轻松备份语雀文档:新手也能上手的完整教程

下一篇:
FluidX3D性能调优实战:从卡顿到流畅的完整优化指南

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

赞(0)
未经允许不得转载:网硕互联帮助中心 » gqlgen 入门实战:用 Go 构建类型安全的 GraphQL 服务器
分享到: 更多 (0)

评论 抢沙发

评论前必须登录!