gqlgenチュートリアルをできるだけわかりやすく解説する

# はじめに
エンジニアインターン2社目にしてGolang+GraphQLで開発することになりました。
最近こそ理解が進みはしたものの、今までREST以外の世界を知らなかった自分にとってgqlgenが何をしてくれるのか、何が便利なのかピンと来なかったので同じような境遇の方のために記事を書こうと思います。

# 前提知識
- GraphQLの基本がわかる
- Golangの基本文法がわかる

# gqlgenとは？
公式から引用
>gqlgen is a Go library for building GraphQL servers without any fuss.
・gqlgen is based on a Schema first approach — You get to Define your API using the GraphQL Schema Definition Language.
・gqlgen prioritizes Type safety — You should never see map[string]interface{} here.
・gqlgen enables Codegen — We generate the boring bits, so you can focus on building your app quickly.

要はGraphQLサーバーを作る際に「**スキーマファースト**」で「**型安全性を保ったまま**」「**コードを自動生成**」できるGolangのライブラリというわけです。

# 目標
gqlgenチュートリアルに沿って簡単なtodoアプリケーションを作成します。挙動はGraphQL Playroundで確認するような形にします。

# Let's start!

まずは準備から。作業ディレクトリを作成してgoの開発環境を整えましょう。今回は`golang 1.8`を使用します。
```
mkdir gqlgen_tutorial && cd gqlgen_tutorial
```
```
go mod init gqlgen_tutorial
```
次に今回の主役gqlgenパッケージとその依存関係含めダウンロードします。
```
go get -u github.com/99designs/gqlgen@v0.17.5
```
こだわりはありませんが今回は最新versionの0.17.5を使用します。

