Saltar al contenido principal

Generador de esquema

[Traducción Beta No Oficial]

Esta página fue traducida por PageTurner AI (beta). No está respaldada oficialmente por el proyecto. ¿Encontraste un error? Reportar problema →

En esta sección, continuaremos con el ejemplo de GraphQL explicando cómo generar un esquema GraphQL con verificación de tipos a partir de nuestro ent/schema.

Configurar Ent

Dirígete a tu archivo ent/entc.go y añade la línea resaltada (opciones de extensión):

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)
}
}

La opción WithSchemaGenerator habilita la generación del esquema GraphQL.

Añadir anotaciones al esquema Todo

La anotación entgql.RelayConnection() se usa para generar los tipos Relay <T>Edge, <T>Connection y PageInfo para el tipo Todo.

La anotación entgql.QueryField() se usa para generar el campo todos en el tipo Query.

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

// Annotations of the Todo.
func (Todo) Annotations() []schema.Annotation {
return []schema.Annotation{
entgql.RelayConnection(),
entgql.QueryField(),
}
}

La anotación entgql.RelayConnection() también puede usarse en campos de relación (edges) para generar argumentos como first, last, after, before... y cambiar el tipo del campo a <T>Connection!. Por ejemplo, para cambiar el campo children de children: [Todo!]! a children(first: Int, last: Int, after: Cursor, before: Cursor): TodoConnection!. Puedes añadir la anotación entgql.RelayConnection() al campo de relación:

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

Limpiar el esquema escrito manualmente

Elimina los siguientes tipos del archivo todo.graphql para evitar conflictos con los tipos generados por EntGQL en el archivo ent.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]!
-}

Asegurar el orden de ejecución de Ent y GQLGen

También necesitamos hacer algunos cambios en nuestros archivos generate.go para asegurar el orden de ejecución entre Ent y GQLGen. Esto es necesario para que GQLGen reconozca los objetos creados por Ent y ejecute el generador de código correctamente.

Primero, elimina el archivo ent/generate.go. Luego, actualiza el archivo ent/entc.go con la ruta correcta, ya que la generación de código de Ent se ejecutará desde el directorio raíz del proyecto.

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)
}
}

Actualiza el archivo generate.go para incluir la generación de código de Ent.

generate.go
package todo

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

Tras modificar el archivo generate.go, estamos listos para ejecutar la generación de código así:

go generate ./...

Verás que el archivo ent.graphql se actualizará con nuevo contenido del Generador de Esquema de EntGQL.

Extender tipos generados por Ent

Puedes observar que el tipo generado incluirá el objeto Query con algunos campos ya definidos:

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!
}

Para añadir nuevos campos al tipo Query, puedes hacer lo siguiente:

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

Puedes extender cualquier tipo generado por Ent. Para omitir un campo del tipo, usa entgql.Skip() en ese campo o relación.


¡Bien hecho! Como ves, tras adaptar la función del Generador de Esquemas ya no necesitamos escribir esquemas GQL manualmente. ¿Tienes preguntas? ¿Necesitas ayuda para empezar? Únete a nuestro servidor de Discord o al canal de Slack.