MyDocs

# gqlgenを触る

database の準備

$ go run -mod=mod entgo.io/ent/cmd/ent new Todo

schema の設定。

// Fields of the Todo.
func (Todo) Fields() []ent.Field {
	return []ent.Field{
		field.String("title"),
		field.String("note"),
		field.Bool("completed").Default(false),
		field.Time("created_at").Default(time.Now()),
		field.Time("updated_at").Default(time.Now()),
	}
}
$ go generate ./ent

gqlgen

$ printf '// +build tools\npackage tools\nimport (_ "github.com/99designs/gqlgen"\n _ "github.com/99designs/gqlgen/graphql/introspection")' | gofmt > tools.go
$ go mod tidy
$ go run github.com/99designs/gqlgen init

graph/ に生成されたファイル。

tree graph/
graph/
├── generated.go # 自動生成
├── model
│   └── models_gen.go # 自動生成
├── resolver.go
├── schema.graphqls
└── schema.resolvers.go

ファイルは resolver.goschema.graphqlsschema.resolvers.go で良さそう。 プロジェクト直下に生成された gqlgen.yml は一旦放置。

スキーマ定義

schema.graphqls / schema.resolvers.go を削除し、新規にスキーマ定義ファイルを作る。

共有インターフェイス Node を定義する。プライマリーキーを持つ場合は Node インターフェイスを作るのがお作法らしい。また、独自の型として Datetime を定義する。

interface Node {
    id: ID!
}

scalar Datetime

次に Todo スキーマを定義する。

type Todo implements Node {
    id: ID!
    title: String!
    note: String!
    completed: Boolean!
    created_at: Datetime!
    updated_at: Datetime!
}

最後に query と mutation を定義する。

type Query {
    todos: [Todo!]!
}
type Mutation {
    createTodo(input: NewTodo!): Todo!
}

input NewTodo {
    id: ID
    title: String!
    note: String!
    completed: Boolean
    created_at: Datetime
    updated_at: Datetime
}

コードの生成

$ go run github.com/99designs/gqlgen

実装

resolver から参照できるようにフィールドを追加する。

type Resolver struct {
	EntClient *ent.Client
}

query.resolvers.go を実装する。

// Todos is the resolver for the todos field.
func (r *queryResolver) Todos(ctx context.Context) ([]*model.Todo, error) {
	todos := []*model.Todo{}
	searched, _ := r.EntClient.Todo.Query().All(ctx)
	for _, todo := range searched {
		todos = append(todos, &model.Todo{
			ID:        strconv.Itoa(todo.ID),
			Title:     todo.Title,
			Note:      todo.Note,
			Completed: todo.Completed,
			CreatedAt: todo.CreatedAt.String(),
			UpdatedAt: todo.UpdatedAt.String(),
		})
	}
	return todos, nil
}

mutation.resolvers.go を実装する。

// CreateTodo is the resolver for the createTodo field.
func (r *mutationResolver) CreateTodo(ctx context.Context, input model.NewTodo) (*model.Todo, error) {
	created, _ := r.EntClient.Todo.Create().
		SetTitle(input.Title).
		SetNote(input.Note).
		Save(ctx)

	return &model.Todo{
		ID:        strconv.Itoa(created.ID),
		Title:     created.Title,
		Note:      created.Note,
		Completed: created.Completed,
		CreatedAt: created.CreatedAt.String(),
		UpdatedAt: created.UpdatedAt.String(),
	}, nil
}

server の実装

最後は echo で実行できるようにする。server.go は不要なので削除。

package main

import (
	"context"
	"gqlgen-todo/ent"
	"gqlgen-todo/graph"
	"net/http"

	"github.com/99designs/gqlgen/graphql/handler"
	"github.com/99designs/gqlgen/graphql/playground"
	"github.com/labstack/echo/v4"
	_ "github.com/mattn/go-sqlite3"
)

const (
	DRIVER      = "sqlite3"
	DATA_SOURCE = "file:ent?mode=memory&cache=shared&_fk=1"
)

func main() {
	ctx := context.Background()
	client, _ := ent.Open(DRIVER, DATA_SOURCE)
	client.Schema.Create(ctx)

	e := echo.New()
	e.GET("/health", func(c echo.Context) error {
		return c.String(http.StatusOK, "ok")
	})

	graphqlHandler := handler.NewDefaultServer(
		graph.NewExecutableSchema(
			graph.Config{
				Resolvers: &graph.Resolver{
					EntClient: client,
				},
			},
		),
	)
	playgroudHandler := playground.Handler("GraphQL", "/query")

	e.POST("/query", func(c echo.Context) error {
		graphqlHandler.ServeHTTP(c.Response(), c.Request())
		return nil
	})

	e.GET("playground", func(c echo.Context) error {
		playgroudHandler.ServeHTTP(c.Response(), c.Request())
		return nil
	})

	e.Logger.Fatal(e.Start(":8080"))
}

試す

mutation {
  createTodo(input: {
    title: "title1",
    note: "note1"
  }) {
    id
    title
    note
    completed
    created_at
    updated_at
  }
}

{
  "data": {
    "createTodo": {
      "id": "1",
      "title": "title1",
      "note": "note1",
      "completed": false,
      "created_at": "2023-02-23 17:33:13",
      "updated_at": "2023-02-23 17:33:13"
    }
  }
}
{
  todos {
    id
    title
    note
    completed
    created_at
    updated_at
  }
}

{
  "data": {
    "todos": [
      {
        "id": "1",
        "title": "title1",
        "note": "note1",
        "completed": false,
        "created_at": "2023-02-23 17:33:13",
        "updated_at": "2023-02-23 17:33:13"
      },
      {
        "id": "2",
        "title": "title2",
        "note": "note2",
        "completed": false,
        "created_at": "2023-02-23 17:33:13",
        "updated_at": "2023-02-23 17:33:13"
      },
      {
        "id": "3",
        "title": "title3",
        "note": "note3",
        "completed": false,
        "created_at": "2023-02-23 17:33:13",
        "updated_at": "2023-02-23 17:33:13"
      }
    ]
  }
}