Consultando > Mejores Prácticas para Consultas

Mejores Prácticas para Consultas

Reading time: 9 min

The Graph proporciona una forma descentralizada de consultar datos de la blockchain.

Los datos de The Graph Network se exponen a través de una API GraphQL, lo que facilita la consulta de datos con el lenguaje GraphQL.

Esta página te guiará a través de las reglas esenciales del lenguaje GraphQL y las mejores prácticas de consulta GraphQL.


Consulta de una API GraphQL

Enlace a esta sección

Anatomía de una consulta GraphQL

Enlace a esta sección

A diferencia de la API REST, una API GraphQL se basa en un esquema que define las consultas que se pueden realizar.

Por ejemplo, una consulta para obtener un token utilizando la consulta de token tendrá el siguiente aspecto:

query GetToken($id: ID!) {
token(id: $id) {
id
owner
}
}

que devolverá la siguiente respuesta JSON predecible (al pasar el valor adecuado de la variable $id):

{
"token": {
"id": "...",
"owner": "..."
}
}

Las consultas GraphQL utilizan el lenguaje GraphQL, que se define a partir de una especificación.

La consulta GetToken anterior se compone de varias partes lingüísticas (sustituidas a continuación por marcadores de posición [...]):

query [operationName]([variableName]: [variableType]) {
[queryName]([argumentName]: [variableName]) {
# "{ ... }" express a Selection-Set, we are querying fields from `queryName`.
[field]
[field]
}
}

Aunque la lista de lo que se debe y no se debe hacer sintácticamente es larga, estas son las reglas esenciales que hay que tener en cuenta a la hora de escribir consultas GraphQL:

  • Cada queryName sólo debe utilizarse una vez por operación.
  • Cada field debe utilizarse una sola vez en una selección (no podemos consultar el id dos veces bajo token)
  • Algunos field o consultas (como los tokens) devuelven tipos complejos que requieren una selección de sub-field. No proporcionar una selección cuando se espera (o proporcionarla cuando no se espera - por ejemplo, en id) generará un error. Para conocer un tipo de campo, consulta The Graph Explorer.
  • Cualquier variable asignada a un argumento debe coincidir con su tipo.
  • En una lista dada de variables, cada una de ellas debe ser única.
  • Deben utilizarse todas las variables definidas.

Si no se siguen las reglas anteriores, se producirá un error de la API Graph.

Para obtener una lista completa de reglas con ejemplos de código, consulta nuestra guía de Validaciones GraphQL.

Envío de una consulta a una API GraphQL

Enlace a esta sección

GraphQL is a language and set of conventions that transport over HTTP.

It means that you can query a GraphQL API using standard fetch (natively or via @whatwg-node/fetch or isomorphic-fetch).

However, as stated in "Querying from an Application", we recommend you to use our graph-client that supports unique features such as:

Here's how to query The Graph with graph-client:

import { execute } from '../.graphclient'
const query = `
query GetToken($id: ID!) {
token(id: $id) {
id
owner
}
}
`
const variables = { id: '1' }
async function main() {
const result = await execute(query, variables)
// `result` is fully typed!
console.log(result)
}
main()

More GraphQL client alternatives are covered in "Querying from an Application".

Now that we covered the basic rules of GraphQL queries syntax, let's now look at the best practices of GraphQL query writing.


Escribir consultas GraphQL

Enlace a esta sección

Escribe siempre consultas estáticas

Enlace a esta sección

A common (bad) practice is to dynamically build query strings as follows:

const id = params.id
const fields = ['id', 'owner']
const query = `
query GetToken {
token(id: ${id}) {
${fields.join('\n')}
}
}
`
// Execute query...

While the above snippet produces a valid GraphQL query, it has many drawbacks:

  • Dificulta la comprensión de la consulta en su conjunto
  • Los desarrolladores son responsables de sanear de forma segura la interpolación de strings
  • No enviar los valores de las variables como parte de los parámetros de la solicitud para evitar un posible almacenamiento en caché en el servidor
  • Impide que las herramientas analicen estáticamente la consulta (por ejemplo, Linter o las herramientas de generación de tipos)

For this reason, it is recommended to always write queries as static strings:

import { execute } from 'your-favorite-graphql-client'
const id = params.id
const query = `
query GetToken($id: ID!) {
token(id: $id) {
id
owner
}
}
`
const result = await execute(query, {
variables: {
id,
},
})

Doing so brings many advantages:

  • Consultas fáciles de leer y mantener
  • El servidor GraphQL se encarga de la limpieza de las variables
  • Las variables pueden almacenarse en caché a nivel de servidor
  • Las consultas pueden ser analizadas estáticamente por herramientas (más información al respecto en las secciones siguientes)

Note: How to include fields conditionally in static queries

We might want to include the owner field only on a particular condition.

For this, we can leverage the @include(if:...) directive as follows:

import { execute } from 'your-favorite-graphql-client'
const id = params.id
const query = `
query GetToken($id: ID!, $includeOwner: Boolean) {
token(id: $id) {
id
owner @include(if: $includeOwner)
}
}
`
const result = await execute(query, {
variables: {
id,
includeOwner: true,
},
})

Note: The opposite directive is @skip(if: ...).

Consejos de rendimiento

Enlace a esta sección

"Ask for what you want"

GraphQL became famous for its "Ask for what you want" tagline.

For this reason, there is no way, in GraphQL, to get all available fields without having to list them individually.

When querying GraphQL APIs, always think of querying only the fields that will be actually used.

A common cause of over-fetching is collections of entities. By default, queries will fetch 100 entities in a collection, which is usually much more than what will actually be used, e.g., for display to the user. Queries should therefore almost always set first explicitly, and make sure they only fetch as many entities as they actually need. This applies not just to top-level collections in a query, but even more so to nested collections of entities.

