subgraphs > Querying > Bästa praxis för förfrågningar

Bästa praxis för förfrågningar

Reading time: 9 min

The Graph provides a decentralized way to query data from blockchains. Its data is exposed through a GraphQL API, making it easier to query with the GraphQL language.

Learn the essential GraphQL language rules and best practices to optimize your subgraph.


Att fråga ett GraphQL API

Länk till detta avsnitt

The Anatomy of a GraphQL Query

Länk till detta avsnitt

Till skillnad från REST API bygger ett GraphQL API på ett schema som definierar vilka frågor som kan utföras.

Till exempel kommer en fråga för att hämta en token med hjälp av frågan token att se ut som följer:

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

som kommer att returnera följande förutsägbara JSON-svar (när du skickar rätt $id variabelvärde):

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

GraphQL-frågor använder GraphQL-språket, som definieras i en specifikation.

Ovanstående GetToken-fråga består av flera språkdelar (ersätts nedan med [...] platshållare):

query [operationName]([variableName]: [variableType]) {
[queryName]([argumentName]: [variableName]) {
# "{ ... }" uttrycker en Selection-Set, vi frågar efter fält från `queryName`.
[field]
[field]
}
}

Rules for Writing GraphQL Queries

Länk till detta avsnitt
  • Varje queryName får endast användas en gång per operation.
  • Varje field får bara användas en gång i ett urval (vi kan inte fråga id två gånger under token)
  • Some fields or queries (like tokens) return complex types that require a selection of sub-field. Not providing a selection when expected (or providing one when not expected - for example, on id) will raise an error. To know a field type, please refer to Graph Explorer.
  • Varje variabel som tilldelas ett argument måste matcha dess typ.
  • I en given lista med variabler måste var och en av dem vara unik.
  • Alla definierade variabler måste användas.

Note: Failing to follow these rules will result in an error from The Graph API.

For a complete list of rules with code examples, check out GraphQL Validations guide.

Att skicka en fråga till ett GraphQL API

Länk till detta avsnitt

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 mentioned in "Querying from an Application", it's recommended to use graph-client, which supports the following unique features:

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` är fullständigt typad!
console.log(result)
}
main()

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


Skriv alltid statiska frågor

Länk till detta avsnitt

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:

  • det gör det svårare att förstå frågan som helhet
  • utvecklare är ansvariga för att säkert sanera stränginterpolationen
  • att inte skicka värdena av variablerna som en del av förfrågningsparametrarna förhindrar möjlig cache på servern
  • det hindrar verktyg från statisk analys av frågan (exempel: Linter eller typgenereringsverktyg)

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:

  • Lättlästa och underhållna frågor
  • GraphQL server hanterar sanitet av variabler
  • Variabler kan cachas på serversidan
  • Frågor kan statiskt analyseras av verktyg (mer om detta i följande avsnitt)

How to include fields conditionally in static queries

Länk till detta avsnitt

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

For this, you 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: ...).

Ask for what you want

Länk till detta avsnitt

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.

  • När du frågar GraphQL API:er, tänk alltid på att endast fråga efter de fält som faktiskt kommer att användas.
  • Make sure queries only fetch as many entities as you actually need. 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. 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 {
# kommer att ge upp till 100 tokens
id
transactions {
# kommer att ge upp till 100 transaktioner
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.

Use a single query to request multiple records

Länk till detta avsnitt

By default, subgraphs have a singular entity for one record. For multiple records, use the plural entities and filter: where: {id_in:[X,Y,Z]} or where: {volume_gt:100000}

Example of inefficient querying:

query SingleRecord {
entity(id: X) {
id
name
}
}
query SingleRecord {
entity(id: Y) {
id
name
}
}

Example of optimized querying:

query ManyRecords {
entities(where: { id_in: [X, Y] }) {
id
name
}
}

Combine multiple queries in a single request

Länk till detta avsnitt

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.

Dra nytta av GraphQL-fragment

Länk till detta avsnitt

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:

  • More extensive queries become harder to read.
  • When using tools that generate TypeScript types based on queries (more on that in the last section), newDelegate and oldDelegate will result in two distinct inline interfaces.

A refactored version of the query would be the following:

query {
bondEvents {
id
newDelegate {
...DelegateItem
}
oldDelegate {
...DelegateItem
}
}
}
# vi definierar ett fragment (subtyp) på Transcoder
# att faktorisera upprepade fält i frågan
fragment DelegateItem on Transcoder {
id
active
status
}

Using GraphQL fragment will improve readability (especially at scale) and 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).

Dos and Don'ts för GraphQL Fragment

Länk till detta avsnitt

Fragmentbas måste vara en typ

Länk till detta avsnitt

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.

Hur man sprider ett fragment

Länk till detta avsnitt

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

Exempel:

query {
bondEvents {
id
newDelegate {
...VoteItem # Fel! `VoteItem` kan inte spridas på `Transcoder` typ
}
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.

Definiera fragment som en atomisk affärsenhet för data

Länk till detta avsnitt

GraphQL Fragments 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 fragments:

  • When fields of the same type are repeated in a query, group them in a Fragment.
  • When similar but different fields are repeated, create multiple fragments, for instance:
# base fragment (mostly used in listing)
fragment Voter on Vote {
id
voter
}
# utökat fragment (vid förfrågan om en detaljerad vy av en omröstning)
fragment VoteWithPoll on Vote {
id
voter
choiceID
poll {
id
proposal
}
}

The Essential Tools

Länk till detta avsnitt

Webbaserade GraphQL-upptäckare

Länk till detta avsnitt

Iterating over queries by running them in your application can be cumbersome. For this reason, don't hesitate to use Graph Explorer to test your queries before adding them to your application. 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

Länk till detta avsnitt

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: används ett fält på en korrekt typ?
  • @graphql-eslint/no-unused variables: bör en given variabel förbli oanvänd?
  • och mer!

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:

  • Syntax highlighting
  • Autocomplete suggestions
  • Validation against schema
  • Snippets
  • Go to definition for fragments and input types

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:

  • Syntax highlighting
  • Autocomplete suggestions
  • Validation against schema
  • Snippets

For more information on this topic, check out the WebStorm article which showcases all the plugin's main features.

Redigera sida

Tidigare
Hantera dina API-nycklar
Nästa
Att göra förfrågningar från en Applikation
Redigera sida