Go初心者のgqlgen事始め

# はじめに
起業している友人が猫の手も借りたいと言うことで、GraphQL関連のお仕事を手伝うことになりました。当時は**golangもGraphQLも全くの初心者**で、猫の手より役立たずだったことを記憶しています。

私のスキルが足らなかったのももちろんですが、慣れているPythonやJSなどにくらべて、初心者に優しい記事が少なかったのも一因であるように感じました。
参照しやすい情報が少ないと言うのも、GoやGraphQLの参入障壁が高い理由のひとつかもしれませんね。

ということで、私のような初心者でも**すぐにGo + GraphQLに取り組める**ように、
備忘録程度ですが、gqlgenについて書き記しておこうと思います。

※ Goの基本的な文法や、GraphQLの概念については、ある程度わかっている前提で話が進みます。もし、これらのことに自信がなかったら、以下の記事を参考にしながら読むと幸せになれるかもしれません。

- [Go言語：文法基礎まとめ](https://qiita.com/HiromuMasuda0228/items/65b9a593275f769f6b69)
- [Web API初心者と学ぶGraphQL](https://qiita.com/SiragumoHuin/items/cc58f456bc43a1be41b4)
- [他言語から来た人がGoを使い始めてすぐハマったこととその答え](https://qiita.com/mumoshu/items/0d2f2a13c6e9fc8da2a4)

# 環境
## OS
```shell
$ sw_vers
ProductName:	Mac OS X
ProductVersion:	10.15.7
BuildVersion:	19H114
```
## Go
```shell
$ go version
go version go1.15.5 darwin/amd64
```

# gqlgenとは

[公式テキスト](https://github.com/99designs/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.

英語アレルギーの人用に、意訳を交えて日本語に直してみました。

> [gqlgen](https://github.com/99designs/gqlgen)とはGraphQLサーバーを簡単に構築するためのgolangのライブラリです。
>
> gqlgenは「スキーマ第一」的なアプローチに基づいています。--- APIをGraphQLのスキーマ定義言語に基づいて定義します。
> gqlgenは型の安全性を優先します。 --- `map[string]interface{}`文を見なくて済むようになります。
> gqlgenはコードの生成を可能にします。 --- 退屈なコード断片を書かなくて済むので、アプリケーションを速やかに構築することに集中できます。

要は、**GraphQLサーバーが簡単に建てられる**ようになるGoのライブラリってことです。
どれだけ簡単か、実際にgqlgenを動かしながら流れを見てみましょう。

# GraphQLサーバーを建ててみよう
## プロジェクト生成~起動まで
とりあえず、GraphQLが動かせるところまで行ってみましょう。
今回は、gqlgen-testというプロジェクトを作ってみます。

```shell
# ディレクトリ作成
$ mkdir gqlgen-test
# Goプロジェクトとして初期化
$ go mod init gqlgen-test
# プロジェクト内に移動
$ cd gqlgen-test
# gqlgenをGOPATH/bin配下におく
$ go get github.com/99designs/gqlgen
```
この状態ではまだ、ディレクトリ内にはファイルが存在しない状況です。

```shell
$ ls
```

次に、gqlgenを使って、GraphQLサーバーの骨組みを自動生成してみましょう。

```shell
# 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ファイルが生成されるので、早速起動させてみましょう。

```shell
$ go run server.go
```

下記アドレスにブラウザからアクセスすると、GraphQLのクエリを叩ける画面になります。
http://localhost:8080/

GraphQLのサーバーが、いとも簡単に立ってくれました。ここまでgolangは全く書いていません。確かにこれは便利ですね。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/494762/a9b69dd7-02c6-cb03-2ef0-7d9b3430e456.png)

## クエリの実行
早速、クエリ文を入力してみましょう。

```GraphQL
query{
  todos{
    id
    text
  }
}
```

これは、「`todos`と言う名前で定義されたクエリを実行し、idとtextを返す」ことを意味します。
「`todos` と言う名前のクエリなんぞ、定義した覚えはないぞ」と言う人は、`graph/schema.graphql`を覗いてみてください。

```GraphQL: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!
}

```

たしかに`todos`を定義している部分があります。
`go run github.com/99designs/gqlgen init` を行った時に、自動でここまで生成してくれていたのですね。

```GraphQL
type Query {
  todos: [Todo!]!
}
```

クエリが定義されていることを確認したなら一安心。実際にクエリを実行してみましょう。
画面の真ん中の実行ボタンをクリックします。
![68747470733a2f2f71696974612d696d6167652d73746f72652e73332e61702d6e6f727468656173742d312e616d617a6f6e6177732e636f6d2f302f3439343736322f61396236396464372d303263362d636230332d326566302d3764396233343330653435362e706e67.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/494762/a9f8dc6e-48ae-1a84-a552-6d671fb8b25b.png)


すると、実行部に次のようなメッセージが。

```json
{
  "errors": [
    {
      "message": "internal system error",
      "path": [
        "todos"
      ]
    }
  ],
  "data": null
}
```

なるほど。`data` が`null`で`internal system error`となってしまっているようです。ふと思い返してみると、`todos`というクエリは、スキーマ(`schema.graphql`)の中に書いてあれど、**レスポンスでなにを返すか**は記述した覚えがありません。

そこで、`graph/schema.resolvers.go`の中身を見てみましょう

```go: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という関数は、エラー文を吐き出すのみで、レスポンスとして何かを返すような仕様にはなっていません。

```go:graph/schema.resolvers.go
func (r *queryResolver) Todos(ctx context.Context) ([]*model.Todo, error) {
	panic(fmt.Errorf("not implemented"))
}
```

## レスポンスを定義してみよう
`schema.resolvers.go`のように、**データを操作し、レスポンスとして返すもの**を決めている部分を、**リゾルバ**と呼びます。
リゾルバを編集し、レスポンスを定義してみましょう。

いったん`Ctrl + C` で先ほど立ち上げたサーバーを停止させます。
停止し終えたら、`schema.resolver.go` を以下のように編集してみます。

```Go:graph/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
}
```
※ 本来ならばここは DBの操作などを行う部分ですが、今回はGraphQLをとりあえず動かしてみることを主な目的としています。説明の簡略化のため固定レスポンスをベタ書きしてます。DBとの接続部分は、また別の記事で書けたら書きたいです。

同様に、`resolver.go`も以下のように編集しておきましょう。

```Go:graph/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
}

```

リゾルバを編集し終えたら、プロジェクトのルートに戻り、再びサーバーを起動します。

```shell
$ go run server.go
```

localhost:8080をリロードし、同じようにクエリを実行してみましょう。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/494762/03072503-c890-e85d-d4be-db2437d06504.png)

今度は、先ほど定義した通りに、レスポンスが返ってくるはずです。

```json
{
  "data": {
    "todos": [
      {
        "id": "1",
        "text": "サンプルテキスト"
      }
    ]
  }
}
```
<!-- ## クエリからレスポンスを変更できる -->

さて、リゾルバで定義したキーは、`ID`, `Text`以外に、`Done`, `User`がありました。
GraphQLベースのAPIは、クエリ側でレスポンスをコントロールできます。
`Done`や `User`についての情報も取り出してみましょう！

```GraphQL
query{
  todos{
    id
    text
    done
    user{
      id
      name
    }
  }
}
```
これを実行すると、レスポンスとして、リゾルバで定義したものがそのまま得られるはずです。

```json
{
  "data": {
    "todos": [
      {
        "id": "1",
        "text": "サンプルテキスト",
        "done": true,
        "user": {
          "id": "001",
          "name": "山田太郎"
        }
      }
    ]
  }
}
```

## 自分でクエリを定義してみる
最後に自分でクエリを定義してみようと思います。
はじめに述べた通り、gqlgenはスキーマ第一的なアプローチに基づいています。
ですので、クエリを定義したくなった時、最初にやることは、**「スキーマの定義」**です。

サーバーを停止したら、`graph/schema.graphql`の`type Query` から始まる部分を編集してみましょう。

```GraphQL:graph/schema.graphql
type Query {
  todos: [Todo!]!
  testquery: String!
}
```
スキーマの修正をresolverに反映するために、以下を実行します。

```shell
$ go run github.com/99designs/gqlgen generate
```

再び、`graph/schema.resolver.go`を見てみましょう。
先ほどは存在しなかったクエリ、`Testquery`が存在しています！

```Go:graph/schema.resolver.go
func (r *queryResolver) Testquery(ctx context.Context) (string, error) {
	panic(fmt.Errorf("not implemented"))
}
```

自分の好きな文字列を返すように修正してみましょう。

```Go:graph/schema.resolver.go
func (r *queryResolver) Testquery(ctx context.Context) (string, error) {
	return "TestString", nil
}
```

もう一度サーバーを起動して、期待するレスポンスが返ってくるか確認してみましょう。

```shell
$ go run server.go
```

```GraphQL
query{
  testquery
}
```

```json
{
  "data": {
    "testquery": "TestString"
  }
}
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/494762/6554f940-842c-6dea-ee4c-c6001ef1442e.png)

以上の流れをステップごとにまとめると、以下のようになりますでしょうか。

1. プロジェクト生成
2. スキーマの編集
3. リゾルバーの編集
4. サーバーの起動
 
# まとめ
Goの知識が少なかったり、Goでコードを書くのが面倒な人でも、gqlgenを使えば簡単にGraphQLのサーバーを作ることがわかりました。日本語で参照できる記事が不足しているだけで、使い慣れたら半端なく便利なものかと思います。

※ 偉そうに記事を書いている私も初心者同然ですので、明らかにおかしな点やアップデートが必要な部分があればご指摘いただけますと幸いです。