For example, in the following query:

query listTokens {
tokens {
# will fetch up to 100 tokens
id
transactions {
# will fetch up to 100 transactions
id
}
}
}

The response could contain 100 transactions for each of the 100 tokens.

If the application only needs 10 transactions, the query should explicitly set first: 10 on the transactions field.

Combining multiple queries

Your application might require querying multiple types of data as follows:

import { execute } from "your-favorite-graphql-client"
const tokensQuery = `
query GetTokens {
tokens(first: 50) {
id
owner
}
}
`
const countersQuery = `
query GetCounters {
counters {
id
value
}
}
`
const [tokens, counters] = Promise.all(
[
tokensQuery,
countersQuery,
].map(execute)
)

While this implementation is totally valid, it will require two round trips with the GraphQL API.

Fortunately, it is also valid to send multiple queries in the same GraphQL request as follows:

import { execute } from "your-favorite-graphql-client"
const query = `
query GetTokensandCounters {
tokens(first: 50) {
id
owner
}
counters {
id
value
}
}
`
const { result: { tokens, counters } } = execute(query)

This approach will improve the overall performance by reducing the time spent on the network (saves you a round trip to the API) and will provide a more concise implementation.

Aprovechar los GraphQL Fragments

Enlace a esta sección

A helpful feature to write GraphQL queries is GraphQL Fragment.

Looking at the following query, you will notice that some fields are repeated across multiple Selection-Sets ({ ... }):

query {
bondEvents {
id
newDelegate {
id
active
status
}
oldDelegate {
id
active
status
}
}
}

Such repeated fields (id, active, status) bring many issues:

  • más difícil de leer para consultas más extensas
  • cuando se utilizan herramientas que generan tipos TypeScript basados en consultas (más sobre esto en la última sección), newDelegate y oldDelegate darán como resultado dos interfaces en línea distintas.

A refactored version of the query would be the following:

query {
bondEvents {
id
newDelegate {
...DelegateItem
}
oldDelegate {
...DelegateItem
}
}
}
# we define a fragment (subtype) on Transcoder
# to factorize repeated fields in the query
fragment DelegateItem on Transcoder {
id
active
status
}

Using GraphQL fragment will improve readability (especially at scale) but also will result in better TypeScript types generation.

When using the types generation tool, the above query will generate a proper DelegateItemFragment type (see last "Tools" section).

Qué hacer y qué no hacer con los GraphQL Fragments

Enlace a esta sección

Fragment base must be a type

A Fragment cannot be based on a non-applicable type, in short, on type not having fields:

fragment MyFragment on BigInt {
# ...
}

BigInt is a scalar (native "plain" type) that cannot be used as a fragment's base.

How to spread a Fragment

Fragments are defined on specific types and should be used accordingly in queries.

Ejemplo:

query {
bondEvents {
id
newDelegate {
...VoteItem # Error! `VoteItem` cannot be spread on `Transcoder` type
}
oldDelegate {
...VoteItem
}
}
}
fragment VoteItem on Vote {
id
voter
}

newDelegate and oldDelegate are of type Transcoder.

It is not possible to spread a fragment of type Vote here.

Define Fragment as an atomic business unit of data

GraphQL Fragment must be defined based on their usage.

For most use-case, defining one fragment per type (in the case of repeated fields usage or type generation) is sufficient.

Here is a rule of thumb for using Fragment:

  • cuando se repiten campos del mismo tipo en una consulta, agruparlos en un Fragment
  • cuando se repiten campos similares pero no iguales, crear varios Fragments, ej:
# base fragment (mostly used in listing)
fragment Voter on Vote {
id
voter
}
# extended fragment (when querying a detailed view of a vote)
fragment VoteWithPoll on Vote {
id
voter
choiceID
poll {
id
proposal
}
}

Las herramientas esenciales

Enlace a esta sección

Exploradores web GraphQL

Enlace a esta sección

Iterating over queries by running them in your application can be cumbersome. For this reason, don't hesitate to use The Graph Explorer to test your queries before adding them to your application. The Graph Explorer will provide you a preconfigured GraphQL playground to test your queries.

If you are looking for a more flexible way to debug/test your queries, other similar web-based tools are available such as Altair and GraphiQL.

GraphQL Linting

Enlace a esta sección

In order to keep up with the mentioned above best practices and syntactic rules, it is highly recommended to use the following workflow and IDE tools.

GraphQL ESLint

GraphQL ESLint will help you stay on top of GraphQL best practices with zero effort.

Setup the "operations-recommended" config will enforce essential rules such as:

  • @graphql-eslint/fields-on-correct-type: ¿se utiliza un campo en un tipo correcto?
  • @graphql-eslint/no-unused variables: ¿debe una variable determinada permanecer sin usar?
  • ¡y mucho más!

This will allow you to catch errors without even testing queries on the playground or running them in production!

VSCode and GraphQL

The GraphQL VSCode extension is an excellent addition to your development workflow to get:

  • resaltado de sintaxis
  • sugerencias de autocompletar
  • validación según el esquema
  • fragmentos
  • ir a la definición de fragments y tipos de entrada

If you are using graphql-eslint, the ESLint VSCode extension is a must-have to visualize errors and warnings inlined in your code correctly.

WebStorm/Intellij and GraphQL

The JS GraphQL plugin will significantly improve your experience while working with GraphQL by providing:

  • resaltado de sintaxis
  • sugerencias de autocompletar
  • validación según el esquema
  • fragmentos

More information on this WebStorm article that showcases all the plugin's main features.

Editar página

Anterior
Administración de tus claves API
Siguiente
Consultar desde una Aplicación
Editar página