学习Go的初学者开始使用gqlgen.
首先
因为一位正在创业的朋友说希望能得到一点帮助,我决定帮忙做一些与GraphQL相关的工作。那时我对golang和GraphQL都完全不了解,比起猫的手更加无用。
我的技能不足当然是一个原因,但我也感觉到相比熟悉的Python和JS等语言,缺乏对初学者友好的文章也是一个因素。
很少有易于理解的信息可供参考,这也可能是Go和GraphQL进入门槛较高的原因之一。
为了让像我这样的初学者能够快速开始使用Go + GraphQL,我会简单地记录一下关于gqlgen的内容。
※ 在讨论中,我们假设你已经对Go的基本语法和GraphQL的概念有一定了解。如果你对这些方面没有自信,阅读下面的文章可能会使你感到幸福。
-
- Go言語:文法基礎まとめ
-
- Web API初心者と学ぶGraphQL
- 他言語から来た人がGoを使い始めてすぐハマったこととその答え
环境
操作系统 (CZXT)
$ sw_vers
ProductName: Mac OS X
ProductVersion: 10.15.7
BuildVersion: 19H114
去
$ go version
go version go1.15.5 darwin/amd64
gqlgen是什么?
只需要引用正式文本。
gqlgen 是一个使用Go语言编写的库,用于构建GraphQL服务器,无需任何繁琐的操作。
gqlgen 基于先定义模式(schema)的方法——您可以使用GraphQL模式定义语言来定义您的API。
gqlgen 优先考虑类型安全——你永远不会在这里看到 map[string]interface{}。
gqlgen 支持代码生成——我们会自动生成一些繁琐的部分,让您可以专注于快速构建您的应用程序。
为了那些对英语过敏的人,我尝试进行了意译并将其转换成了日语。
gqlgen是一个用于简便构建GraphQL服务器的Go语言库。
gqlgen基于”以模式为主”的方法,即按照GraphQL模式定义语言来定义API。
gqlgen优先考虑类型安全性,不再需要使用map[string]interface{}类型。
gqlgen使代码生成成为可能,不需要编写冗长的代码片段,能够专注于快速构建应用程序。
简而言之,这意味着现在可以轻松建立GraphQL服务器的Go库。让我们在运行gqlgen的同时实际观察一下它的流程,看看它有多简单。
试着搭建一个GraphQL服务器。
项目的创建和启动
暂时先试试看能否运行GraphQL。
这次我们创建一个名为”gqlgen-test”的项目。
# ディレクトリ作成
$ mkdir gqlgen-test
# Goプロジェクトとして初期化
$ go mod init gqlgen-test
# プロジェクト内に移動
$ cd gqlgen-test
# gqlgenをGOPATH/bin配下におく
$ go get github.com/99designs/gqlgen
目前的状态下,目录中还没有任何文件存在。
$ ls
接下来,让我们尝试使用gqlgen自动生成GraphQL服务器的框架。
# gqlgen initにより、GraphQLサーバーの骨組みを生成
$ go run github.com/99designs/gqlgen init
然后,会形成以下这样的结构。
(以下是tree命令的输出。)
├── gqlgen.yml
├── graph
│ ├── generated
│ │ └── generated.go
│ ├── model
│ │ └── models_gen.go
│ ├── resolver.go
│ ├── schema.graphqls
│ └── schema.resolvers.go
└── server.go
3 directories, 7 files
创建一个名为serger.go的go文件,然后立即启动它。
$ go run server.go
当您通过浏览器访问下面的网址时,将跳转至GraphQL查询界面。
http://localhost:8080/
执行查询
让我们立即输入查询语句。
query{
todos{
id
text
}
}
这意味着执行一个以“todos”命名的查询,并返回id和text。如果有人说“我没有定义名为 todos 的查询”,请查看 graph/schema.graphql。
# GraphQL schema example
#
# https://gqlgen.com/getting-started/
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!
}
当我执行 `go run github.com/99designs/gqlgen init` 命令时,它自动为我生成了定义了 `todos` 的部分。
type Query {
todos: [Todo!]!
}
接着,执行部收到如下消息。
{
"errors": [
{
"message": "internal system error",
"path": [
"todos"
]
}
],
"data": null
}
原來如此。看起來數據為 null,內部系統發生錯誤。仔細回想一下,我們雖然在 schema.graphql 中寫了名為 todos 的查詢,但沒有記錄要返回什麼內容。
那么,让我们来看一下graph/schema.resolvers.go文件的内容。
package graph
// This file will be automatically regenerated based on the schema, any resolver implementations
// will be copied through when generating and any unknown code will be moved to the end.
import (
"context"
"fmt"
"gqlgen-test/gqlgen-test/graph/generated"
"gqlgen-test/gqlgen-test/graph/model"
)
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"))
}
// Mutation returns generated.MutationResolver implementation.
func (r *Resolver) Mutation() generated.MutationResolver { return &mutationResolver{r} }
// Query returns generated.QueryResolver implementation.
func (r *Resolver) Query() generated.QueryResolver { return &queryResolver{r} }
type mutationResolver struct{ *Resolver }
type queryResolver struct{ *Resolver }
Todos函数只会抛出错误消息,没有返回任何响应的规格。
func (r *queryResolver) Todos(ctx context.Context) ([]*model.Todo, error) {
panic(fmt.Errorf("not implemented"))
}
让我们定义响应
让我们编辑解析器并定义响应,就像 schema.resolvers.go 一样,处理数据并返回作为响应的部分被称为解析器。
一旦使用Ctrl + C停止之前启动的服务器。
停止后,尝试编辑schema.resolver.go如下。
func (r *queryResolver) Todos(ctx context.Context) ([]*model.Todo, error) {
todo := []*model.Todo{
{
ID: "1",
Text: "サンプルテキスト",
Done: true,
User: &model.User{
ID: "001",
Name: "山田太郎",
},
},
}
return todo, nil
}
※本来這裡應該是執行資料庫操作等的部分,但這次的主要目的是先試用GraphQL。為了簡化說明,我們將固定回應直接寫在這裡。關於與資料庫的連接部分,我希望能在另一篇文章中寫到。
同样地,我们也需要对resolver.go做如下编辑。
package graph
import "gqlgen-test/gqlgen-test/graph/model"
// This file will not be regenerated automatically.
//
// It serves as dependency injection for your app, add any dependencies you require here.
type Resolver struct {
todos []*model.Todo
}
编辑完解析器后,返回项目的根目录并重新启动服务器。
$ go run server.go
请重新加载localhost:8080,并尝试以相同的查询方式执行。
这次应该会按照之前定义的通常情况返回响应。
{
"data": {
"todos": [
{
"id": "1",
"text": "サンプルテキスト"
}
]
}
}
现在,我们在解析器中定义的键除了ID和文本外,还有完成状态和用户信息。
基于GraphQL的API可以通过查询来控制响应。
让我们也提取一下完成状态和用户信息吧!
query{
todos{
id
text
done
user{
id
name
}
}
}
执行此操作时,您应该会直接得到您在解析函数中定义的结果作为响应。
{
"data": {
"todos": [
{
"id": "1",
"text": "サンプルテキスト",
"done": true,
"user": {
"id": "001",
"name": "山田太郎"
}
}
]
}
}
尝试自己定义查询
最后我想尝试自己定义查询。
如前所述,gqlgen基于模式优先的方法。
所以,当想要定义查询时,第一步是”定义模式”。
当停止服务器后,我们可以尝试编辑包含type Query的graph/schema.graphql文件。
type Query {
todos: [Todo!]!
testquery: String!
}
为了将模式更改反映到解析器中,请执行以下操作。
$ go run github.com/99designs/gqlgen generate
让我们再次看看graph/schema.resolver.go。
刚才不存在的查询语句Testquery现在存在了!
func (r *queryResolver) Testquery(ctx context.Context) (string, error) {
panic(fmt.Errorf("not implemented"))
}
让我们修改代码,以返回自己喜欢的字符串。
func (r *queryResolver) Testquery(ctx context.Context) (string, error) {
return "TestString", nil
}
让我们重新启动服务器,以确保能够得到期望的响应。
$ go run server.go
query{
testquery
}
{
"data": {
"testquery": "TestString"
}
}
把上述的流程逐步总结一下,结果如下,对吗?
-
- 项目生成
-
- 编辑模式
-
- 编辑解析器
- 启动服务器
总结
就算對Go語言知識不多,或者覺得用Go寫代碼很麻煩的人,只要使用gqlgen能夠輕鬆地建立GraphQL服務器。我們只是缺乏能在日語上找到相關文章而已,一旦習慣使用,我認為這將無比方便。
由于我也是一个自以为写得很牛的初学者,所以如果您能指出明显的错误或需要更新的部分,我将不胜感激。