そうしたら以下のコマンドで雛形ファイルを作成します。
```
go run github.com/99designs/gqlgen init
```
いとも簡単にGraphQLサーバーが作成されました！ディレクトリ構成が以下のようになっていれば正常です。
![](https://storage.googleapis.com/zenn-user-upload/005a9ddc94ec-20220430.png)
> 生成されたファイルの説明
- graph/generated/generated.go
  このファイルはGraphQLサーバーに対するリクエストを解釈し`graph/resolver.go`の適切なメソッド呼ぶ役割を果たしています。
- graph/model/models_gen.go
 schemaで定義したtypeやinputをgolangの構造体に変換したものが定義されます。
- graph/schema.resolver.go
 リクエストを元に実際の処理を実装する`resolver`ファイルです。
 
上記の3つはschemaを変更した後`go run github.com/99designs/gqlgen generate`を実行することでコードが再生成されます。
- graph/resolver.go
 ルートとなるresolver構造体が宣言されます。再生成はされません。
- graph/schema.graphqls
 GraphQLスキーマを定義するファイルです。このファイルをもとに他のファイルが再生成されます。
- gqlgen.yml
 gqlgenの設定ファイルです。今回は行いませんがshcemaの分割などの設定もこのファイルで行うことができます。


早速`graph/schema.graphqls`を見てみましょう。
```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!
}
```
身に覚えのないschemeが生成されています。gqlgenがデフォルトで生成してくれるものですね。今回はこのschemaをベースに簡単なtodoアプリを作っていこいうと思います。
冒頭でも記載しましたがgqlgenは**スキーマファースト**でGraphQLサーバーを生成してくれます。コードを自動生成する際やresolver(後述)を実装していく際もこのスキーマベースに実装していくことになります。何か意図しないエラーが出た際はこのschemaから確認していくと良いと思います。

次に`graph/model/models_gen.go`を見てみましょう。
```go
// Code generated by github.com/99designs/gqlgen, DO NOT EDIT.

package model

type NewTodo struct {
	Text   string `json:"text"`
	UserID string `json:"userId"`
}

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

type User struct {
	ID   string `json:"id"`
	Name string `json:"name"`
}
```
先ほど見たschema内で定義ているQueryやMutation以外の型が構造体として定義されています。このファイルの構造体を使用しながら後述のresolverを実装していきます。

最後に`graph/schema.resolver.go`を見てみましょう。
```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_tutorial/graph/generated"
	"gqlgen_tutorial/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 }
```
何やら怪しげなメソッドがいくつか生成されています。注目すべきは`CreateTodo`と`Todos`です。こちらは`graph/schema.graphqls`で定義した
```graphql
type Query {
  todos: [Todo!]!
}
...省略
type Mutation {
  createTodo(input: NewTodo!): Todo!
}
```
こちらにそれぞれ対応しています。GraphQLサーバーに対するリクエストがgqlgenが自動生成した`/generated/generated.go`内でよしなに処理された後、これらのメソッドが呼び出されます。そのためこれらのメソッドはいわゆる`Controller`の役割を果たしているわけです。
今の段階だと中身が空で何も実装されていないのでこれから中身を実装していきます。データはデータベースに永続化されることが多いですが、とりあえず動作確認のためインメモリ上にデータを保管します。

まずは`graph/resolver.go`を変更します。
```go
type Resolver struct {
	todos []*model.Todo // 追加
}
```
schema.resolver.goで定義されているmutationResolverやqueryResolverはこのResolverをラップしているのでまずはこのResolverにtodosを保持するようにしたいと思います。

次にresolverを実装していきます。
```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
}
```
`CreateTodo`メソッドはリクエストされた情報`(input　model.NewTodo)`からTodoモデルを作成しResolver struct内のtodos sliceに追加するだけの処理です。

`Todos`メソッドでは既存のResolver struct内のtodos sliceを返しているだけです。

実際のアプリケーションであればもっと複雑なロジックやDB操作が入るかと思いますが今回はできるだけ最小限でいきたいので割愛します。

resolverの実装が終わったので、いざ動作確認！GraphQLサーバーを立ち上げます。
```
go run server.go
```
`http://localhost:8080/`にアクセスしましょう。
![](https://storage.googleapis.com/zenn-user-upload/2f47e68bb981-20220501.png)
このようなページが表示されたら成功です！GraphQL playgroundを使うことでフロントエンドを用意したりわざわざcurlコマンドを叩くことなく動作確認できます。
早速mutationをリクエストしてtodoを作成してみましょう！以下のリクエストをコピペした後、画面左上の再生マーク？を押してリクエストしてみて下さい。
```graphql
mutation {
  createTodo(input: { text: "todo", userId: "1" }) {
    user {
      id
    }
    text
    done
  }
}
```
以下のようなレスポンスが返ってくれば正常に作成されています！
```
{
  "data": {
    "createTodo": {
      "user": {
        "id": "1"
      },
      "text": "todo",
      "done": false
    }
  }
}
```
次に作成したtodoを取得してみましょう。
```graphql
query {
  todos {
    text
    done
    user {
      name
    }
  }
}
```
```graphql
{
  "data": {
    "todos": [
      {
        "text": "todo",
        "done": false,
        "user": {
          "name": "user 1"
        }
      }
    ]
  }
}
```
素晴らしい！
resolverを実装するだけで、こんなにも簡単にGraphQLサーバーができました。
しかしこれだけだとGraphQLのメリットの一つである**取得するデータの取捨選択**ができていません。
例えば今の段階ではtodosを取得する際、必ずuserも取得されます。もちろん
```graphql
query {
  todos {
    text
    done
  }
}
```
のようなリクエストを送ればレスポンスとしてuserデータが返ってくることはありませんが、内部的にはuserデータを取得しています。よくあるRDBを使用したwebアプリケーションではtodoモデルはUserIDだけを持っていてそれに紐づくuserはtodoとは別にDBから取得する場合が多いと思います。その場合、レスポンスとしてuserデータが不要なリクエストでも不要なsqlが走ってしまう訳です。
このままだとGraphQLの恩恵を十分受けていると言えません。これは`todoResolver`を実装することで解決できます。ここから修正していきましょう。

まず必要なことは新しくモデルとして`Todo struct`を作成することです。今の状態では`schema.graphqls`を元に`models/models_gen.go`に自動生成したTodoモデルが使用されます。しかしこれだとmutaionである`CreateTodo`の返り値であるTodoモデルにUserを必ず含める必要が出てきてしまうため新しくTodoモデルを定義します。

新たに`graph/models/todo.go`を以下の内容で作成して下さい。
```go
type Todo struct {
    ID     string
    Text   string
    Done   bool
    UserID string
}
```

そしてgqlgen.ymlファイルに以下を追加します。
```yml
models:
  Todo:
    model: gqlgen_tutorial/graph/model.Todo
```
これによって「todoモデルは`gqlgen_tutorial/graph/model.go`のTodo structを使う」という指定ができる訳ですね。

準備が整ったのでファイルを再生成しましょう。
```
go run github.com/99designs/gqlgen generate
```

`graph/model/models_gen.go`を見て下さい。Todo structが消えていますね。これで先ほど定義したTodoモデルが使用されるようになりました。

そしたらresolverを実装していきましょう。`schema.resolvers.go`を開いて下さい。
Todo structは先ほどの変更によって`User`ではなく`UserID`を保持するようになったので
まず`CreateTodo`を修正します。

```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()),
		UserID: input.UserID, //　修正
	}
	r.todos = append(r.todos, todo)
	return todo, nil
}
```


次に先ほどまでなかったUserメソッドに
```go
func (r *todoResolver) User(ctx context.Context, obj *model.Todo) (*model.User, error) {
	return &model.User{ID: obj.UserID, Name: "user " + obj.UserID}, nil //修正
}
```
上記の変更を加えます。

ここが自分が最初なかなか理解できない点だったのですが、
mutationの`CreateTodo`やqueryの`Todos`によってtodo structが返された際、schema中の`todo type`が指定しているUserフィールドをtodo structが持っていないためtodoResolverに対して定義されているメソッドであるUserメソッドが呼び出される訳です。


これで完成です。最後にもう一度
```
go run server.go
```
を実行しGraphQL Playgroundで動作確認してみて下さい。先程と同じ挙動をしていれば完璧です。

# 最後に
いかがでしたでしょうか。わかりにくい部分多々あったかと思いますが、お役に立てれば光栄です。
マサカリや文句、不満等心よりお持ちしております！