この記事は こちら をなぞってみた記事になります。
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 ./...
いい感じですね!