メインコンテンツへスキップする

スキーマ生成

In this section, we will continue the GraphQL example by explaining how to generate a type-safe GraphQL schema from our ent/schema.

Entの設定

ent/entc.go ファイルに移動し、ハイライトされた行(拡張機能オプション)を追加する:

ent/entc.go
func main() {
ex, err := entgql.NewExtension(
entgql.WithWhereInputs(true),
entgql.WithConfigPath("../gqlgen.yml"),
entgql.WithSchemaGenerator(),
entgql.WithSchemaPath("../ent.graphql"),
)
if err != nil {
log.Fatalf("creating entgql extension: %v", err)
}
opts := []entc.Option{
entc.Extensions(ex),
entc.TemplateDir("./template"),
}
if err := entc.Generate("./schema", &gen.Config{}, opts...); err != nil {
log.Fatalf("running ent codegen: %v", err)
}
}

WithSchemaGeneratorオプションにより、GraphQLのスキーマ生成が有効になります。

Todo スキーマにアノテーションを追加

entgql.RelayConnection() アノテーションは、Todo 型の Relayである <T>Edge<T>Connection、および PageInfo のような型を生成するために使用されます。

entgql.QueryField() アノテーションは、Query 型の todos フィールドを生成するために使用されます。

ent/schema/todo.go
// Todoのエッジ
func (Todo) Edges() []ent.Edge {
return []ent.Edge{
edge.To("parent", Todo.Type).
Unique().
From("children").
}
}

// Todoのアノテーション
func (Todo) Annotations() []schema.Annotation {
return []schema.Annotation{
entgql.RelayConnection(),
entgql.QueryField(),
}
}

entgql.RelayConnection() アノテーションはエッジフィールドでも使用でき、first, last, after, before... などの引数を生成してフィールドの型を <T>Connection! に変更します。 例えば、children フィールドを children: [Todo!]! から children(first: Int, last: Int, after: Cursor, before: Cursor): TodoConnection! に変更します。 エッジフィールドに entgql.RelayConnection() アノテーションを追加することができます:

ent/schema/todo.go
// Todoのエッジ
func (Todo) Edges() []ent.Edge {
return []ent.Edge{
edge.To("parent", Todo.Type).
Unique().
From("children").
Annotations(entgql.RelayConnection()),
}
}

手書きのスキーマを削除

EntGQLが ent.graphql ファイルに生成する型との競合を避けるために、 todo.graphql ファイルから以下の型を削除してください

todo.graphql
-interface Node {
- id: ID!
-}

"""Maps a Time GraphQL scalar to a Go time.Time struct."""
scalar Time

-"""
-Define a Relay Cursor type:
-https://relay.dev/graphql/connections.htm#sec-Cursor
-"""
-scalar Cursor

-"""
-Define an enumeration type and map it later to Ent enum (Go type).
-https://graphql.org/learn/schema/#enumeration-types
-"""
-enum Status {
- IN_PROGRESS
- COMPLETED
-}
-
-type PageInfo {
- hasNextPage: Boolean!
- hasPreviousPage: Boolean!
- startCursor: Cursor
- endCursor: Cursor
-}

-type TodoConnection {
- totalCount: Int!
- pageInfo: PageInfo!
- edges: [TodoEdge]
-}

-type TodoEdge {
- node: Todo
- cursor: Cursor!
-}

-"""The following enums match the entgql annotations in the ent/schema."""
-enum TodoOrderField {
- CREATED_AT
- PRIORITY
- STATUS
- TEXT
-}

-enum OrderDirection {
- ASC
- DESC
-}

input TodoOrder {
direction: OrderDirection!
field: TodoOrderField
}

-"""
-Define an object type and map it later to the generated Ent model.
-https://graphql.org/learn/schema/#object-types-and-fields
-"""
-type Todo implements Node {
- id: ID!
- createdAt: Time
- status: Status!
- priority: Int!
- text: String!
- parent: Todo
- children: [Todo!]
-}

"""
Define an input type for the mutation below.
https://graphql.org/learn/schema/#input-types
Note that this type is mapped to the generated
input type in mutation_input.go.
"""
input CreateTodoInput {
status: Status! = IN_PROGRESS
priority: Int
text: String
parentID: ID
ChildIDs: [ID!]
}

