Saltar al contenido principal

Introducción

[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 este tutorial aprenderemos a conectar Ent con GraphQL y configurar las diversas integraciones que proporciona Ent, como:

  1. Generar un esquema GraphQL para nodos y aristas definidos en un esquema Ent.

  2. Resolvedores Query y Mutation auto-generados con integración fluida del framework Relay.

  3. Filtrado, paginación (incluyendo anidada) y soporte compatible con la especificación Relay Cursor Connections.

  4. Recolección eficiente de campos para evitar el problema N+1 sin necesidad de data loaders.

  5. Mutaciones transaccionales para garantizar consistencia ante fallos.

Si no estás familiarizado con GraphQL, se recomienda revisar su guía de introducción antes de continuar con este tutorial.

Clonar el código (opcional)

El código de este tutorial está disponible en github.com/a8m/ent-graphql-example, con etiquetas (usando Git) en cada paso. Si deseas saltar la configuración básica y comenzar con la versión inicial del servidor GraphQL, puedes clonar el repositorio así:

git clone git@github.com:a8m/ent-graphql-example.git
cd ent-graphql-example
go run ./cmd/todo

Configuración básica

Este tutorial comienza donde terminó el anterior (con un esquema funcional de lista Todo). Empezaremos instalando la extensión Ent contrib/entgql para generar nuestro primer esquema. Luego, instalaremos y configuraremos el framework 99designs/gqlgen para construir nuestro servidor GraphQL y exploraremos la integración oficial que Ent proporciona para él.

Instalar y configurar entgql

1. Instalar entgql:

go get entgo.io/contrib/entgql@master

2. Añade las siguientes anotaciones al esquema Todo para habilitar capacidades de Query y Mutation (creación):

ent/schema/todo.go
func (Todo) Annotations() []schema.Annotation {
return []schema.Annotation{
entgql.QueryField(),
entgql.Mutations(entgql.MutationCreate()),
}
}

3. Crea un nuevo archivo Go llamado ent/entc.go y pega el siguiente contenido:

ent/entc.go
//go:build ignore

package main

import (
"log"

"entgo.io/ent/entc"
"entgo.io/ent/entc/gen"
"entgo.io/contrib/entgql"
)

func main() {
ex, err := entgql.NewExtension(
// Tell Ent to generate a GraphQL schema for
// the Ent schema in a file named ent.graphql.
entgql.WithSchemaGenerator(),
entgql.WithSchemaPath("ent.graphql"),
)
if err != nil {
log.Fatalf("creating entgql extension: %v", err)
}
opts := []entc.Option{
entc.Extensions(ex),
}
if err := entc.Generate("./ent/schema", &gen.Config{}, opts...); err != nil {
log.Fatalf("running ent codegen: %v", err)
}
}
nota

El archivo ent/entc.go se ignora mediante una etiqueta de compilación, y se ejecuta mediante el comando go generate a través del archivo generate.go.

4. Elimina el archivo ent/generate.go y crea uno nuevo en la raíz del proyecto con el siguiente contenido. En pasos posteriores, se añadirán comandos de gqlgen a este archivo.

generate.go
package todo

//go:generate go run -mod=mod ./ent/entc.go

Ejecutar generación de esquema

Tras instalar y configurar entgql, es momento de ejecutar la generación de código:

go generate .

Observarás que se ha creado un nuevo archivo llamado ent.graphql:

ent.graphql
directive @goField(forceResolver: Boolean, name: String) on FIELD_DEFINITION | INPUT_FIELD_DEFINITION
directive @goModel(model: String, models: [String!]) on OBJECT | INPUT_OBJECT | SCALAR | ENUM | INTERFACE | UNION
"""
Define a Relay Cursor type:
https://relay.dev/graphql/connections.htm#sec-Cursor
"""
scalar Cursor
"""
An object with an ID.
Follows the [Relay Global Object Identification Specification](https://relay.dev/graphql/objectidentification.htm)
"""
interface Node @goModel(model: "todo/ent.Noder") {
"""The id of the object."""
id: ID!
}

# ...

Instalar y configurar gqlgen

1. Instalar 99designs/gqlgen:

go get github.com/99designs/gqlgen

2. El paquete gqlgen se configura mediante un archivo gqlgen.yml que se carga automáticamente desde el directorio actual. Añadamos este archivo en la raíz del proyecto. Sigue los comentarios en este archivo para entender cada directiva:

gqlgen.yml
# schema tells gqlgen where the GraphQL schema is located.
schema:
- ent.graphql

# resolver reports where the resolver implementations go.
resolver:
layout: follow-schema
dir: .

# gqlgen will search for any type names in the schema in these go packages
# if they match it will use them, otherwise it will generate them.

# autobind tells gqngen to search for any type names in the GraphQL schema in the
# provided package. If they match it will use them, otherwise it will generate new.
autobind:
- todo/ent
- todo/ent/todo

# This section declares type mapping between the GraphQL and Go type systems.
models:
# Defines the ID field as Go 'int'.
ID:
model:
- github.com/99designs/gqlgen/graphql.IntID
Node:
model:
- todo/ent.Noder

3. Edita ent/entc.go para informar a Ent sobre la configuración de gqlgen:

//go:build ignore

package main

import (
"log"

"entgo.io/ent/entc"
"entgo.io/ent/entc/gen"
"entgo.io/contrib/entgql"
)

func main() {
ex, err := entgql.NewExtension(
// Tell Ent to generate a GraphQL schema for
// the Ent schema in a file named ent.graphql.
entgql.WithSchemaGenerator(),
entgql.WithSchemaPath("ent.graphql"),
entgql.WithConfigPath("gqlgen.yml"),
)
if err != nil {
log.Fatalf("creating entgql extension: %v", err)
}
opts := []entc.Option{
entc.Extensions(ex),
}
if err := entc.Generate("./ent/schema", &gen.Config{}, opts...); err != nil {
log.Fatalf("running ent codegen: %v", err)
}
}

4. Añade el comando de generación de gqlgen al archivo generate.go:

generate.go
package todo

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

Ahora estamos listos para ejecutar go generate y activar la generación de código de ent y gqlgen. Ejecuta el siguiente comando desde la raíz del proyecto:

go generate .

Habrás notado que gqlgen ha generado algunos archivos:

tree -L 1
.
├── ent/
├── ent.graphql
├── ent.resolvers.go
├── example_test.go
├── generate.go
├── generated.go
├── go.mod
├── go.sum
├── gqlgen.yml
└── resolver.go

Servidor Básico

Antes de construir el servidor GraphQL, necesitamos configurar el Resolver principal del esquema definido en resolver.go. gqlgen permite modificar el Resolver generado y añadirle dependencias. Usemos ent.Client como dependencia pegando el siguiente código en resolver.go:

resolver.go
package todo

import (
"todo/ent"

"github.com/99designs/gqlgen/graphql"
)

// Resolver is the resolver root.
type Resolver struct{ client *ent.Client }

// NewSchema creates a graphql executable schema.
func NewSchema(client *ent.Client) graphql.ExecutableSchema {
return NewExecutableSchema(Config{
Resolvers: &Resolver{client},
})
}

Tras configurar el resolver principal, creamos un nuevo directorio cmd/todo y un archivo main.go con el siguiente código para configurar un servidor GraphQL:

cmd/todo/main.go

package main

import (
"context"
"log"
"net/http"

"todo"
"todo/ent"
"todo/ent/migrate"

"entgo.io/ent/dialect"
"github.com/99designs/gqlgen/graphql/handler"
"github.com/99designs/gqlgen/graphql/playground"

_ "github.com/mattn/go-sqlite3"
)

func main() {
// Create ent.Client and run the schema migration.
client, err := ent.Open(dialect.SQLite, "file:ent?mode=memory&cache=shared&_fk=1")
if err != nil {
log.Fatal("opening ent client", err)
}
if err := client.Schema.Create(
context.Background(),
migrate.WithGlobalUniqueID(true),
); err != nil {
log.Fatal("opening ent client", err)
}

// Configure the server and start listening on :8081.
srv := handler.NewDefaultServer(todo.NewSchema(client))
http.Handle("/",
playground.Handler("Todo", "/query"),
)
http.Handle("/query", srv)
log.Println("listening on :8081")
if err := http.ListenAndServe(":8081", nil); err != nil {
log.Fatal("http server terminated", err)
}
}

Ejecuta el servidor con el siguiente comando y abre localhost:8081:

go run ./cmd/todo

Deberías ver el playground interactivo:

Playground de tutorial Todo

Si tienes problemas para hacer funcionar el playground, ve a la primera sección y clona el repositorio de ejemplo.

Consultar Todos

Si intentamos consultar nuestra lista de todos, obtendremos un error porque el método del resolver aún no está implementado. Implementemos el resolver reemplazando la implementación de Todos en el query resolver:

ent.resolvers.go
func (r *queryResolver) Todos(ctx context.Context) ([]*ent.Todo, error) {
- panic(fmt.Errorf("not implemented"))
+ return r.client.Todo.Query().All(ctx)
}

Luego, ejecutar esta consulta GraphQL debería devolver una lista vacía de todos:

query AllTodos {
todos {
id
}
}

Mutar Todos

Como vimos anteriormente, nuestro esquema GraphQL devuelve una lista vacía de elementos todo. Vamos a crear algunos, pero esta vez lo haremos desde GraphQL. Afortunadamente, Ent proporciona mutaciones generadas automáticamente para crear y actualizar nodos y aristas.

1. Comenzamos extendiendo nuestro esquema GraphQL con mutaciones personalizadas. Creamos un nuevo archivo llamado todo.graphql y añadimos nuestro tipo Mutation:

todo.graphql
type Mutation {
# The input and the output are types generated by Ent.
createTodo(input: CreateTodoInput!): Todo
}

2. Añadimos el esquema GraphQL personalizado a la configuración gqlgen.yml:

gqlgen.yml
schema:
- ent.graphql
- todo.graphql
# ...

3. Ejecutamos la generación de código:

go generate .

Como ves, gqlgen nos generó un nuevo archivo llamado todo.resolvers.go con el resolver createTodo. Conectémoslo al código generado por Ent y pidamos a Ent que maneje esta mutación:

todo.resolvers.go
func (r *mutationResolver) CreateTodo(ctx context.Context, input ent.CreateTodoInput) (*ent.Todo, error) {
- panic(fmt.Errorf("not implemented: CreateTodo - createTodo"))
+ return r.client.Todo.Create().SetInput(input).Save(ctx)
}

4. Vuelve a ejecutar go run ./cmd/todo y ve al playground:

Demo

En este punto ya podemos crear un elemento todo y consultarlo:

mutation CreateTodo {
createTodo(input: {text: "Create GraphQL Example", status: IN_PROGRESS, priority: 1}) {
id
text
createdAt
priority
}
}

Si tienes problemas para que funcione este ejemplo, ve a la primera sección y clona el repositorio de ejemplo.


Continúa con la siguiente sección donde explicamos cómo implementar la Interfaz Node de Relay y aprenderás cómo Ent la soporta automáticamente.