寝ても覚めてもこんぴうた

プログラム書いたり、ネットワーク設計したり、サーバ構築したり、車いじったり、ゲームしたり。そんなひとにわたしはなりたい。 投げ銭は kyash_id : chidakiyo マデ

GoのGraphQLライブラリのgqlgenのGetting startedをやってみた

f:id:chidakiyo:20191209014145j:plain

この記事は こちら をなぞってみた記事になります。
GraphQLをこれから始めたいという方におすすめ

gqlgenを利用してGraphQLサーバを実装する方法です。

  • todoのリストを返却する
  • 新しいtodoを作成する
  • 完了したtodoをマークする

ちなみにココからの手順はgo moduleを利用した方法になります。

プロジェクトの作成

プロジェクトのディレクトリを作成し、go moduleを初期化します

$ mkdir gqlgen-todos
$ cd gqlgen-todos
$ go mod init # 必要に応じてパスを指定

サーバを構築します

スキーマ定義します

qglgenはスキーマファーストのライブラリです。 コードを書く前に、GraphQLスキーマ定義言語を使用してAPIを記述します。 以下のようなschema.graphqlというファイルを作成し、initコマンドによってコードを生成します。

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

コマンドを実行すると

  • gqlgen.yml
  • generated.go
  • models_gen.go
  • resolver.go
  • server/server.go

のスケルトンが作成されます

データベースモデルの作成

Todo用に生成されたモデルは正しくありません。 ユーザが埋め込まれていますが、要求されたときのみ取得できるようにします。 そのため、代わりにtodo.goで新しいモデルを作成します。

package gqlgen_todos

type Todo struct {
    ID     string
    Text   string
    Done   bool
    UserID string
}

次に gqlgen.yml に以下を追加し、gqlgen を実行します。

models:
  Todo:
    model: github.com/[username]/gqlgen-todos.Todo

以下を実行し、再生成します

$ go run github.com/99designs/gqlgen -v

Note: -v フラグはgqlgenの動作を見るためにあります。

resolverを実装

ジェネレータが必要なresolverのインタフェースを生成しているので generated.go を確認します。

func NewExecutableSchema(cfg Config) graphql.ExecutableSchema {}
    // ...
}

type Config struct {
    Resolvers  ResolverRoot
    // ...
}

type ResolverRoot interface {
    Mutation() MutationResolver
    Query() QueryResolver
    Todo() TodoResolver
}

type MutationResolver interface {
    CreateTodo(ctx context.Context, input NewTodo) (*Todo, error)
}
type QueryResolver interface {
    Todos(ctx context.Context) ([]Todo, error)
}
type TodoResolver interface {
    User(ctx context.Context, obj *Todo) (*User, error)
}

~~

resolverを実装する

既存のコードを修正することができないため、 resolver.go を削除した後、 gqlgen を実行することで強制的に再実行します。

$ rm resolver.go
$ go run github.com/99designs/gqlgen

resolver.go の実装されていないところを埋めていきます。

package gqlgen_todos

import (
    context "context"
    "fmt"
    "math/rand"
)

type Resolver struct {
    todos []*Todo
}

func (r *Resolver) Mutation() MutationResolver {
    return &mutationResolver{r}
}
func (r *Resolver) Query() QueryResolver {
    return &queryResolver{r}
}
func (r *Resolver) Todo() TodoResolver {
    return &todoResolver{r}
}

type mutationResolver struct{ *Resolver }

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

type queryResolver struct{ *Resolver }

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

type todoResolver struct{ *Resolver }

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

Resolver, CreateTodo, Todos, User 内を実装しています。

動作するようになったのでサーバを起動してみましょう

$ go run server/server.go

起動が成功したら、ブラウザで http://localhost:8080 へアクセスし、
以下のクエリを試してみましょう

これがmutationなので、todoを作成するクエリです

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

以下のクエリで作成したtodoを取得できます

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

仕上げ

resolver.go のpackageとimportの間に以下の行を追加します。

//go:generate go run github.com/99designs/gqlgen

このマジックコメントは、コードを再生成したいときに実行するコマンドになります。
プロジェクト全体で再帰的に生成を実行するには、以下のコマンドを使用します。

$ go generate ./...

いい感じですね!