简介
gqlgen 是一个用于构建GraphQL服务器的Go语言库。
- gqlgen基于Schema构建,可以通过Schema Definition Language定义API
- gqlgen倾向于类型安全,所以不会出现
map[string]interface - gqlgen支持代码生成,只需要关注业务相关代码即可
快速开始
-
初始化一个module
mkdir demo cd demo go mod init demo -
引用gqlgen
-
创建tool.go文件
-
写入一下内容
//go:build tools // +build tools package tools import ( _ "github.com/99designs/gqlgen" ) -
执行
go mod tidy
-
-
初始化gqlgen配置和模型
go run github.com/99designs/gqlgen initgqlgen为构建好GraphQL服务端代码
-
启动GraphQL服务端代码
go run server.go -
一些其他资料
- Getting started tutorial - 一个帮助你开始的综合性教程
- Real-world examples 一个实际应用案例
- Reference docs API参考文档
教程
-
构建项目
- 初始化go module
mkdir gqlgen-todos cd gqlgen-todos go mod init github.com/[username]/gqlgen-todos- 在当前目录创建一个
tools.go文件,并添将gqlgen作为匿名以来添加
//go:build tools // +build tools package tools import ( _ "github.com/99designs/gqlgen" )- 使用
go mod tidy命令将依赖自动添加到go.mod中 - 根据需求,更新
gqlgen版本
go get -d github.com/99designs/gqlgen<@VERSION>如果只是看看效果,可以暂时不管
-
构建服务
- 生成服务框架
go run github.com/99designs/gqlgen init如果已经安装了gqlgen工具,可以直接使用
gqlgen init命令(前提是已经将GOPATH/bin加入环境变量PATH中)安装命令如下:go install github.com/99designs/gqlgengqlgen会生成默认的工程框架,结构如下
├── go.mod ├── go.sum ├── gqlgen.yml - The gqlgen config file, knobs for controlling the generated code. ├── graph │ ├── generated - A package that only contains the generated runtime │ │ └── generated.go │ ├── model - A package for all your graph models, generated or otherwise │ │ └── models_gen.go │ ├── resolver.go - The root graph resolver type. This file wont get regenerated │ ├── schema.graphqls - Some schema. You can split the schema into as many graphql files as you like │ └── schema.resolvers.go - the resolver implementation for schema.graphql └── server.go - The entry point to your app. Customize it however you see fit-
定义的模式
schema.graphqls为模式定义文件,实际应用中需要有用户定义。默认生成的
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! }GraphQL语法在后文中介绍,此处内容着重关注Query和Mutation两个type即可。对照
graph/schema.resolvers.go中内容阅读,可以看到,其中的方法正好与schema中对应。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")) } -
依赖注入
在具体实现前,我们现在
graph/resolver.go文件中为Resolver添加一个字段。type Resolver struct{ todos []*model.Todo }graph/resolver.go文件是以来注入文件,仅在项目init自动生成一次,后续的generate不会发生修改。需要注入的以来由用户自定义。此时注入一个todo的切片,用作内存数据暂存。 -
实现方法
此时我们返回
graph/schema.resolvers.go实现之前两个方法。func (r *mutationResolver) CreateTodo(ctx context.Context, input model.NewTodo) (*model.Todo, error) { todo := &model.Todo{ Text: input.Text, ID: fmt.Sprintf("T%d", rand.Int()), 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此时可以通过访问
localhost:8080访问页面。页面首次加载时间比可能较长,请耐心等待。 -
测试
根据页面提示分别执行创建和查询操作。
mutation createTodo { createTodo(input: { text: "todo", userId: "1" }) { user { id } text done } }query findTodos { todos { text done user { name } } } -
自定义模型
- 修改配置文件
gqlgen.yml,取消对自动绑定内容的注释。
# gqlgen will search for any type names in the schema in these go packages # if they match it will use them, otherwise it will generate them. autobind: - "github.com/[username]/gqlgen-todos/graph/model"自动绑定的功能是,从内容中匹配对应shecma中的名称,如果存在则不会由gqlgen生成。
-
创建自定义模型
创建新文件
graph/model/todo.go,内容如下:package model type Todo struct { ID string `json:"id"` Text string `json:"text"` Done bool `json:"done"` UserID string `json:"user"` } -
生成代码
go run github.com/99designs/gqlgen generate我们会发现``graph/schema.resolvers.go` 中出现了一个新的方法。
func (r *mutationResolver) CreateTodo(ctx context.Context, input model.NewTodo) (*model.Todo, error) { todo := &model.Todo{ Text: input.Text, ID: fmt.Sprintf("T%d", rand.Int()), User: &model.User{ID: input.UserID, Name: "user " + 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 }修改代码并实现新方法
- 修改配置文件
-
收尾工作
在
resolver.go的package和import之间添加如下行://go:generate go run github.com/99designs/gqlgen generate之后运行
go generate命令就会自动执行代码生成指令
GraphQL基础语法
参考文档
模式定义语言:Schema Definition Language.
语法说明
- Exampel One
type Character { name: String! appearsIn: [Episode!]! }-
type Character指示Character为一个包含一个或多个字段的对象(此处包含两个字段,name和appearsIn) -
String!说明name字段为内建类型string(string),且不为null(!)。 -
[Episode!]!说明appearsIn是一个Episode(其中Episode不能为空)的数组,且不能为空(!)。
- Example Two
type Starship { id: ID! name: String! length(unit: LengthUnit = METER): Float }对象的每一个字段都可以有零个或多个参数。参数可以是必须的也可以是可选的。当以参数为可选的时,我们可以为其设定默认值。如
unit参数如果没有传入,则会被默认为METER。- Example Three
type Query { hero(episode: Episode): Character droid(id: ID!): Droid }GraphQL中有两个特殊的类型(type),
Query和Mutation。一个GraphQL的服务有一个Query类型和至多一个mutation类型。这两个类型与普通type的定义规则一致,但是他们是用来定义GraphQL查询入口的。-
type Query指明服务需要一个包含hero和droid字段的Query入口。 -
Mutation的工作方式和你定义Mutation字段相似,可以作为查询的根Mutation使用。
-
标量(Scalar types)
-
没有子字段
-
GraphQL提供了几个内建标量
- Int:32为有符号整型
- Float:双精度浮点数
- String:utf8 字符序列
- Boolean:true或者false
- ID:代表唯一ID,通常用于重新获取对象或用于缓存。序列化方式和String一样。
-
自定义标量
-
scalar Date之后可以根据需求设定它的序列化、反序列化、校验方式。
-
枚举类型(Enumeration types)
enum Episode { NEWHOPE EMPIRE JEDI } -
列表和非空(List and Non-Null)
- 非空可以用于声明字段值不为空。用法查看Example One
- 非空还可以用于查询参数非空的说明
query DroidById($id: ID!) { droid(id: $id) { name } } -
接口(Interfac)
一个接口类型实现了接口对象应该包含特定的字段、参数、返回类型。
interface Character { id: ID! name: String! friends: [Character] appearsIn: [Episode]! } type Human implements Character { id: ID! name: String! friends: [Character] appearsIn: [Episode]! starships: [Starship] totalCredits: Int } type Droid implements Character { id: ID! name: String! friends: [Character] appearsIn: [Episode]! primaryFunction: String }-
联合类型(Union Type)
类似与Interface,但是不需要有共同的字段。
union SearchResult = Human | Droid | Starship- 输入类型(Input Type)
input ReviewInput { stars: Int! commentary: String }用于向
mutation中加入复杂对象>mutation CreateReviewForEpisode($ep: Episode!, $review: ReviewInput!) { createReview(episode: $ep, review: $review) { stars commentary } }
\