Saltar al contenido principal

Conexiones Relay con Cursor (Paginació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 esta sección, continuamos con el ejemplo de GraphQL explicando cómo implementar la Especificación de Conexiones Relay con Cursor. Si no estás familiarizado con la interfaz de Conexiones con Cursor, lee los siguientes párrafos tomados de relay.dev:

En la consulta, el modelo de conexión proporciona un mecanismo estándar para segmentar y paginar el conjunto de resultados.

En la respuesta, el modelo de conexión proporciona una forma estándar de ofrecer cursores y de indicar al cliente cuándo hay más resultados disponibles.

Un ejemplo que incluye todos estos elementos es la siguiente consulta:

{
user {
id
name
friends(first: 10, after: "opaqueCursor") {
edges {
cursor
node {
id
name
}
}
pageInfo {
hasNextPage
}
}
}
}

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 quieres saltarte 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/

Añadir anotaciones al esquema

El ordenamiento puede definirse en cualquier campo comparable de Ent mediante la anotación entgql.Annotation. Nota que el nombre OrderField debe estar en mayúsculas y coincidir con su valor de enumeración en el esquema GraphQL.

ent/schema/todo.go
func (Todo) Fields() []ent.Field {
return []ent.Field{
field.Text("text").
NotEmpty().
Annotations(
entgql.OrderField("TEXT"),
),
field.Time("created_at").
Default(time.Now).
Immutable().
Annotations(
entgql.OrderField("CREATED_AT"),
),
field.Enum("status").
NamedValues(
"InProgress", "IN_PROGRESS",
"Completed", "COMPLETED",
).
Default("IN_PROGRESS").
Annotations(
entgql.OrderField("STATUS"),
),
field.Int("priority").
Default(0).
Annotations(
entgql.OrderField("PRIORITY"),
),
}
}

Ordenar por múltiples campos

Por defecto, el argumento orderBy solo acepta un único valor <T>Order. Para habilitar el ordenamiento por múltiples campos, simplemente añade la anotación entgql.MultiOrder() al esquema deseado.

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

Al añadir esta anotación al esquema Todo, el argumento orderBy cambiará de TodoOrder a [TodoOrder!].

Ordenar por recuento de relaciones

Las aristas no únicas pueden anotarse con OrderField para habilitar el ordenamiento de nodos basado en el recuento de tipos específicos de aristas.

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

La convención de nombres para este término de ordenamiento es: UPPER(<edge-name>)_COUNT. Por ejemplo, CHILDREN_COUNT o POSTS_COUNT.

Ordenar por campo de relación

Las aristas únicas pueden anotarse con OrderField para permitir ordenar nodos por campos asociados a sus aristas. Por ejemplo, ordenar publicaciones por el nombre de su autor u ordenar todos por la prioridad de su padre. Nota que para ordenar por un campo de arista, dicho campo debe estar anotado con OrderField en el tipo referenciado.

La convención de nombres para este término de ordenamiento es: UPPER(<edge-name>)_<edge-field>. Por ejemplo, PARENT_PRIORITY.

ent/schema/todo.go
// Fields returns todo fields.
func (Todo) Fields() []ent.Field {
return []ent.Field{
// ...
field.Int("priority").
Default(0).
Annotations(
entgql.OrderField("PRIORITY"),
),
}
}

// Edges returns todo edges.
func (Todo) Edges() []ent.Edge {
return []ent.Edge{
edge.To("children", Todo.Type).
From("parent").
Annotations(
entgql.OrderField("PARENT_PRIORITY"),
).
Unique(),
}
}
información

La convención de nombres para este término de ordenamiento es: UPPER(<edge-name>)_<edge-field>. Por ejemplo, PARENT_PRIORITY o AUTHOR_NAME.

Añadir soporte de paginación para consultas

1. El siguiente paso para habilitar la paginación es indicar a Ent que el tipo Todo es una Conexión Relay.

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

2. Luego, ejecuta go generate . y notarás que ent.resolvers.go ha cambiado. Dirígete al resolver Todos y actualízalo para pasar los argumentos de paginación a .Paginate():

ent.resolvers.go
func (r *queryResolver) Todos(ctx context.Context, after *ent.Cursor, first *int, before *ent.Cursor, last *int, orderBy *ent.TodoOrder) (*ent.TodoConnection, error) {
return r.client.Todo.Query().
Paginate(ctx, after, first, before, last,
ent.WithTodoOrder(orderBy),
)
}
[Configuración de Conexiones Relay]

La función entgql.RelayConnection() indica que el nodo o arista debe soportar paginación. Por lo tanto, el resultado devuelto será una conexión Relay en lugar de una lista de nodos ([T!]! => <T>Connection!).

Al aplicar esta anotación en el esquema T (ubicado en ent/schema), se habilita la paginación para este nodo. Ent generará automáticamente todos los tipos Relay asociados: <T>Edge, <T>Connection y PageInfo. Por ejemplo:

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

Al aplicar esta anotación en una arista, indica que el campo GraphQL correspondiente debe soportar paginación anidada, devolviendo una conexión Relay. Ejemplo:

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

El esquema GraphQL generado será:

-children: [Todo!]!
+children(first: Int, last: Int, after: Cursor, before: Cursor): TodoConnection!

Uso de la Paginación

Ahora podemos probar nuestros nuevos resolvers de GraphQL. Comencemos creando varios elementos "todo" ejecutando esta consulta múltiples veces (modificar las variables es opcional):

mutation CreateTodo($input: CreateTodoInput!) {
createTodo(input: $input) {
id
text
createdAt
priority
parent {
id
}
}
}

# Query Variables: { "input": { "text": "Create GraphQL Example", "status": "IN_PROGRESS", "priority": 1 } }
# Output: { "data": { "createTodo": { "id": "2", "text": "Create GraphQL Example", "createdAt": "2021-03-10T15:02:18+02:00", "priority": 1, "parent": null } } }

Luego, podemos consultar nuestra lista de tareas utilizando la API de paginación:

query {
todos(first: 3, orderBy: {direction: DESC, field: TEXT}) {
edges {
node {
id
text
}
cursor
}
}
}

# Output: { "data": { "todos": { "edges": [ { "node": { "id": "16", "text": "Create GraphQL Example" }, "cursor": "gqFpEKF2tkNyZWF0ZSBHcmFwaFFMIEV4YW1wbGU" }, { "node": { "id": "15", "text": "Create GraphQL Example" }, "cursor": "gqFpD6F2tkNyZWF0ZSBHcmFwaFFMIEV4YW1wbGU" }, { "node": { "id": "14", "text": "Create GraphQL Example" }, "cursor": "gqFpDqF2tkNyZWF0ZSBHcmFwaFFMIEV4YW1wbGU" } ] } } }

También podemos usar el cursor que obtuvimos en la consulta anterior para obtener todos los elementos que vienen después.

query {
todos(first: 3, after:"gqFpEKF2tkNyZWF0ZSBHcmFwaFFMIEV4YW1wbGU", orderBy: {direction: DESC, field: TEXT}) {
edges {
node {
id
text
}
cursor
}
}
}

# Output: { "data": { "todos": { "edges": [ { "node": { "id": "15", "text": "Create GraphQL Example" }, "cursor": "gqFpD6F2tkNyZWF0ZSBHcmFwaFFMIEV4YW1wbGU" }, { "node": { "id": "14", "text": "Create GraphQL Example" }, "cursor": "gqFpDqF2tkNyZWF0ZSBHcmFwaFFMIEV4YW1wbGU" }, { "node": { "id": "13", "text": "Create GraphQL Example" }, "cursor": "gqFpDaF2tkNyZWF0ZSBHcmFwaFFMIEV4YW1wbGU" } ] } } }

¡Excelente! Con unos pocos cambios, nuestra aplicación ahora soporta paginación. Continúa en la siguiente sección donde explicamos cómo implementar colecciones de campos en GraphQL y cómo Ent resuelve el "problema N+1" en los resolvers.