"""
Define an input type for the mutation below.
https://graphql.org/learn/schema/#input-types
Note that this type is mapped to the generated
input type in mutation_input.go.
"""
input UpdateTodoInput {
status: Status
priority: Int
text: String
parentID: ID
clearParent: Boolean
addChildIDs: [ID!]
removeChildIDs: [ID!]
}

"""
Define a mutation for creating todos.
https://graphql.org/learn/queries/#mutations
"""
type Mutation {
createTodo(input: CreateTodoInput!): Todo!
updateTodo(id: ID!, input: UpdateTodoInput!): Todo!
updateTodos(ids: [ID!]!, input: UpdateTodoInput!): [Todo!]!
}

-"""Define a query for getting all todos and support the Node interface."""
-type Query {
- todos(after: Cursor, first: Int, before: Cursor, last: Int, orderBy: TodoOrder, where: TodoWhereInput): TodoConnection
- node(id: ID!): Node
- nodes(ids: [ID!]!): [Node]!
-}

Ent と GQLGen の実行順序を守る

Ent と GQLGen が正しい順番で実行されるようにするために generate.go ファイルにもいくつかの変更を加える必要があります。 これは、GQLGen が Entによって生成されたオブジェクトを確認し、コードジェネレータを適切に実行することを保障するためです。

まず、 ent/generate.go ファイルを削除します。 次に、 ent/entc.go ファイルを正しいパスで更新します。これは、Entのコード生成がプロジェクトのルートディレクトリから実行されるためです。

ent/entc.go
func main() {
ex, err := entgql.NewExtension(
entgql.WithWhereInputs(true),
- entgql.WithConfigPath("../gqlgen.yml"),
+ entgql.WithConfigPath("./gqlgen.yml"),
entgql.WithSchemaGenerator(),
- entgql.WithSchemaPath("../ent.graphql"),
+ entgql.WithSchemaPath("./ent.graphql"),
)
if err != nil {
log.Fatalf("creating entgql extension: %v", err)
}
opts := []entc.Option{
entc.Extensions(ex),
- entc.TemplateDir("./template"),
+ entc.TemplateDir("./ent/template"),
}
- if err := entc.Generate("./schema", &gen.Config{}, opts...); err != nil {
+ if err := entc.Generate("./ent/schema", &gen.Config{}, opts...); err != nil {
log.Fatalf("running ent codegen: %v", err)
}
}

generate.go を更新して Ent のコード生成が実行されるようにします。

generate.go
package todo

//go:generate go run -mod=mod ./ent/entc.go
//go:generate go run -mod=mod github.com/99designs/gqlgen

generate.goファイルを変更したので、以下のようにコード生成を実行できるようになりました:

go generate ./...

EntGQLのスキーマジェネレータにより ent.graphql ファイルの内容が更新されます。

Entによって生成された型を拡張する

生成された型の中には、いくつかのフィールドを持った Query 型が定義されているということを覚えておいてください:

type Query {
"""Fetches an object given its ID."""
node(
"""ID of the object."""
id: ID!
): Node
"""Lookup nodes by a list of IDs."""
nodes(
"""The list of node IDs."""
ids: [ID!]!
): [Node]!
todos(
"""Returns the elements in the list that come after the specified cursor."""
after: Cursor

"""Returns the first _n_ elements from the list."""
first: Int

"""Returns the elements in the list that come before the specified cursor."""
before: Cursor

"""Returns the last _n_ elements from the list."""
last: Int

"""Ordering options for Todos returned from the connection."""
orderBy: TodoOrder

"""Filtering options for Todos returned from the connection."""
where: TodoWhereInput
): TodoConnection!
}

以下のようにして Query 型に新しいフィールドを追加することができます。

todo.graphql
extend type Query {
"""Returns the literal string 'pong'."""
ping: String!
}

Ent によって生成された任意の型を拡張できます。 あるフィールドを型に加えたくない場合、そのフィールドまたはエッジに entgql.Skip() を使用してください。


お疲れ様です! このようにスキーマジェネレータ機能を利用すれば、もうGQLスキーマを手書きする必要はありません。 何か質問はありますか? 始めるにあたって手助けが必要ですか? 何かありましたら、私たちの Discord サーバーSlack チャンネル にお気軽にご参加ください。