From 5a4cfa2e696989d861f1356ee62123383d486df8 Mon Sep 17 00:00:00 2001 From: Charly POLY Date: Mon, 14 Feb 2022 17:19:05 +0000 Subject: [PATCH 1/6] doc(developer): complete beginner guide to querying a GraphQL API --- pages/en/developer/querying-from-your-app.mdx | 601 +++++++++++++++--- 1 file changed, 519 insertions(+), 82 deletions(-) diff --git a/pages/en/developer/querying-from-your-app.mdx b/pages/en/developer/querying-from-your-app.mdx index c09c44efee72..c575b4b809db 100644 --- a/pages/en/developer/querying-from-your-app.mdx +++ b/pages/en/developer/querying-from-your-app.mdx @@ -2,135 +2,572 @@ title: Querying from an Application --- -Once a subgraph is deployed to the Subgraph Studio or to the Graph Explorer, you will be given the endpoint for your GraphQL API that should look something like this: +The Graph provides a decentralized way to query data from blockchains. -**Subgraph Studio (testing endpoint)** +The Graph network's data is exposed through a GraphQL API, making it easier to query data with the GraphQL language. -```sh -Queries (HTTP) -https://api.studio.thegraph.com/query/// +This page will guide you through the essential GraphQL language rules and GraphQL queries best practices. + +--- + +## Querying a GraphQL API + +### The anatomy of a GraphQL query + +Unlike REST API, a GraphQL API is built upon a Schema that defines which queries can be performed. + +For example, a query to get a token using the `token` query will look as follows: + +```graphql +query GetToken($id: ID!) { + token(id: $id) { + id + owner + } +} ``` -**Graph Explorer** +which will return the following predictable JSON response (*when passing the proper `$id` variable value*): -```sh -Queries (HTTP) -https://gateway.thegraph.com/api//subgraphs/id/ +```json +{ + "token": { + "id": "...", + "owner": "..." + }, +} ``` -Using the GraphQL endpoint, you can use various GraphQL Client libraries to query the subgraph and populate your app with the data indexed by the subgraph. +GraphQL queries use the GraphQL language, which is defined upon [a specification](https://spec.graphql.org/). -Here are a couple of the more popular GraphQL clients in the ecosystem and how to use them: +The above `GetToken` query is composed of multiple language parts (replaced below with `[...]` placeholders): -### Apollo client +```graphql +query [operationName]([variableName]: [variableType]) { + [queryName]([argumentName]: [variableName]) { + # "{ ... }" express a Selection-Set, we are querying fields from `queryName`. + [field] + [field] + } +} +``` -[Apollo client](https://www.apollographql.com/docs/) supports web projects including frameworks like React and Vue, as well as mobile clients like iOS, Android, and React Native. +While the list of syntactic do's and don'ts is long, here are the essential rules to keep in mind when it comes to writing GraphQL queries: -Let's look at how fetch data from a subgraph with Apollo client in a web project. +- Each `queryName` must only be used once per operation. +- Each `field` must be used only once in a selection (we cannot query `id` twice under `token`) +- Some `field`s 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 [The Graph Explorer](https://thegraph.com/docs/en/explorer/). +- Any variable assigned to an argument must match its type. +- In a given list of variables, each of them must be unique. +- All defined variables must be used. -First, install `@apollo/client` and `graphql`: +Failing to follow the above rules will end with an error from the Graph API. -```sh -npm install @apollo/client graphql -``` +For a complete list of rules with code examples, please look at our GraphQL Validations guide. -Then you can query the API with the following code: +
-```javascript -import { ApolloClient, InMemoryCache, gql } from '@apollo/client' +### Sending a query to a GraphQL API -const APIURL = 'https://api.studio.thegraph.com/query///' +GraphQL is a language and set of conventions that transport over HTTP. -const tokensQuery = ` - query { - tokens { - id - tokenID - contentURI - metadataURI - } +It means that you can query a GraphQL API using standard `fetch` (natively or via `cross-undici-fetch` or `isomorphic-fetch`) as follows: + +```tsx +const query = ` +query GetToken($id: ID!) { + token(id: $id) { + id + owner } +} ` +const variables = { id: "1" } -const client = new ApolloClient({ - uri: APIURL, - cache: new InMemoryCache(), +const fetchResult = await fetch('http://example.com/graphql', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ query, variables }) }) +const result = await fetchResult.json() +``` + +Another lightweight alternative, best for back-end use-cases, is the famous [`graphql-request` library](https://github.com/prisma-labs/graphql-request). + +This lightweight GraphQL client comes with the essential features to query a GraphQL API: + +- Mutations validation +- Support for file upload +- Batching +- Promise-based API +- TypeScript support + +
+ +A complete GraphQL client is [URQL](https://formidable.com/open-source/urql/) which is available within Node.js, React/Preact, Vue, Svelte environments, with more advanced features: + +- Flexible cache system +- Extensible design (easing adding new capabilities on top of it) +- Lightweight bundle (~5x lighter than Apollo Client) +- Support for file uploads and offline mode -client - .query({ - query: gql(tokensQuery), - }) - .then((data) => console.log('Subgraph data: ', data)) - .catch((err) => { - console.log('Error fetching data: ', err) - }) +
+ +In the React ecosystem, [React Query](https://react-query.tanstack.com/graphql) brings a lightweight and agnostic solution for GraphQL. + +React Query is a great candidate if you are looking for an easy-to-use and lightweight multipurpose client (GraphQL-capable) that provides essential features such as: + +- Powerful cache (background refresh, window-focus refreshing) +- Advanced querying pattern (parallel queries, dependent queries, prefetching) +- UX patterns: optimistic updates, scroll restoration +- Great dev tools + +
+ +Finally, Apollo Client is the ubiquitous GraphQL client on the front-end ecosystem. + +Available for React, Angular, Vue, Ember, iOS, and Android, Apollo Client, although the heaviest clients, brings many features to build advanced UI on-top of GraphQL: + +- advanced error handling +- pagination +- data prefetching +- optimistic UI +- local state management + +
+ +Now that we covered the basic rules of GraphQL queries syntax, let's now look at the best practices of GraphQL query writing. + +--- + +## Writing GraphQL queries + +### Always write static queries + +A common (bad) practice is to dynamically build query strings as follows: + +```tsx +const id = params.id +const fields = ["id", "owner"] +const query = ` +query GetToken { + token(id: ${id}) { + ${fields.join("\n")} + } +} +` + +// Execute query... ``` -To use variables, you can pass in a `variables` argument to the query: +While the above snippet produces a valid GraphQL query, **it has many drawbacks**: -```javascript -const tokensQuery = ` - query($first: Int, $orderBy: BigInt, $orderDirection: String) { - tokens( - first: $first, orderBy: $orderBy, orderDirection: $orderDirection - ) { +- it makes it **harder to understand** the query as a whole +- developers are **responsible for safely sanitizing the string interpolation** +- not sending the values of the variables as part of the request parameters **prevent possible caching on server-side** +- it **prevents tools from statically analyzing the query** (ex: Linter, or type generations tools) + +For this reason, it is recommended to always write queries as static strings: + +```tsx +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 - tokenID - contentURI - metadataURI } } +) +``` + +Doing so brings **many advantages**: + +- **Easy to read and maintain** queries +- The GraphQL **server handles variables sanitization** +- **Variables can be cached** at server-level +- **Queries can be statically analyzed by tools** (more on this in the following sections) + +**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: + +```tsx +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) + } +} ` -client - .query({ - query: gql(tokensQuery), +const result = await execute( + query, + { variables: { - first: 10, - orderBy: 'createdAtTimestamp', - orderDirection: 'desc', - }, - }) - .then((data) => console.log('Subgraph data: ', data)) - .catch((err) => { - console.log('Error fetching data: ', err) - }) + id, + includeOwner: true + } + } +) ``` -### URQL +Note: The opposite directive is `@skip(if: ...)`. + +
-Another option is [URQL](https://formidable.com/open-source/urql/), a somewhat lighter weight GraphQL client library. +### Performance tips -Let's look at how fetch data from a subgraph with URQL in a web project. +**"Ask for what you want"** -First, install `urql` and `graphql`: +GraphQL became famous for its “Ask for what you want” tagline. -```sh -npm install urql graphql +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. + +Since some fields might be computed or proxied on the server-side, fetching fewer fields can make a big difference! + +```graphql +query getProtocols { + protocols(first: 5) { + id + inflation + inflationChange + maxEarningsClaimsRounds + numActiveTranscoders + } +} ``` -Then you can query the API with the following code: +`numActiveTranscoders` might be slower (computed) field to resolve. + +If not needed, removing it will improve the overall query performance (*especially in a list context:* `list: 5`). -```javascript -import { createClient } from 'urql' +**Combining multiple queries** -const APIURL = 'https://api.thegraph.com/subgraphs/name/username/subgraphname' +Your application might require querying multiple types of data as follows: + +```graphql +import { execute } from "your-favorite-graphql-client" const tokensQuery = ` - query { - tokens { +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: + +```graphql +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**. + +
+ +### Leverage GraphQL Fragments + +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 (`{ ... }`): + +```graphql +query { + bondEvents { + id + newDelegate { + id + active + status + } + oldDelegate { id - tokenID - contentURI - metadataURI + active + status + } + } +} +``` + +Such repeated fields (`id`, `active`, `status`) bring many issues: + +- harder to read for more extensive queries +- 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: + +```graphql +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*). + +
+ +### GraphQL Fragment do's and don'ts + +**Fragment base must be a type** + +A Fragment cannot be based on a non-applicable type, in short, **on type not having fields**: + +```graphql +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. + +Example: + +```graphql +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: + +- when fields of the same type are repeated in a query, group them in a Fragment +- when similar but not the same fields are repeated, create multiple fragments, ex: + +```graphql +# 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 + } +} +``` + +--- + +## The essential tools + +### GraphQL Linting + +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](https://github.com/dotansimha/graphql-eslint) will help you stay on top of GraphQL best practices with zero effort. + +[Setup the "operations-recommended"](https://github.com/dotansimha/graphql-eslint#available-configs) config will enforce essential rules such as: + +- `@graphql-eslint/fields-on-correct-type`: is a field used on a proper type? +- `@graphql-eslint/no-unused variables`: should a given variable stay unused? +- and more! + +This will allow you to **catch errors without even testing queries** on the playground or running them in production! + +
+ +### IDE plugins + +**VSCode and GraphQL** + +The [GraphQL VSCode extension](https://marketplace.visualstudio.com/items?itemName=GraphQL.vscode-graphql) 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](https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint) is a must-have to visualize errors and warnings inlined in your code correctly. + + + +**WebStorm/Intellij and GraphQL** + +The [JS GraphQL plugin](https://plugins.jetbrains.com/plugin/8097-graphql/) will significantly improve your experience while working with GraphQL by providing: + +- syntax highlighting +- autocomplete suggestions +- validation against schema +- snippets + +More information on this [WebStorm article](https://blog.jetbrains.com/webstorm/2019/04/featured-plugin-js-graphql/) that showcases all the plugin's main features. + +
+ +### TypeScript types generation + +Finally, the best GraphQL experience is reached when the TypeScript data types are generated from the defined GraphQL queries, as follows: + +```tsx +import { execute } from "your-favorite-graphql-client" +import { GetTokenQuery } from "./generated." + +const id = params.id +const query = ` +query GetToken($id: ID!) { + token(id: $id) { + id + owner + } +} ` -const client = createClient({ - url: APIURL, -}) +const result: GetTokenQuery = await execute( + query, + { + variables: { + id + } + } +) -const data = await client.query(tokensQuery).toPromise() +// `result` is typed! ``` + +Such a setup can be easily achieved by installing and configuring [GraphQL Code Generator](https://www.graphql-code-generator.com/docs/getting-started) as follows: + +```bash +yarn add graphql @graphql-codegen/cli @graphql-codegen/typescript @graphql-codegen/typescript-operations +``` + +Then update your `package.json` (or similar script configuration setup) as follows: + +```tsx +{ + // ... + "scripts": { + // ... + "generate": "graphql-codegen", + // ... + } + // ... +} +``` + +Add the following configuration file to your project: + +```tsx +schema: "" +documents: './src/**/*.ts' +generates: + ./generated.ts: + plugins: + - typescript + - typescript-operations +``` + +Finally, by simply running the configured script (`yarn generate`), GraphQL Code Generator will generate the proper TypeScript types in the `generated.ts` file: + +- Each query will get a corresponding `[QueryName]Query` type (ex: `GetToken` → `GetTokenQuery` type +- Each fragment will get a corresponding `[FragmentName]Fragment` type (ex: `DelegateItem` → `DelegateItemFragment` type From 15f050620e225b31feb3808fccb62c9ceaad2194 Mon Sep 17 00:00:00 2001 From: Charly POLY Date: Tue, 15 Feb 2022 18:13:45 +0000 Subject: [PATCH 2/6] feat(developer): force english for "Querying from an Application" to force retranslation --- pages/ar/developer/querying-from-your-app.mdx | 603 +++++++++++++++--- pages/es/developer/querying-from-your-app.mdx | 603 +++++++++++++++--- pages/ja/developer/querying-from-your-app.mdx | 603 +++++++++++++++--- pages/ko/developer/querying-from-your-app.mdx | 603 +++++++++++++++--- pages/vi/developer/querying-from-your-app.mdx | 603 +++++++++++++++--- pages/zh/developer/querying-from-your-app.mdx | 603 +++++++++++++++--- 6 files changed, 3120 insertions(+), 498 deletions(-) diff --git a/pages/ar/developer/querying-from-your-app.mdx b/pages/ar/developer/querying-from-your-app.mdx index 5082bfae455c..c575b4b809db 100644 --- a/pages/ar/developer/querying-from-your-app.mdx +++ b/pages/ar/developer/querying-from-your-app.mdx @@ -1,136 +1,573 @@ --- -title: الاستعلام من التطبيق +title: Querying from an Application --- -بمجرد نشر ال Subgraph في Subgraph Studio أو في Graph Explorer ، سيتم إعطاؤك endpoint ل GraphQL API الخاصة بك والتي يجب أن تبدو كما يلي: +The Graph provides a decentralized way to query data from blockchains. -**Subgraph Studio (اختبار endpoint)** +The Graph network's data is exposed through a GraphQL API, making it easier to query data with the GraphQL language. -```sh -استعلامات (HTTP) -https://api.studio.thegraph.com/query/// +This page will guide you through the essential GraphQL language rules and GraphQL queries best practices. + +--- + +## Querying a GraphQL API + +### The anatomy of a GraphQL query + +Unlike REST API, a GraphQL API is built upon a Schema that defines which queries can be performed. + +For example, a query to get a token using the `token` query will look as follows: + +```graphql +query GetToken($id: ID!) { + token(id: $id) { + id + owner + } +} ``` -**Graph Explorer** +which will return the following predictable JSON response (*when passing the proper `$id` variable value*): -```sh -استعلامات (HTTP) -https://gateway.thegraph.com/api//subgraphs/id/ +```json +{ + "token": { + "id": "...", + "owner": "..." + }, +} ``` -باستخدام GraphQL endpoint ، يمكنك استخدام العديد من مكتبات GraphQL Client للاستعلام عن ال Subgraph وملء تطبيقك بالبيانات المفهرسة بواسطة ال Subgraph. +GraphQL queries use the GraphQL language, which is defined upon [a specification](https://spec.graphql.org/). -في ما يلي بعض عملاء GraphQL الأكثر شيوعا في النظام البيئي وكيفية استخدامها: +The above `GetToken` query is composed of multiple language parts (replaced below with `[...]` placeholders): -### عميل Apollo +```graphql +query [operationName]([variableName]: [variableType]) { + [queryName]([argumentName]: [variableName]) { + # "{ ... }" express a Selection-Set, we are querying fields from `queryName`. + [field] + [field] + } +} +``` -[Apoolo client](https://www.apollographql.com/docs/) يدعم مشاريع الويب بما في ذلك ال framework مثل React و Vue ، بالإضافة إلى mobile clients مثل iOS و Android و React Native. +While the list of syntactic do's and don'ts is long, here are the essential rules to keep in mind when it comes to writing GraphQL queries: -لنلقِ نظرة على كيفية جلب البيانات من Subgraph وذلك باستخدام Apollo client في مشروع ويب. +- Each `queryName` must only be used once per operation. +- Each `field` must be used only once in a selection (we cannot query `id` twice under `token`) +- Some `field`s 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 [The Graph Explorer](https://thegraph.com/docs/en/explorer/). +- Any variable assigned to an argument must match its type. +- In a given list of variables, each of them must be unique. +- All defined variables must be used. -اولا قم بتثبيت `apollo/client@` و `graphql`: +Failing to follow the above rules will end with an error from the Graph API. -```sh -npm install @apollo/client graphql -``` +For a complete list of rules with code examples, please look at our GraphQL Validations guide. -بعد ذلك يمكنك الاستعلام عن API بالكود التالي: +
-```javascript -import { ApolloClient, InMemoryCache, gql } from '@apollo/client' +### Sending a query to a GraphQL API -const APIURL = 'https://api.studio.thegraph.com/query///' +GraphQL is a language and set of conventions that transport over HTTP. -const tokensQuery = ` - query { - tokens { - id - tokenID - contentURI - metadataURI - } +It means that you can query a GraphQL API using standard `fetch` (natively or via `cross-undici-fetch` or `isomorphic-fetch`) as follows: + +```tsx +const query = ` +query GetToken($id: ID!) { + token(id: $id) { + id + owner } +} ` +const variables = { id: "1" } -const client = new ApolloClient({ - uri: APIURL, - cache: new InMemoryCache(), +const fetchResult = await fetch('http://example.com/graphql', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ query, variables }) }) +const result = await fetchResult.json() +``` + +Another lightweight alternative, best for back-end use-cases, is the famous [`graphql-request` library](https://github.com/prisma-labs/graphql-request). + +This lightweight GraphQL client comes with the essential features to query a GraphQL API: + +- Mutations validation +- Support for file upload +- Batching +- Promise-based API +- TypeScript support + +
+ +A complete GraphQL client is [URQL](https://formidable.com/open-source/urql/) which is available within Node.js, React/Preact, Vue, Svelte environments, with more advanced features: + +- Flexible cache system +- Extensible design (easing adding new capabilities on top of it) +- Lightweight bundle (~5x lighter than Apollo Client) +- Support for file uploads and offline mode -client - .query({ - query: gql(tokensQuery), - }) - .then((data) => console.log('Subgraph data: ', data)) - .catch((err) => { - console.log('Error fetching data: ', err) - }) +
+ +In the React ecosystem, [React Query](https://react-query.tanstack.com/graphql) brings a lightweight and agnostic solution for GraphQL. + +React Query is a great candidate if you are looking for an easy-to-use and lightweight multipurpose client (GraphQL-capable) that provides essential features such as: + +- Powerful cache (background refresh, window-focus refreshing) +- Advanced querying pattern (parallel queries, dependent queries, prefetching) +- UX patterns: optimistic updates, scroll restoration +- Great dev tools + +
+ +Finally, Apollo Client is the ubiquitous GraphQL client on the front-end ecosystem. + +Available for React, Angular, Vue, Ember, iOS, and Android, Apollo Client, although the heaviest clients, brings many features to build advanced UI on-top of GraphQL: + +- advanced error handling +- pagination +- data prefetching +- optimistic UI +- local state management + +
+ +Now that we covered the basic rules of GraphQL queries syntax, let's now look at the best practices of GraphQL query writing. + +--- + +## Writing GraphQL queries + +### Always write static queries + +A common (bad) practice is to dynamically build query strings as follows: + +```tsx +const id = params.id +const fields = ["id", "owner"] +const query = ` +query GetToken { + token(id: ${id}) { + ${fields.join("\n")} + } +} +` + +// Execute query... ``` -لاستخدام المتغيرات، يمكنك تمرير وسيطة (argument) الـ `variables` للاستعلام: +While the above snippet produces a valid GraphQL query, **it has many drawbacks**: -```javascript -const tokensQuery = ` - query($first: Int, $orderBy: BigInt, $orderDirection: String) { - tokens( - first: $first, orderBy: $orderBy, orderDirection: $orderDirection - ) { +- it makes it **harder to understand** the query as a whole +- developers are **responsible for safely sanitizing the string interpolation** +- not sending the values of the variables as part of the request parameters **prevent possible caching on server-side** +- it **prevents tools from statically analyzing the query** (ex: Linter, or type generations tools) + +For this reason, it is recommended to always write queries as static strings: + +```tsx +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 - tokenID - contentURI - metadataURI } } +) +``` + +Doing so brings **many advantages**: + +- **Easy to read and maintain** queries +- The GraphQL **server handles variables sanitization** +- **Variables can be cached** at server-level +- **Queries can be statically analyzed by tools** (more on this in the following sections) + +**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: + +```tsx +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) + } +} ` -client - .query({ - query: gql(tokensQuery), +const result = await execute( + query, + { variables: { - first: 10, - orderBy: 'createdAtTimestamp', - orderDirection: 'desc', - }, - }) - .then((data) => console.log('Subgraph data: ', data)) - .catch((err) => { - console.log('Error fetching data: ', err) - }) + id, + includeOwner: true + } + } +) ``` -### URQL +Note: The opposite directive is `@skip(if: ...)`. + +
-هناك خيار آخر وهو [URQL](https://formidable.com/open-source/urql/) ، وهي مكتبة GraphQL client أخف وزنا إلى حد ما. +### Performance tips -لنلقِ نظرة على كيفية جلب البيانات من Subgraph باستخدام URQL في مشروع ويب. +**"Ask for what you want"** -اولا قم بتثبيت `urql` و `graphql`: +GraphQL became famous for its “Ask for what you want” tagline. -```sh -npm install urql graphql +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. + +Since some fields might be computed or proxied on the server-side, fetching fewer fields can make a big difference! + +```graphql +query getProtocols { + protocols(first: 5) { + id + inflation + inflationChange + maxEarningsClaimsRounds + numActiveTranscoders + } +} ``` -بعد ذلك يمكنك الاستعلام عن API بالكود التالي: +`numActiveTranscoders` might be slower (computed) field to resolve. + +If not needed, removing it will improve the overall query performance (*especially in a list context:* `list: 5`). -```javascript -import { createClient } from 'urql' +**Combining multiple queries** -const APIURL = 'https://api.thegraph.com/subgraphs/name/username/subgraphname' +Your application might require querying multiple types of data as follows: + +```graphql +import { execute } from "your-favorite-graphql-client" const tokensQuery = ` - query { - tokens { +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: + +```graphql +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**. + +
+ +### Leverage GraphQL Fragments + +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 (`{ ... }`): + +```graphql +query { + bondEvents { + id + newDelegate { + id + active + status + } + oldDelegate { id - tokenID - contentURI - metadataURI + active + status + } + } +} +``` + +Such repeated fields (`id`, `active`, `status`) bring many issues: + +- harder to read for more extensive queries +- 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: + +```graphql +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*). + +
+ +### GraphQL Fragment do's and don'ts + +**Fragment base must be a type** + +A Fragment cannot be based on a non-applicable type, in short, **on type not having fields**: + +```graphql +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. + +Example: + +```graphql +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: + +- when fields of the same type are repeated in a query, group them in a Fragment +- when similar but not the same fields are repeated, create multiple fragments, ex: + +```graphql +# 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 + } +} +``` + +--- + +## The essential tools + +### GraphQL Linting + +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](https://github.com/dotansimha/graphql-eslint) will help you stay on top of GraphQL best practices with zero effort. + +[Setup the "operations-recommended"](https://github.com/dotansimha/graphql-eslint#available-configs) config will enforce essential rules such as: + +- `@graphql-eslint/fields-on-correct-type`: is a field used on a proper type? +- `@graphql-eslint/no-unused variables`: should a given variable stay unused? +- and more! + +This will allow you to **catch errors without even testing queries** on the playground or running them in production! + +
+ +### IDE plugins + +**VSCode and GraphQL** + +The [GraphQL VSCode extension](https://marketplace.visualstudio.com/items?itemName=GraphQL.vscode-graphql) 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](https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint) is a must-have to visualize errors and warnings inlined in your code correctly. + + + +**WebStorm/Intellij and GraphQL** + +The [JS GraphQL plugin](https://plugins.jetbrains.com/plugin/8097-graphql/) will significantly improve your experience while working with GraphQL by providing: + +- syntax highlighting +- autocomplete suggestions +- validation against schema +- snippets + +More information on this [WebStorm article](https://blog.jetbrains.com/webstorm/2019/04/featured-plugin-js-graphql/) that showcases all the plugin's main features. + +
+ +### TypeScript types generation + +Finally, the best GraphQL experience is reached when the TypeScript data types are generated from the defined GraphQL queries, as follows: + +```tsx +import { execute } from "your-favorite-graphql-client" +import { GetTokenQuery } from "./generated." + +const id = params.id +const query = ` +query GetToken($id: ID!) { + token(id: $id) { + id + owner + } +} ` -const client = createClient({ - url: APIURL, -}) +const result: GetTokenQuery = await execute( + query, + { + variables: { + id + } + } +) -const data = await client.query(tokensQuery).toPromise() +// `result` is typed! ``` + +Such a setup can be easily achieved by installing and configuring [GraphQL Code Generator](https://www.graphql-code-generator.com/docs/getting-started) as follows: + +```bash +yarn add graphql @graphql-codegen/cli @graphql-codegen/typescript @graphql-codegen/typescript-operations +``` + +Then update your `package.json` (or similar script configuration setup) as follows: + +```tsx +{ + // ... + "scripts": { + // ... + "generate": "graphql-codegen", + // ... + } + // ... +} +``` + +Add the following configuration file to your project: + +```tsx +schema: "" +documents: './src/**/*.ts' +generates: + ./generated.ts: + plugins: + - typescript + - typescript-operations +``` + +Finally, by simply running the configured script (`yarn generate`), GraphQL Code Generator will generate the proper TypeScript types in the `generated.ts` file: + +- Each query will get a corresponding `[QueryName]Query` type (ex: `GetToken` → `GetTokenQuery` type +- Each fragment will get a corresponding `[FragmentName]Fragment` type (ex: `DelegateItem` → `DelegateItemFragment` type diff --git a/pages/es/developer/querying-from-your-app.mdx b/pages/es/developer/querying-from-your-app.mdx index fb8c7895afaa..c575b4b809db 100644 --- a/pages/es/developer/querying-from-your-app.mdx +++ b/pages/es/developer/querying-from-your-app.mdx @@ -1,136 +1,573 @@ --- -title: Consultar desde una Aplicacion +title: Querying from an Application --- -Una vez que un subgrafo es desplegado en Subgraph Studio o en The Graph Explorer, se te dará el endpoint para tu API GraphQL que debería ser algo así: +The Graph provides a decentralized way to query data from blockchains. -**Subgraph Studio (endpoint de prueba)** +The Graph network's data is exposed through a GraphQL API, making it easier to query data with the GraphQL language. -```sh -Queries (HTTP) -https://api.studio.thegraph.com/query/// +This page will guide you through the essential GraphQL language rules and GraphQL queries best practices. + +--- + +## Querying a GraphQL API + +### The anatomy of a GraphQL query + +Unlike REST API, a GraphQL API is built upon a Schema that defines which queries can be performed. + +For example, a query to get a token using the `token` query will look as follows: + +```graphql +query GetToken($id: ID!) { + token(id: $id) { + id + owner + } +} ``` -**Graph Explorer** +which will return the following predictable JSON response (*when passing the proper `$id` variable value*): -```sh -Queries (HTTP) -https://gateway.thegraph.com/api//subgraphs/id/ +```json +{ + "token": { + "id": "...", + "owner": "..." + }, +} ``` -Usando el endpoint de GraphQL, puedes usar varias librerías de Clientes de GraphQL para consultar el subgrafo y rellenar tu aplicación con los datos indexados por el subgrafo. +GraphQL queries use the GraphQL language, which is defined upon [a specification](https://spec.graphql.org/). -A continuación se presentan un par de clientes GraphQL más populares en el ecosistema y cómo utilizarlos: +The above `GetToken` query is composed of multiple language parts (replaced below with `[...]` placeholders): -### Cliente Apollo +```graphql +query [operationName]([variableName]: [variableType]) { + [queryName]([argumentName]: [variableName]) { + # "{ ... }" express a Selection-Set, we are querying fields from `queryName`. + [field] + [field] + } +} +``` -[Apollo client](https://www.apollographql.com/docs/) admite proyectos web que incluyen frameworks como React y Vue, así como clientes móviles como iOS, Android y React Native. +While the list of syntactic do's and don'ts is long, here are the essential rules to keep in mind when it comes to writing GraphQL queries: -Veamos cómo obtener datos de un subgrafo con el cliente Apollo en un proyecto web. +- Each `queryName` must only be used once per operation. +- Each `field` must be used only once in a selection (we cannot query `id` twice under `token`) +- Some `field`s 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 [The Graph Explorer](https://thegraph.com/docs/en/explorer/). +- Any variable assigned to an argument must match its type. +- In a given list of variables, each of them must be unique. +- All defined variables must be used. -Primero, instala `@apollo/client` y `graphql`: +Failing to follow the above rules will end with an error from the Graph API. -```sh -npm install @apollo/client graphql -``` +For a complete list of rules with code examples, please look at our GraphQL Validations guide. -A continuación, puedes consultar la API con el siguiente código: +
-```javascript -import { ApolloClient, InMemoryCache, gql } from '@apollo/client' +### Sending a query to a GraphQL API -const APIURL = 'https://api.studio.thegraph.com/query///' +GraphQL is a language and set of conventions that transport over HTTP. -const tokensQuery = ` - query { - tokens { - id - tokenID - contentURI - metadataURI - } +It means that you can query a GraphQL API using standard `fetch` (natively or via `cross-undici-fetch` or `isomorphic-fetch`) as follows: + +```tsx +const query = ` +query GetToken($id: ID!) { + token(id: $id) { + id + owner } +} ` +const variables = { id: "1" } -const client = new ApolloClient({ - uri: APIURL, - cache: new InMemoryCache(), +const fetchResult = await fetch('http://example.com/graphql', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ query, variables }) }) +const result = await fetchResult.json() +``` + +Another lightweight alternative, best for back-end use-cases, is the famous [`graphql-request` library](https://github.com/prisma-labs/graphql-request). + +This lightweight GraphQL client comes with the essential features to query a GraphQL API: + +- Mutations validation +- Support for file upload +- Batching +- Promise-based API +- TypeScript support + +
+ +A complete GraphQL client is [URQL](https://formidable.com/open-source/urql/) which is available within Node.js, React/Preact, Vue, Svelte environments, with more advanced features: + +- Flexible cache system +- Extensible design (easing adding new capabilities on top of it) +- Lightweight bundle (~5x lighter than Apollo Client) +- Support for file uploads and offline mode -client - .query({ - query: gql(tokensQuery), - }) - .then((data) => console.log('Subgraph data: ', data)) - .catch((err) => { - console.log('Error fetching data: ', err) - }) +
+ +In the React ecosystem, [React Query](https://react-query.tanstack.com/graphql) brings a lightweight and agnostic solution for GraphQL. + +React Query is a great candidate if you are looking for an easy-to-use and lightweight multipurpose client (GraphQL-capable) that provides essential features such as: + +- Powerful cache (background refresh, window-focus refreshing) +- Advanced querying pattern (parallel queries, dependent queries, prefetching) +- UX patterns: optimistic updates, scroll restoration +- Great dev tools + +
+ +Finally, Apollo Client is the ubiquitous GraphQL client on the front-end ecosystem. + +Available for React, Angular, Vue, Ember, iOS, and Android, Apollo Client, although the heaviest clients, brings many features to build advanced UI on-top of GraphQL: + +- advanced error handling +- pagination +- data prefetching +- optimistic UI +- local state management + +
+ +Now that we covered the basic rules of GraphQL queries syntax, let's now look at the best practices of GraphQL query writing. + +--- + +## Writing GraphQL queries + +### Always write static queries + +A common (bad) practice is to dynamically build query strings as follows: + +```tsx +const id = params.id +const fields = ["id", "owner"] +const query = ` +query GetToken { + token(id: ${id}) { + ${fields.join("\n")} + } +} +` + +// Execute query... ``` -Para utilizar variables, puedes pasar un argumento `variables` a la consulta: +While the above snippet produces a valid GraphQL query, **it has many drawbacks**: -```javascript -const tokensQuery = ` - query($first: Int, $orderBy: BigInt, $orderDirection: String) { - tokens( - first: $first, orderBy: $orderBy, orderDirection: $orderDirection - ) { +- it makes it **harder to understand** the query as a whole +- developers are **responsible for safely sanitizing the string interpolation** +- not sending the values of the variables as part of the request parameters **prevent possible caching on server-side** +- it **prevents tools from statically analyzing the query** (ex: Linter, or type generations tools) + +For this reason, it is recommended to always write queries as static strings: + +```tsx +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 - tokenID - contentURI - metadataURI } } +) +``` + +Doing so brings **many advantages**: + +- **Easy to read and maintain** queries +- The GraphQL **server handles variables sanitization** +- **Variables can be cached** at server-level +- **Queries can be statically analyzed by tools** (more on this in the following sections) + +**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: + +```tsx +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) + } +} ` -client - .query({ - query: gql(tokensQuery), +const result = await execute( + query, + { variables: { - first: 10, - orderBy: 'createdAtTimestamp', - orderDirection: 'desc', - }, - }) - .then((data) => console.log('Subgraph data: ', data)) - .catch((err) => { - console.log('Error fetching data: ', err) - }) + id, + includeOwner: true + } + } +) ``` -### URQL +Note: The opposite directive is `@skip(if: ...)`. + +
-Otra opción es [URQL](https://formidable.com/open-source/urql/), una libreria cliente de GraphQL algo más ligera. +### Performance tips -Veamos cómo obtener datos de un subgrafo con URQL en un proyecto web. +**"Ask for what you want"** -Primero, instala `urql` and `graphql`: +GraphQL became famous for its “Ask for what you want” tagline. -```sh -npm install urql graphql +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. + +Since some fields might be computed or proxied on the server-side, fetching fewer fields can make a big difference! + +```graphql +query getProtocols { + protocols(first: 5) { + id + inflation + inflationChange + maxEarningsClaimsRounds + numActiveTranscoders + } +} ``` -A continuación, puedes consultar la API con el siguiente código: +`numActiveTranscoders` might be slower (computed) field to resolve. + +If not needed, removing it will improve the overall query performance (*especially in a list context:* `list: 5`). -```javascript -import { createClient } from 'urql' +**Combining multiple queries** -const APIURL = 'https://api.thegraph.com/subgraphs/name/username/subgraphname' +Your application might require querying multiple types of data as follows: + +```graphql +import { execute } from "your-favorite-graphql-client" const tokensQuery = ` - query { - tokens { +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: + +```graphql +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**. + +
+ +### Leverage GraphQL Fragments + +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 (`{ ... }`): + +```graphql +query { + bondEvents { + id + newDelegate { + id + active + status + } + oldDelegate { id - tokenID - contentURI - metadataURI + active + status + } + } +} +``` + +Such repeated fields (`id`, `active`, `status`) bring many issues: + +- harder to read for more extensive queries +- 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: + +```graphql +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*). + +
+ +### GraphQL Fragment do's and don'ts + +**Fragment base must be a type** + +A Fragment cannot be based on a non-applicable type, in short, **on type not having fields**: + +```graphql +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. + +Example: + +```graphql +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: + +- when fields of the same type are repeated in a query, group them in a Fragment +- when similar but not the same fields are repeated, create multiple fragments, ex: + +```graphql +# 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 + } +} +``` + +--- + +## The essential tools + +### GraphQL Linting + +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](https://github.com/dotansimha/graphql-eslint) will help you stay on top of GraphQL best practices with zero effort. + +[Setup the "operations-recommended"](https://github.com/dotansimha/graphql-eslint#available-configs) config will enforce essential rules such as: + +- `@graphql-eslint/fields-on-correct-type`: is a field used on a proper type? +- `@graphql-eslint/no-unused variables`: should a given variable stay unused? +- and more! + +This will allow you to **catch errors without even testing queries** on the playground or running them in production! + +
+ +### IDE plugins + +**VSCode and GraphQL** + +The [GraphQL VSCode extension](https://marketplace.visualstudio.com/items?itemName=GraphQL.vscode-graphql) 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](https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint) is a must-have to visualize errors and warnings inlined in your code correctly. + + + +**WebStorm/Intellij and GraphQL** + +The [JS GraphQL plugin](https://plugins.jetbrains.com/plugin/8097-graphql/) will significantly improve your experience while working with GraphQL by providing: + +- syntax highlighting +- autocomplete suggestions +- validation against schema +- snippets + +More information on this [WebStorm article](https://blog.jetbrains.com/webstorm/2019/04/featured-plugin-js-graphql/) that showcases all the plugin's main features. + +
+ +### TypeScript types generation + +Finally, the best GraphQL experience is reached when the TypeScript data types are generated from the defined GraphQL queries, as follows: + +```tsx +import { execute } from "your-favorite-graphql-client" +import { GetTokenQuery } from "./generated." + +const id = params.id +const query = ` +query GetToken($id: ID!) { + token(id: $id) { + id + owner + } +} ` -const client = createClient({ - url: APIURL, -}) +const result: GetTokenQuery = await execute( + query, + { + variables: { + id + } + } +) -const data = await client.query(tokensQuery).toPromise() +// `result` is typed! ``` + +Such a setup can be easily achieved by installing and configuring [GraphQL Code Generator](https://www.graphql-code-generator.com/docs/getting-started) as follows: + +```bash +yarn add graphql @graphql-codegen/cli @graphql-codegen/typescript @graphql-codegen/typescript-operations +``` + +Then update your `package.json` (or similar script configuration setup) as follows: + +```tsx +{ + // ... + "scripts": { + // ... + "generate": "graphql-codegen", + // ... + } + // ... +} +``` + +Add the following configuration file to your project: + +```tsx +schema: "" +documents: './src/**/*.ts' +generates: + ./generated.ts: + plugins: + - typescript + - typescript-operations +``` + +Finally, by simply running the configured script (`yarn generate`), GraphQL Code Generator will generate the proper TypeScript types in the `generated.ts` file: + +- Each query will get a corresponding `[QueryName]Query` type (ex: `GetToken` → `GetTokenQuery` type +- Each fragment will get a corresponding `[FragmentName]Fragment` type (ex: `DelegateItem` → `DelegateItemFragment` type diff --git a/pages/ja/developer/querying-from-your-app.mdx b/pages/ja/developer/querying-from-your-app.mdx index 507ba4d02ad6..c575b4b809db 100644 --- a/pages/ja/developer/querying-from-your-app.mdx +++ b/pages/ja/developer/querying-from-your-app.mdx @@ -1,136 +1,573 @@ --- -title: アプリケーションからのクエリ +title: Querying from an Application --- -サブグラフが Subgraph Studio または Graph Explorer にデプロイされると、GraphQL API のエンドポイントが与えられ、以下のような形になります。 +The Graph provides a decentralized way to query data from blockchains. -**Subgraph Studio (テスト用エンドポイント)** +The Graph network's data is exposed through a GraphQL API, making it easier to query data with the GraphQL language. -```sh -Queries (HTTP) -https://api.studio.thegraph.com/query/// +This page will guide you through the essential GraphQL language rules and GraphQL queries best practices. + +--- + +## Querying a GraphQL API + +### The anatomy of a GraphQL query + +Unlike REST API, a GraphQL API is built upon a Schema that defines which queries can be performed. + +For example, a query to get a token using the `token` query will look as follows: + +```graphql +query GetToken($id: ID!) { + token(id: $id) { + id + owner + } +} ``` -**グラフエクスプローラ** +which will return the following predictable JSON response (*when passing the proper `$id` variable value*): -```sh -Queries (HTTP) -https://gateway.thegraph.com/api//subgraphs/id/ +```json +{ + "token": { + "id": "...", + "owner": "..." + }, +} ``` -GraphQL エンドポイントを使用すると、さまざまな GraphQL クライアントライブラリを使用してサブグラフをクエリし、サブグラフによってインデックス化されたデータをアプリに入力することができます。 +GraphQL queries use the GraphQL language, which is defined upon [a specification](https://spec.graphql.org/). -ここでは、エコシステムで人気のある GraphQL クライアントをいくつか紹介し、その使い方を説明します: +The above `GetToken` query is composed of multiple language parts (replaced below with `[...]` placeholders): -### Apollo クライアント +```graphql +query [operationName]([variableName]: [variableType]) { + [queryName]([argumentName]: [variableName]) { + # "{ ... }" express a Selection-Set, we are querying fields from `queryName`. + [field] + [field] + } +} +``` -[Apollo クライアント](https://www.apollographql.com/docs/)は、React や Vue などのフレームワークを含む Web プロジェクトや、iOS、Android、React Native などのモバイルクライアントをサポートしています。 +While the list of syntactic do's and don'ts is long, here are the essential rules to keep in mind when it comes to writing GraphQL queries: -Web プロジェクトで Apollo クライアントを使ってサブグラフからデータを取得する方法を見てみましょう。 +- Each `queryName` must only be used once per operation. +- Each `field` must be used only once in a selection (we cannot query `id` twice under `token`) +- Some `field`s 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 [The Graph Explorer](https://thegraph.com/docs/en/explorer/). +- Any variable assigned to an argument must match its type. +- In a given list of variables, each of them must be unique. +- All defined variables must be used. -まず、`@apollo/client`と`graphql`をインストールします: +Failing to follow the above rules will end with an error from the Graph API. -```sh -npm install @apollo/client graphql -``` +For a complete list of rules with code examples, please look at our GraphQL Validations guide. -その後、以下のコードで API をクエリできます: +
-```javascript -import { ApolloClient, InMemoryCache, gql } from '@apollo/client' +### Sending a query to a GraphQL API -const APIURL = 'https://api.studio.thegraph.com/query///' +GraphQL is a language and set of conventions that transport over HTTP. -const tokensQuery = ` - query { - tokens { - id - tokenID - contentURI - metadataURI - } +It means that you can query a GraphQL API using standard `fetch` (natively or via `cross-undici-fetch` or `isomorphic-fetch`) as follows: + +```tsx +const query = ` +query GetToken($id: ID!) { + token(id: $id) { + id + owner } +} ` +const variables = { id: "1" } -const client = new ApolloClient({ - uri: APIURL, - cache: new InMemoryCache(), +const fetchResult = await fetch('http://example.com/graphql', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ query, variables }) }) +const result = await fetchResult.json() +``` + +Another lightweight alternative, best for back-end use-cases, is the famous [`graphql-request` library](https://github.com/prisma-labs/graphql-request). + +This lightweight GraphQL client comes with the essential features to query a GraphQL API: + +- Mutations validation +- Support for file upload +- Batching +- Promise-based API +- TypeScript support + +
+ +A complete GraphQL client is [URQL](https://formidable.com/open-source/urql/) which is available within Node.js, React/Preact, Vue, Svelte environments, with more advanced features: + +- Flexible cache system +- Extensible design (easing adding new capabilities on top of it) +- Lightweight bundle (~5x lighter than Apollo Client) +- Support for file uploads and offline mode -client - .query({ - query: gql(tokensQuery), - }) - .then((data) => console.log('Subgraph data: ', data)) - .catch((err) => { - console.log('Error fetching data: ', err) - }) +
+ +In the React ecosystem, [React Query](https://react-query.tanstack.com/graphql) brings a lightweight and agnostic solution for GraphQL. + +React Query is a great candidate if you are looking for an easy-to-use and lightweight multipurpose client (GraphQL-capable) that provides essential features such as: + +- Powerful cache (background refresh, window-focus refreshing) +- Advanced querying pattern (parallel queries, dependent queries, prefetching) +- UX patterns: optimistic updates, scroll restoration +- Great dev tools + +
+ +Finally, Apollo Client is the ubiquitous GraphQL client on the front-end ecosystem. + +Available for React, Angular, Vue, Ember, iOS, and Android, Apollo Client, although the heaviest clients, brings many features to build advanced UI on-top of GraphQL: + +- advanced error handling +- pagination +- data prefetching +- optimistic UI +- local state management + +
+ +Now that we covered the basic rules of GraphQL queries syntax, let's now look at the best practices of GraphQL query writing. + +--- + +## Writing GraphQL queries + +### Always write static queries + +A common (bad) practice is to dynamically build query strings as follows: + +```tsx +const id = params.id +const fields = ["id", "owner"] +const query = ` +query GetToken { + token(id: ${id}) { + ${fields.join("\n")} + } +} +` + +// Execute query... ``` -変数を使うには、クエリの引数に`variables` を渡します。 +While the above snippet produces a valid GraphQL query, **it has many drawbacks**: -```javascript -const tokensQuery = ` - query($first: Int, $orderBy: BigInt, $orderDirection: String) { - tokens( - first: $first, orderBy: $orderBy, orderDirection: $orderDirection - ) { +- it makes it **harder to understand** the query as a whole +- developers are **responsible for safely sanitizing the string interpolation** +- not sending the values of the variables as part of the request parameters **prevent possible caching on server-side** +- it **prevents tools from statically analyzing the query** (ex: Linter, or type generations tools) + +For this reason, it is recommended to always write queries as static strings: + +```tsx +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 - tokenID - contentURI - metadataURI } } +) +``` + +Doing so brings **many advantages**: + +- **Easy to read and maintain** queries +- The GraphQL **server handles variables sanitization** +- **Variables can be cached** at server-level +- **Queries can be statically analyzed by tools** (more on this in the following sections) + +**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: + +```tsx +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) + } +} ` -client - .query({ - query: gql(tokensQuery), +const result = await execute( + query, + { variables: { - first: 10, - orderBy: 'createdAtTimestamp', - orderDirection: 'desc', - }, - }) - .then((data) => console.log('Subgraph data: ', data)) - .catch((err) => { - console.log('Error fetching data: ', err) - }) + id, + includeOwner: true + } + } +) ``` -### URQL +Note: The opposite directive is `@skip(if: ...)`. + +
-もう一つの選択肢は[URQL](https://formidable.com/open-source/urql/)で、URQL は、やや軽量な GraphQL クライアントライブラリです。 +### Performance tips -URQL は、やや軽量な GraphQL クライアントライブラリです。 +**"Ask for what you want"** -Web プロジェクトで URQL を使ってサブグラフからデータを取得する方法を見てみましょう。 まず、`urql`と`graphql`をインストールします。 +GraphQL became famous for its “Ask for what you want” tagline. -```sh -npm install urql graphql +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. + +Since some fields might be computed or proxied on the server-side, fetching fewer fields can make a big difference! + +```graphql +query getProtocols { + protocols(first: 5) { + id + inflation + inflationChange + maxEarningsClaimsRounds + numActiveTranscoders + } +} ``` -その後、以下のコードで API をクエリできます: +`numActiveTranscoders` might be slower (computed) field to resolve. + +If not needed, removing it will improve the overall query performance (*especially in a list context:* `list: 5`). -```javascript -import { createClient } from 'urql' +**Combining multiple queries** -const APIURL = 'https://api.thegraph.com/subgraphs/name/username/subgraphname' +Your application might require querying multiple types of data as follows: + +```graphql +import { execute } from "your-favorite-graphql-client" const tokensQuery = ` - query { - tokens { +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: + +```graphql +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**. + +
+ +### Leverage GraphQL Fragments + +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 (`{ ... }`): + +```graphql +query { + bondEvents { + id + newDelegate { + id + active + status + } + oldDelegate { id - tokenID - contentURI - metadataURI + active + status + } + } +} +``` + +Such repeated fields (`id`, `active`, `status`) bring many issues: + +- harder to read for more extensive queries +- 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: + +```graphql +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*). + +
+ +### GraphQL Fragment do's and don'ts + +**Fragment base must be a type** + +A Fragment cannot be based on a non-applicable type, in short, **on type not having fields**: + +```graphql +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. + +Example: + +```graphql +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: + +- when fields of the same type are repeated in a query, group them in a Fragment +- when similar but not the same fields are repeated, create multiple fragments, ex: + +```graphql +# 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 + } +} +``` + +--- + +## The essential tools + +### GraphQL Linting + +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](https://github.com/dotansimha/graphql-eslint) will help you stay on top of GraphQL best practices with zero effort. + +[Setup the "operations-recommended"](https://github.com/dotansimha/graphql-eslint#available-configs) config will enforce essential rules such as: + +- `@graphql-eslint/fields-on-correct-type`: is a field used on a proper type? +- `@graphql-eslint/no-unused variables`: should a given variable stay unused? +- and more! + +This will allow you to **catch errors without even testing queries** on the playground or running them in production! + +
+ +### IDE plugins + +**VSCode and GraphQL** + +The [GraphQL VSCode extension](https://marketplace.visualstudio.com/items?itemName=GraphQL.vscode-graphql) 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](https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint) is a must-have to visualize errors and warnings inlined in your code correctly. + + + +**WebStorm/Intellij and GraphQL** + +The [JS GraphQL plugin](https://plugins.jetbrains.com/plugin/8097-graphql/) will significantly improve your experience while working with GraphQL by providing: + +- syntax highlighting +- autocomplete suggestions +- validation against schema +- snippets + +More information on this [WebStorm article](https://blog.jetbrains.com/webstorm/2019/04/featured-plugin-js-graphql/) that showcases all the plugin's main features. + +
+ +### TypeScript types generation + +Finally, the best GraphQL experience is reached when the TypeScript data types are generated from the defined GraphQL queries, as follows: + +```tsx +import { execute } from "your-favorite-graphql-client" +import { GetTokenQuery } from "./generated." + +const id = params.id +const query = ` +query GetToken($id: ID!) { + token(id: $id) { + id + owner + } +} ` -const client = createClient({ - url: APIURL, -}) +const result: GetTokenQuery = await execute( + query, + { + variables: { + id + } + } +) -const data = await client.query(tokensQuery).toPromise() +// `result` is typed! ``` + +Such a setup can be easily achieved by installing and configuring [GraphQL Code Generator](https://www.graphql-code-generator.com/docs/getting-started) as follows: + +```bash +yarn add graphql @graphql-codegen/cli @graphql-codegen/typescript @graphql-codegen/typescript-operations +``` + +Then update your `package.json` (or similar script configuration setup) as follows: + +```tsx +{ + // ... + "scripts": { + // ... + "generate": "graphql-codegen", + // ... + } + // ... +} +``` + +Add the following configuration file to your project: + +```tsx +schema: "" +documents: './src/**/*.ts' +generates: + ./generated.ts: + plugins: + - typescript + - typescript-operations +``` + +Finally, by simply running the configured script (`yarn generate`), GraphQL Code Generator will generate the proper TypeScript types in the `generated.ts` file: + +- Each query will get a corresponding `[QueryName]Query` type (ex: `GetToken` → `GetTokenQuery` type +- Each fragment will get a corresponding `[FragmentName]Fragment` type (ex: `DelegateItem` → `DelegateItemFragment` type diff --git a/pages/ko/developer/querying-from-your-app.mdx b/pages/ko/developer/querying-from-your-app.mdx index e2216f6f75da..c575b4b809db 100644 --- a/pages/ko/developer/querying-from-your-app.mdx +++ b/pages/ko/developer/querying-from-your-app.mdx @@ -1,136 +1,573 @@ --- -title: 애플리케이션부터 쿼리하기 +title: Querying from an Application --- -서브그래프가 Subgraph Studio 또는 Graph Explorer에 배포되면 다음과 같은 GraphQL API용 엔드포인트가 제공됩니다. +The Graph provides a decentralized way to query data from blockchains. -**Subgraph Studio (테스팅 엔드포인트)** +The Graph network's data is exposed through a GraphQL API, making it easier to query data with the GraphQL language. -```sh -Queries (HTTP) -https://api.studio.thegraph.com/query/// +This page will guide you through the essential GraphQL language rules and GraphQL queries best practices. + +--- + +## Querying a GraphQL API + +### The anatomy of a GraphQL query + +Unlike REST API, a GraphQL API is built upon a Schema that defines which queries can be performed. + +For example, a query to get a token using the `token` query will look as follows: + +```graphql +query GetToken($id: ID!) { + token(id: $id) { + id + owner + } +} ``` -**그래프 탐색기** +which will return the following predictable JSON response (*when passing the proper `$id` variable value*): -```sh -Queries (HTTP) -https://gateway.thegraph.com/api//subgraphs/id/ +```json +{ + "token": { + "id": "...", + "owner": "..." + }, +} ``` -GraphQL 엔드포인트를 사용하면 다양한 GraphQL 클라이언트 라이브러리를 사용하여 서브그래프를 쿼리하고 서브그래프에 의해 인덱싱된 데이터로 앱을 채울 수 있습니다. +GraphQL queries use the GraphQL language, which is defined upon [a specification](https://spec.graphql.org/). -다음은 생태계에서 더 인기 있는 몇몇 GraphQL 클라이언트 및 사용 방법입니다: +The above `GetToken` query is composed of multiple language parts (replaced below with `[...]` placeholders): -### 아폴로 클라이언트(Apollo client) +```graphql +query [operationName]([variableName]: [variableType]) { + [queryName]([argumentName]: [variableName]) { + # "{ ... }" express a Selection-Set, we are querying fields from `queryName`. + [field] + [field] + } +} +``` -[Apollo client](https://www.apollographql.com/docs/) 는 iOS, Android 및 React Native와 같은 모바일 클라이언트는 물론 React 및 Vue와 같은 프레임워크를 포함한 웹 프로젝트를 지원합니다. +While the list of syntactic do's and don'ts is long, here are the essential rules to keep in mind when it comes to writing GraphQL queries: -웹 프로젝트에서 Apollo 클라이언트를 사용하여 서브그래프에서 데이터를 가져오는 방법을 살펴보겠습니다. +- Each `queryName` must only be used once per operation. +- Each `field` must be used only once in a selection (we cannot query `id` twice under `token`) +- Some `field`s 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 [The Graph Explorer](https://thegraph.com/docs/en/explorer/). +- Any variable assigned to an argument must match its type. +- In a given list of variables, each of them must be unique. +- All defined variables must be used. -우선, `@apollo/client` 및 `graphql`을 설치합니다: +Failing to follow the above rules will end with an error from the Graph API. -```sh -npm install @apollo/client graphql -``` +For a complete list of rules with code examples, please look at our GraphQL Validations guide. -그러면 여러분들은 다음의 코드로 API를 쿼리할 수 있습니다: +
-```javascript -import { ApolloClient, InMemoryCache, gql } from '@apollo/client' +### Sending a query to a GraphQL API -const APIURL = 'https://api.studio.thegraph.com/query///' +GraphQL is a language and set of conventions that transport over HTTP. -const tokensQuery = ` - query { - tokens { - id - tokenID - contentURI - metadataURI - } +It means that you can query a GraphQL API using standard `fetch` (natively or via `cross-undici-fetch` or `isomorphic-fetch`) as follows: + +```tsx +const query = ` +query GetToken($id: ID!) { + token(id: $id) { + id + owner } +} ` +const variables = { id: "1" } -const client = new ApolloClient({ - uri: APIURL, - cache: new InMemoryCache(), +const fetchResult = await fetch('http://example.com/graphql', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ query, variables }) }) +const result = await fetchResult.json() +``` + +Another lightweight alternative, best for back-end use-cases, is the famous [`graphql-request` library](https://github.com/prisma-labs/graphql-request). + +This lightweight GraphQL client comes with the essential features to query a GraphQL API: + +- Mutations validation +- Support for file upload +- Batching +- Promise-based API +- TypeScript support + +
+ +A complete GraphQL client is [URQL](https://formidable.com/open-source/urql/) which is available within Node.js, React/Preact, Vue, Svelte environments, with more advanced features: + +- Flexible cache system +- Extensible design (easing adding new capabilities on top of it) +- Lightweight bundle (~5x lighter than Apollo Client) +- Support for file uploads and offline mode -client - .query({ - query: gql(tokensQuery), - }) - .then((data) => console.log('Subgraph data: ', data)) - .catch((err) => { - console.log('Error fetching data: ', err) - }) +
+ +In the React ecosystem, [React Query](https://react-query.tanstack.com/graphql) brings a lightweight and agnostic solution for GraphQL. + +React Query is a great candidate if you are looking for an easy-to-use and lightweight multipurpose client (GraphQL-capable) that provides essential features such as: + +- Powerful cache (background refresh, window-focus refreshing) +- Advanced querying pattern (parallel queries, dependent queries, prefetching) +- UX patterns: optimistic updates, scroll restoration +- Great dev tools + +
+ +Finally, Apollo Client is the ubiquitous GraphQL client on the front-end ecosystem. + +Available for React, Angular, Vue, Ember, iOS, and Android, Apollo Client, although the heaviest clients, brings many features to build advanced UI on-top of GraphQL: + +- advanced error handling +- pagination +- data prefetching +- optimistic UI +- local state management + +
+ +Now that we covered the basic rules of GraphQL queries syntax, let's now look at the best practices of GraphQL query writing. + +--- + +## Writing GraphQL queries + +### Always write static queries + +A common (bad) practice is to dynamically build query strings as follows: + +```tsx +const id = params.id +const fields = ["id", "owner"] +const query = ` +query GetToken { + token(id: ${id}) { + ${fields.join("\n")} + } +} +` + +// Execute query... ``` -변수를 사용하기 위해서, 쿼리에 `variables` 인수를 전달할 수 있습니다. +While the above snippet produces a valid GraphQL query, **it has many drawbacks**: -```javascript -const tokensQuery = ` - query($first: Int, $orderBy: BigInt, $orderDirection: String) { - tokens( - first: $first, orderBy: $orderBy, orderDirection: $orderDirection - ) { +- it makes it **harder to understand** the query as a whole +- developers are **responsible for safely sanitizing the string interpolation** +- not sending the values of the variables as part of the request parameters **prevent possible caching on server-side** +- it **prevents tools from statically analyzing the query** (ex: Linter, or type generations tools) + +For this reason, it is recommended to always write queries as static strings: + +```tsx +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 - tokenID - contentURI - metadataURI } } +) +``` + +Doing so brings **many advantages**: + +- **Easy to read and maintain** queries +- The GraphQL **server handles variables sanitization** +- **Variables can be cached** at server-level +- **Queries can be statically analyzed by tools** (more on this in the following sections) + +**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: + +```tsx +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) + } +} ` -client - .query({ - query: gql(tokensQuery), +const result = await execute( + query, + { variables: { - first: 10, - orderBy: 'createdAtTimestamp', - orderDirection: 'desc', - }, - }) - .then((data) => console.log('Subgraph data: ', data)) - .catch((err) => { - console.log('Error fetching data: ', err) - }) + id, + includeOwner: true + } + } +) ``` -### URQL +Note: The opposite directive is `@skip(if: ...)`. + +
-또 다른 옵션은 약간 더 가벼운 GraphQL 클라이언트 라이브러리인 [URQL](https://formidable.com/open-source/urql/)입니다. +### Performance tips -웹 프로젝트에서 URQL을 사용하여 서브그래프에서 데이터를 가져오는 방법을 살펴보겠습니다. +**"Ask for what you want"** -우선, `urql` 및 `graphql`을 설치합니다: +GraphQL became famous for its “Ask for what you want” tagline. -```sh -npm install urql graphql +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. + +Since some fields might be computed or proxied on the server-side, fetching fewer fields can make a big difference! + +```graphql +query getProtocols { + protocols(first: 5) { + id + inflation + inflationChange + maxEarningsClaimsRounds + numActiveTranscoders + } +} ``` -그러면 여러분들은 다음의 코드로 API를 쿼리할 수 있습니다: +`numActiveTranscoders` might be slower (computed) field to resolve. + +If not needed, removing it will improve the overall query performance (*especially in a list context:* `list: 5`). -```javascript -import { createClient } from 'urql' +**Combining multiple queries** -const APIURL = 'https://api.thegraph.com/subgraphs/name/username/subgraphname' +Your application might require querying multiple types of data as follows: + +```graphql +import { execute } from "your-favorite-graphql-client" const tokensQuery = ` - query { - tokens { +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: + +```graphql +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**. + +
+ +### Leverage GraphQL Fragments + +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 (`{ ... }`): + +```graphql +query { + bondEvents { + id + newDelegate { + id + active + status + } + oldDelegate { id - tokenID - contentURI - metadataURI + active + status + } + } +} +``` + +Such repeated fields (`id`, `active`, `status`) bring many issues: + +- harder to read for more extensive queries +- 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: + +```graphql +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*). + +
+ +### GraphQL Fragment do's and don'ts + +**Fragment base must be a type** + +A Fragment cannot be based on a non-applicable type, in short, **on type not having fields**: + +```graphql +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. + +Example: + +```graphql +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: + +- when fields of the same type are repeated in a query, group them in a Fragment +- when similar but not the same fields are repeated, create multiple fragments, ex: + +```graphql +# 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 + } +} +``` + +--- + +## The essential tools + +### GraphQL Linting + +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](https://github.com/dotansimha/graphql-eslint) will help you stay on top of GraphQL best practices with zero effort. + +[Setup the "operations-recommended"](https://github.com/dotansimha/graphql-eslint#available-configs) config will enforce essential rules such as: + +- `@graphql-eslint/fields-on-correct-type`: is a field used on a proper type? +- `@graphql-eslint/no-unused variables`: should a given variable stay unused? +- and more! + +This will allow you to **catch errors without even testing queries** on the playground or running them in production! + +
+ +### IDE plugins + +**VSCode and GraphQL** + +The [GraphQL VSCode extension](https://marketplace.visualstudio.com/items?itemName=GraphQL.vscode-graphql) 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](https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint) is a must-have to visualize errors and warnings inlined in your code correctly. + + + +**WebStorm/Intellij and GraphQL** + +The [JS GraphQL plugin](https://plugins.jetbrains.com/plugin/8097-graphql/) will significantly improve your experience while working with GraphQL by providing: + +- syntax highlighting +- autocomplete suggestions +- validation against schema +- snippets + +More information on this [WebStorm article](https://blog.jetbrains.com/webstorm/2019/04/featured-plugin-js-graphql/) that showcases all the plugin's main features. + +
+ +### TypeScript types generation + +Finally, the best GraphQL experience is reached when the TypeScript data types are generated from the defined GraphQL queries, as follows: + +```tsx +import { execute } from "your-favorite-graphql-client" +import { GetTokenQuery } from "./generated." + +const id = params.id +const query = ` +query GetToken($id: ID!) { + token(id: $id) { + id + owner + } +} ` -const client = createClient({ - url: APIURL, -}) +const result: GetTokenQuery = await execute( + query, + { + variables: { + id + } + } +) -const data = await client.query(tokensQuery).toPromise() +// `result` is typed! ``` + +Such a setup can be easily achieved by installing and configuring [GraphQL Code Generator](https://www.graphql-code-generator.com/docs/getting-started) as follows: + +```bash +yarn add graphql @graphql-codegen/cli @graphql-codegen/typescript @graphql-codegen/typescript-operations +``` + +Then update your `package.json` (or similar script configuration setup) as follows: + +```tsx +{ + // ... + "scripts": { + // ... + "generate": "graphql-codegen", + // ... + } + // ... +} +``` + +Add the following configuration file to your project: + +```tsx +schema: "" +documents: './src/**/*.ts' +generates: + ./generated.ts: + plugins: + - typescript + - typescript-operations +``` + +Finally, by simply running the configured script (`yarn generate`), GraphQL Code Generator will generate the proper TypeScript types in the `generated.ts` file: + +- Each query will get a corresponding `[QueryName]Query` type (ex: `GetToken` → `GetTokenQuery` type +- Each fragment will get a corresponding `[FragmentName]Fragment` type (ex: `DelegateItem` → `DelegateItemFragment` type diff --git a/pages/vi/developer/querying-from-your-app.mdx b/pages/vi/developer/querying-from-your-app.mdx index 8cd9e23757df..c575b4b809db 100644 --- a/pages/vi/developer/querying-from-your-app.mdx +++ b/pages/vi/developer/querying-from-your-app.mdx @@ -1,136 +1,573 @@ --- -title: Truy vấn từ một ứng dụng +title: Querying from an Application --- -Sau khi một subgraph được triển khai tới Subgraph Studio hoặc tới Graph Explorer, bạn sẽ được cung cấp điểm cuối cho API GraphQL của mình trông giống như sau: +The Graph provides a decentralized way to query data from blockchains. -**Subgraph Studio (điểm cuối thử nghiệm)** +The Graph network's data is exposed through a GraphQL API, making it easier to query data with the GraphQL language. -```sh -Queries (HTTP) -https://api.studio.thegraph.com/query/// +This page will guide you through the essential GraphQL language rules and GraphQL queries best practices. + +--- + +## Querying a GraphQL API + +### The anatomy of a GraphQL query + +Unlike REST API, a GraphQL API is built upon a Schema that defines which queries can be performed. + +For example, a query to get a token using the `token` query will look as follows: + +```graphql +query GetToken($id: ID!) { + token(id: $id) { + id + owner + } +} ``` -**Trình khám phá Graph** +which will return the following predictable JSON response (*when passing the proper `$id` variable value*): -```sh -Queries (HTTP) -https://gateway.thegraph.com/api//subgraphs/id/ +```json +{ + "token": { + "id": "...", + "owner": "..." + }, +} ``` -Sử dụng điểm cuối GraphQL, bạn có thể sử dụng các thư viện GraphQL Client khác nhau để truy vấn subgraph và điền vào ứng dụng của bạn với dữ liệu được lập chỉ mục bởi subgraph. +GraphQL queries use the GraphQL language, which is defined upon [a specification](https://spec.graphql.org/). -Dưới đây là một số ứng dụng khách GraphQL phổ biến hơn trong hệ sinh thái và cách sử dụng chúng: +The above `GetToken` query is composed of multiple language parts (replaced below with `[...]` placeholders): -### Apollo client +```graphql +query [operationName]([variableName]: [variableType]) { + [queryName]([argumentName]: [variableName]) { + # "{ ... }" express a Selection-Set, we are querying fields from `queryName`. + [field] + [field] + } +} +``` -[Apollo client](https://www.apollographql.com/docs/) hỗ trợ các dự án web bao gồm các khuôn khổ như React và Vue, cũng như các ứng dụng khách di động như iOS, Android và React Native. +While the list of syntactic do's and don'ts is long, here are the essential rules to keep in mind when it comes to writing GraphQL queries: -Hãy xem cách tìm nạp dữ liệu từ một subgraph với Apollo client trong một dự án web. +- Each `queryName` must only be used once per operation. +- Each `field` must be used only once in a selection (we cannot query `id` twice under `token`) +- Some `field`s 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 [The Graph Explorer](https://thegraph.com/docs/en/explorer/). +- Any variable assigned to an argument must match its type. +- In a given list of variables, each of them must be unique. +- All defined variables must be used. -Đầu tiên, cài đặt `@apollo/client` và `graphql`: +Failing to follow the above rules will end with an error from the Graph API. -```sh -npm install @apollo/client graphql -``` +For a complete list of rules with code examples, please look at our GraphQL Validations guide. -Sau đó, bạn có thể truy vấn API bằng mã sau: +
-```javascript -import { ApolloClient, InMemoryCache, gql } from '@apollo/client' +### Sending a query to a GraphQL API -const APIURL = 'https://api.studio.thegraph.com/query///' +GraphQL is a language and set of conventions that transport over HTTP. -const tokensQuery = ` - query { - tokens { - id - tokenID - contentURI - metadataURI - } +It means that you can query a GraphQL API using standard `fetch` (natively or via `cross-undici-fetch` or `isomorphic-fetch`) as follows: + +```tsx +const query = ` +query GetToken($id: ID!) { + token(id: $id) { + id + owner } +} ` +const variables = { id: "1" } -const client = new ApolloClient({ - uri: APIURL, - cache: new InMemoryCache(), +const fetchResult = await fetch('http://example.com/graphql', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ query, variables }) }) +const result = await fetchResult.json() +``` + +Another lightweight alternative, best for back-end use-cases, is the famous [`graphql-request` library](https://github.com/prisma-labs/graphql-request). + +This lightweight GraphQL client comes with the essential features to query a GraphQL API: + +- Mutations validation +- Support for file upload +- Batching +- Promise-based API +- TypeScript support + +
+ +A complete GraphQL client is [URQL](https://formidable.com/open-source/urql/) which is available within Node.js, React/Preact, Vue, Svelte environments, with more advanced features: + +- Flexible cache system +- Extensible design (easing adding new capabilities on top of it) +- Lightweight bundle (~5x lighter than Apollo Client) +- Support for file uploads and offline mode -client - .query({ - query: gql(tokensQuery), - }) - .then((data) => console.log('Subgraph data: ', data)) - .catch((err) => { - console.log('Error fetching data: ', err) - }) +
+ +In the React ecosystem, [React Query](https://react-query.tanstack.com/graphql) brings a lightweight and agnostic solution for GraphQL. + +React Query is a great candidate if you are looking for an easy-to-use and lightweight multipurpose client (GraphQL-capable) that provides essential features such as: + +- Powerful cache (background refresh, window-focus refreshing) +- Advanced querying pattern (parallel queries, dependent queries, prefetching) +- UX patterns: optimistic updates, scroll restoration +- Great dev tools + +
+ +Finally, Apollo Client is the ubiquitous GraphQL client on the front-end ecosystem. + +Available for React, Angular, Vue, Ember, iOS, and Android, Apollo Client, although the heaviest clients, brings many features to build advanced UI on-top of GraphQL: + +- advanced error handling +- pagination +- data prefetching +- optimistic UI +- local state management + +
+ +Now that we covered the basic rules of GraphQL queries syntax, let's now look at the best practices of GraphQL query writing. + +--- + +## Writing GraphQL queries + +### Always write static queries + +A common (bad) practice is to dynamically build query strings as follows: + +```tsx +const id = params.id +const fields = ["id", "owner"] +const query = ` +query GetToken { + token(id: ${id}) { + ${fields.join("\n")} + } +} +` + +// Execute query... ``` -Để sử dụng biến, bạn có thể bỏ `biến` vào truy vấn: +While the above snippet produces a valid GraphQL query, **it has many drawbacks**: -```javascript -const tokensQuery = ` - query($first: Int, $orderBy: BigInt, $orderDirection: String) { - tokens( - first: $first, orderBy: $orderBy, orderDirection: $orderDirection - ) { +- it makes it **harder to understand** the query as a whole +- developers are **responsible for safely sanitizing the string interpolation** +- not sending the values of the variables as part of the request parameters **prevent possible caching on server-side** +- it **prevents tools from statically analyzing the query** (ex: Linter, or type generations tools) + +For this reason, it is recommended to always write queries as static strings: + +```tsx +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 - tokenID - contentURI - metadataURI } } +) +``` + +Doing so brings **many advantages**: + +- **Easy to read and maintain** queries +- The GraphQL **server handles variables sanitization** +- **Variables can be cached** at server-level +- **Queries can be statically analyzed by tools** (more on this in the following sections) + +**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: + +```tsx +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) + } +} ` -client - .query({ - query: gql(tokensQuery), +const result = await execute( + query, + { variables: { - first: 10, - orderBy: 'createdAtTimestamp', - orderDirection: 'desc', - }, - }) - .then((data) => console.log('Subgraph data: ', data)) - .catch((err) => { - console.log('Error fetching data: ', err) - }) + id, + includeOwner: true + } + } +) ``` -### URQL +Note: The opposite directive is `@skip(if: ...)`. + +
-Một tùy chọn khác là [URQL](https://formidable.com/open-source/urql/),một thư viện khách hàng GraphQL có trọng lượng nhẹ hơn một chút. +### Performance tips -Hãy xem cách tìm nạp dữ liệu từ một subgraph với URQL trong một dự án web. +**"Ask for what you want"** -Đầu tiên, cài đặt `urql` và `graphql`: +GraphQL became famous for its “Ask for what you want” tagline. -```sh -npm install urql graphql +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. + +Since some fields might be computed or proxied on the server-side, fetching fewer fields can make a big difference! + +```graphql +query getProtocols { + protocols(first: 5) { + id + inflation + inflationChange + maxEarningsClaimsRounds + numActiveTranscoders + } +} ``` -Sau đó, bạn có thể truy vấn API bằng mã sau: +`numActiveTranscoders` might be slower (computed) field to resolve. + +If not needed, removing it will improve the overall query performance (*especially in a list context:* `list: 5`). -```javascript -import { createClient } from 'urql' +**Combining multiple queries** -const APIURL = 'https://api.thegraph.com/subgraphs/name/username/subgraphname' +Your application might require querying multiple types of data as follows: + +```graphql +import { execute } from "your-favorite-graphql-client" const tokensQuery = ` - query { - tokens { +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: + +```graphql +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**. + +
+ +### Leverage GraphQL Fragments + +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 (`{ ... }`): + +```graphql +query { + bondEvents { + id + newDelegate { + id + active + status + } + oldDelegate { id - tokenID - contentURI - metadataURI + active + status + } + } +} +``` + +Such repeated fields (`id`, `active`, `status`) bring many issues: + +- harder to read for more extensive queries +- 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: + +```graphql +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*). + +
+ +### GraphQL Fragment do's and don'ts + +**Fragment base must be a type** + +A Fragment cannot be based on a non-applicable type, in short, **on type not having fields**: + +```graphql +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. + +Example: + +```graphql +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: + +- when fields of the same type are repeated in a query, group them in a Fragment +- when similar but not the same fields are repeated, create multiple fragments, ex: + +```graphql +# 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 + } +} +``` + +--- + +## The essential tools + +### GraphQL Linting + +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](https://github.com/dotansimha/graphql-eslint) will help you stay on top of GraphQL best practices with zero effort. + +[Setup the "operations-recommended"](https://github.com/dotansimha/graphql-eslint#available-configs) config will enforce essential rules such as: + +- `@graphql-eslint/fields-on-correct-type`: is a field used on a proper type? +- `@graphql-eslint/no-unused variables`: should a given variable stay unused? +- and more! + +This will allow you to **catch errors without even testing queries** on the playground or running them in production! + +
+ +### IDE plugins + +**VSCode and GraphQL** + +The [GraphQL VSCode extension](https://marketplace.visualstudio.com/items?itemName=GraphQL.vscode-graphql) 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](https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint) is a must-have to visualize errors and warnings inlined in your code correctly. + + + +**WebStorm/Intellij and GraphQL** + +The [JS GraphQL plugin](https://plugins.jetbrains.com/plugin/8097-graphql/) will significantly improve your experience while working with GraphQL by providing: + +- syntax highlighting +- autocomplete suggestions +- validation against schema +- snippets + +More information on this [WebStorm article](https://blog.jetbrains.com/webstorm/2019/04/featured-plugin-js-graphql/) that showcases all the plugin's main features. + +
+ +### TypeScript types generation + +Finally, the best GraphQL experience is reached when the TypeScript data types are generated from the defined GraphQL queries, as follows: + +```tsx +import { execute } from "your-favorite-graphql-client" +import { GetTokenQuery } from "./generated." + +const id = params.id +const query = ` +query GetToken($id: ID!) { + token(id: $id) { + id + owner + } +} ` -const client = createClient({ - url: APIURL, -}) +const result: GetTokenQuery = await execute( + query, + { + variables: { + id + } + } +) -const data = await client.query(tokensQuery).toPromise() +// `result` is typed! ``` + +Such a setup can be easily achieved by installing and configuring [GraphQL Code Generator](https://www.graphql-code-generator.com/docs/getting-started) as follows: + +```bash +yarn add graphql @graphql-codegen/cli @graphql-codegen/typescript @graphql-codegen/typescript-operations +``` + +Then update your `package.json` (or similar script configuration setup) as follows: + +```tsx +{ + // ... + "scripts": { + // ... + "generate": "graphql-codegen", + // ... + } + // ... +} +``` + +Add the following configuration file to your project: + +```tsx +schema: "" +documents: './src/**/*.ts' +generates: + ./generated.ts: + plugins: + - typescript + - typescript-operations +``` + +Finally, by simply running the configured script (`yarn generate`), GraphQL Code Generator will generate the proper TypeScript types in the `generated.ts` file: + +- Each query will get a corresponding `[QueryName]Query` type (ex: `GetToken` → `GetTokenQuery` type +- Each fragment will get a corresponding `[FragmentName]Fragment` type (ex: `DelegateItem` → `DelegateItemFragment` type diff --git a/pages/zh/developer/querying-from-your-app.mdx b/pages/zh/developer/querying-from-your-app.mdx index 0cd65bcc84c8..c575b4b809db 100644 --- a/pages/zh/developer/querying-from-your-app.mdx +++ b/pages/zh/developer/querying-from-your-app.mdx @@ -1,136 +1,573 @@ --- -title: 从应用程序中进行查询 +title: Querying from an Application --- -一旦子图被部署到子图工作室或 Graph Explorer,你将得到 GraphQL API 的端点,它应该看起来像这样。 +The Graph provides a decentralized way to query data from blockchains. -**子图工作室(测试端点)** +The Graph network's data is exposed through a GraphQL API, making it easier to query data with the GraphQL language. -```sh -Queries (HTTP) -https://api.studio.thegraph.com/query/// +This page will guide you through the essential GraphQL language rules and GraphQL queries best practices. + +--- + +## Querying a GraphQL API + +### The anatomy of a GraphQL query + +Unlike REST API, a GraphQL API is built upon a Schema that defines which queries can be performed. + +For example, a query to get a token using the `token` query will look as follows: + +```graphql +query GetToken($id: ID!) { + token(id: $id) { + id + owner + } +} ``` -**Graph 浏览器** +which will return the following predictable JSON response (*when passing the proper `$id` variable value*): -```sh -Queries (HTTP) -https://gateway.thegraph.com/api//subgraphs/id/ +```json +{ + "token": { + "id": "...", + "owner": "..." + }, +} ``` -使用 GraphQL 端点,你可以使用各种 GraphQL 客户端库来查询子图,并用子图索引的数据来填充你的应用程序。 +GraphQL queries use the GraphQL language, which is defined upon [a specification](https://spec.graphql.org/). -下面是生态系统中几个比较流行的 GraphQL 客户端以及如何使用它们。 +The above `GetToken` query is composed of multiple language parts (replaced below with `[...]` placeholders): -### Apollo 客户端 +```graphql +query [operationName]([variableName]: [variableType]) { + [queryName]([argumentName]: [variableName]) { + # "{ ... }" express a Selection-Set, we are querying fields from `queryName`. + [field] + [field] + } +} +``` -[Apollo 客户端](https://www.apollographql.com/docs/) 支持网络项目,包括 React 和 Vue 等框架,以及 iOS、Android 和 React Native 等移动客户端。 +While the list of syntactic do's and don'ts is long, here are the essential rules to keep in mind when it comes to writing GraphQL queries: -让我们看看如何在一个网络项目中用 Apollo 客户端从子图中获取数据。 +- Each `queryName` must only be used once per operation. +- Each `field` must be used only once in a selection (we cannot query `id` twice under `token`) +- Some `field`s 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 [The Graph Explorer](https://thegraph.com/docs/en/explorer/). +- Any variable assigned to an argument must match its type. +- In a given list of variables, each of them must be unique. +- All defined variables must be used. -首先,安装`@apollo/client` 和`graphql`: +Failing to follow the above rules will end with an error from the Graph API. -```sh -npm install @apollo/client graphql -``` +For a complete list of rules with code examples, please look at our GraphQL Validations guide. -然后你可以用以下代码查询 API: +
-```javascript -import { ApolloClient, InMemoryCache, gql } from '@apollo/client' +### Sending a query to a GraphQL API -const APIURL = 'https://api.studio.thegraph.com/query///' +GraphQL is a language and set of conventions that transport over HTTP. -const tokensQuery = ` - query { - tokens { - id - tokenID - contentURI - metadataURI - } +It means that you can query a GraphQL API using standard `fetch` (natively or via `cross-undici-fetch` or `isomorphic-fetch`) as follows: + +```tsx +const query = ` +query GetToken($id: ID!) { + token(id: $id) { + id + owner } +} ` +const variables = { id: "1" } -const client = new ApolloClient({ - uri: APIURL, - cache: new InMemoryCache(), +const fetchResult = await fetch('http://example.com/graphql', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ query, variables }) }) +const result = await fetchResult.json() +``` + +Another lightweight alternative, best for back-end use-cases, is the famous [`graphql-request` library](https://github.com/prisma-labs/graphql-request). + +This lightweight GraphQL client comes with the essential features to query a GraphQL API: + +- Mutations validation +- Support for file upload +- Batching +- Promise-based API +- TypeScript support + +
+ +A complete GraphQL client is [URQL](https://formidable.com/open-source/urql/) which is available within Node.js, React/Preact, Vue, Svelte environments, with more advanced features: + +- Flexible cache system +- Extensible design (easing adding new capabilities on top of it) +- Lightweight bundle (~5x lighter than Apollo Client) +- Support for file uploads and offline mode -client - .query({ - query: gql(tokensQuery), - }) - .then((data) => console.log('Subgraph data: ', data)) - .catch((err) => { - console.log('Error fetching data: ', err) - }) +
+ +In the React ecosystem, [React Query](https://react-query.tanstack.com/graphql) brings a lightweight and agnostic solution for GraphQL. + +React Query is a great candidate if you are looking for an easy-to-use and lightweight multipurpose client (GraphQL-capable) that provides essential features such as: + +- Powerful cache (background refresh, window-focus refreshing) +- Advanced querying pattern (parallel queries, dependent queries, prefetching) +- UX patterns: optimistic updates, scroll restoration +- Great dev tools + +
+ +Finally, Apollo Client is the ubiquitous GraphQL client on the front-end ecosystem. + +Available for React, Angular, Vue, Ember, iOS, and Android, Apollo Client, although the heaviest clients, brings many features to build advanced UI on-top of GraphQL: + +- advanced error handling +- pagination +- data prefetching +- optimistic UI +- local state management + +
+ +Now that we covered the basic rules of GraphQL queries syntax, let's now look at the best practices of GraphQL query writing. + +--- + +## Writing GraphQL queries + +### Always write static queries + +A common (bad) practice is to dynamically build query strings as follows: + +```tsx +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**: -```javascript -const tokensQuery = ` - query($first: Int, $orderBy: BigInt, $orderDirection: String) { - tokens( - first: $first, orderBy: $orderBy, orderDirection: $orderDirection - ) { +- it makes it **harder to understand** the query as a whole +- developers are **responsible for safely sanitizing the string interpolation** +- not sending the values of the variables as part of the request parameters **prevent possible caching on server-side** +- it **prevents tools from statically analyzing the query** (ex: Linter, or type generations tools) + +For this reason, it is recommended to always write queries as static strings: + +```tsx +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 - tokenID - contentURI - metadataURI } } +) +``` + +Doing so brings **many advantages**: + +- **Easy to read and maintain** queries +- The GraphQL **server handles variables sanitization** +- **Variables can be cached** at server-level +- **Queries can be statically analyzed by tools** (more on this in the following sections) + +**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: + +```tsx +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) + } +} ` -client - .query({ - query: gql(tokensQuery), +const result = await execute( + query, + { variables: { - first: 10, - orderBy: 'createdAtTimestamp', - orderDirection: 'desc', - }, - }) - .then((data) => console.log('Subgraph data: ', data)) - .catch((err) => { - console.log('Error fetching data: ', err) - }) + id, + includeOwner: true + } + } +) ``` -### URQL +Note: The opposite directive is `@skip(if: ...)`. + +
-另一个选择是[URQL](https://formidable.com/open-source/urql/),一个有点轻量级的 GraphQL 客户端库。 +### Performance tips -让我们看看如何在一个网络项目中用 URQL 从子图中获取数据。 +**"Ask for what you want"** -首先,安装`urql`和 `graphql`: +GraphQL became famous for its “Ask for what you want” tagline. -```sh -npm install urql graphql +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. + +Since some fields might be computed or proxied on the server-side, fetching fewer fields can make a big difference! + +```graphql +query getProtocols { + protocols(first: 5) { + id + inflation + inflationChange + maxEarningsClaimsRounds + numActiveTranscoders + } +} ``` -然后你可以用以下代码查询 API: +`numActiveTranscoders` might be slower (computed) field to resolve. + +If not needed, removing it will improve the overall query performance (*especially in a list context:* `list: 5`). -```javascript -import { createClient } from 'urql' +**Combining multiple queries** -const APIURL = 'https://api.thegraph.com/subgraphs/name/username/subgraphname' +Your application might require querying multiple types of data as follows: + +```graphql +import { execute } from "your-favorite-graphql-client" const tokensQuery = ` - query { - tokens { +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: + +```graphql +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**. + +
+ +### Leverage GraphQL Fragments + +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 (`{ ... }`): + +```graphql +query { + bondEvents { + id + newDelegate { + id + active + status + } + oldDelegate { id - tokenID - contentURI - metadataURI + active + status + } + } +} +``` + +Such repeated fields (`id`, `active`, `status`) bring many issues: + +- harder to read for more extensive queries +- 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: + +```graphql +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*). + +
+ +### GraphQL Fragment do's and don'ts + +**Fragment base must be a type** + +A Fragment cannot be based on a non-applicable type, in short, **on type not having fields**: + +```graphql +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. + +Example: + +```graphql +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: + +- when fields of the same type are repeated in a query, group them in a Fragment +- when similar but not the same fields are repeated, create multiple fragments, ex: + +```graphql +# 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 + } +} +``` + +--- + +## The essential tools + +### GraphQL Linting + +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](https://github.com/dotansimha/graphql-eslint) will help you stay on top of GraphQL best practices with zero effort. + +[Setup the "operations-recommended"](https://github.com/dotansimha/graphql-eslint#available-configs) config will enforce essential rules such as: + +- `@graphql-eslint/fields-on-correct-type`: is a field used on a proper type? +- `@graphql-eslint/no-unused variables`: should a given variable stay unused? +- and more! + +This will allow you to **catch errors without even testing queries** on the playground or running them in production! + +
+ +### IDE plugins + +**VSCode and GraphQL** + +The [GraphQL VSCode extension](https://marketplace.visualstudio.com/items?itemName=GraphQL.vscode-graphql) 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](https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint) is a must-have to visualize errors and warnings inlined in your code correctly. + + + +**WebStorm/Intellij and GraphQL** + +The [JS GraphQL plugin](https://plugins.jetbrains.com/plugin/8097-graphql/) will significantly improve your experience while working with GraphQL by providing: + +- syntax highlighting +- autocomplete suggestions +- validation against schema +- snippets + +More information on this [WebStorm article](https://blog.jetbrains.com/webstorm/2019/04/featured-plugin-js-graphql/) that showcases all the plugin's main features. + +
+ +### TypeScript types generation + +Finally, the best GraphQL experience is reached when the TypeScript data types are generated from the defined GraphQL queries, as follows: + +```tsx +import { execute } from "your-favorite-graphql-client" +import { GetTokenQuery } from "./generated." + +const id = params.id +const query = ` +query GetToken($id: ID!) { + token(id: $id) { + id + owner + } +} ` -const client = createClient({ - url: APIURL, -}) +const result: GetTokenQuery = await execute( + query, + { + variables: { + id + } + } +) -const data = await client.query(tokensQuery).toPromise() +// `result` is typed! ``` + +Such a setup can be easily achieved by installing and configuring [GraphQL Code Generator](https://www.graphql-code-generator.com/docs/getting-started) as follows: + +```bash +yarn add graphql @graphql-codegen/cli @graphql-codegen/typescript @graphql-codegen/typescript-operations +``` + +Then update your `package.json` (or similar script configuration setup) as follows: + +```tsx +{ + // ... + "scripts": { + // ... + "generate": "graphql-codegen", + // ... + } + // ... +} +``` + +Add the following configuration file to your project: + +```tsx +schema: "" +documents: './src/**/*.ts' +generates: + ./generated.ts: + plugins: + - typescript + - typescript-operations +``` + +Finally, by simply running the configured script (`yarn generate`), GraphQL Code Generator will generate the proper TypeScript types in the `generated.ts` file: + +- Each query will get a corresponding `[QueryName]Query` type (ex: `GetToken` → `GetTokenQuery` type +- Each fragment will get a corresponding `[FragmentName]Fragment` type (ex: `DelegateItem` → `DelegateItemFragment` type From b65682886f91350811037d1c3859b0a7604318a9 Mon Sep 17 00:00:00 2001 From: Charly POLY Date: Tue, 22 Feb 2022 10:32:13 +0100 Subject: [PATCH 3/6] doc(developer): querying from your app: add better over-fetching example + links to web-based playgrounds --- pages/en/developer/querying-from-your-app.mdx | 35 ++++++++++++------- 1 file changed, 23 insertions(+), 12 deletions(-) diff --git a/pages/en/developer/querying-from-your-app.mdx b/pages/en/developer/querying-from-your-app.mdx index c575b4b809db..75c1d4030992 100644 --- a/pages/en/developer/querying-from-your-app.mdx +++ b/pages/en/developer/querying-from-your-app.mdx @@ -239,29 +239,30 @@ Note: The opposite directive is `@skip(if: ...)`. **"Ask for what you want"** -GraphQL became famous for its “Ask for what you want” tagline. +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. -Since some fields might be computed or proxied on the server-side, fetching fewer fields can make a big difference! +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: ```graphql -query getProtocols { - protocols(first: 5) { +query listTokens { + tokens { # will fetch up to 100 tokens id - inflation - inflationChange - maxEarningsClaimsRounds - numActiveTranscoders + transactions { # will fetch up to 100 transactions + id + } } } ``` -`numActiveTranscoders` might be slower (computed) field to resolve. +The response could contain 100 transactions for each of the 100 tokens. -If not needed, removing it will improve the overall query performance (*especially in a list context:* `list: 5`). +If the application only needs 10 transactions, the query should explicitly set `first: 10` on the transactions field. **Combining multiple queries** @@ -377,7 +378,7 @@ fragment DelegateItem on Transcoder { 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*). +When using the types generation tool, the above query will generate a proper `DelegateItemFragment` type (*see last "Tools" section*).
@@ -393,7 +394,7 @@ fragment MyFragment on BigInt { } ``` -`BigInt` is a **scalar** (native “plain” type) that cannot be used as a fragment's base. +`BigInt` is a **scalar** (native "plain" type) that cannot be used as a fragment's base. **How to spread a Fragment** @@ -458,6 +459,16 @@ fragment VoteWithPoll on Vote { ## The essential tools +### GraphQL web-based explorers + +Iterating over queries by running them in your application can be cumbersome. +For this reason, don't hesitate to use [The Graph Explorer](https://thegraph.com/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](https://altair.sirmuel.design/) and [GraphiQL](https://graphiql-online.com/graphiql). + +
+ ### GraphQL Linting 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. From 02f6f84d1eb53e8e1127b923d765bffbe86c08b4 Mon Sep 17 00:00:00 2001 From: Charly POLY Date: Wed, 2 Mar 2022 18:11:10 +0100 Subject: [PATCH 4/6] Revert "feat(developer): force english for "Querying from an Application" to force retranslation" This reverts commit 15f050620e225b31feb3808fccb62c9ceaad2194. --- pages/ar/developer/querying-from-your-app.mdx | 603 +++--------------- pages/es/developer/querying-from-your-app.mdx | 603 +++--------------- pages/ja/developer/querying-from-your-app.mdx | 603 +++--------------- pages/ko/developer/querying-from-your-app.mdx | 603 +++--------------- pages/vi/developer/querying-from-your-app.mdx | 603 +++--------------- pages/zh/developer/querying-from-your-app.mdx | 603 +++--------------- 6 files changed, 498 insertions(+), 3120 deletions(-) diff --git a/pages/ar/developer/querying-from-your-app.mdx b/pages/ar/developer/querying-from-your-app.mdx index c575b4b809db..5082bfae455c 100644 --- a/pages/ar/developer/querying-from-your-app.mdx +++ b/pages/ar/developer/querying-from-your-app.mdx @@ -1,573 +1,136 @@ --- -title: Querying from an Application +title: الاستعلام من التطبيق --- -The Graph provides a decentralized way to query data from blockchains. +بمجرد نشر ال Subgraph في Subgraph Studio أو في Graph Explorer ، سيتم إعطاؤك endpoint ل GraphQL API الخاصة بك والتي يجب أن تبدو كما يلي: -The Graph network's data is exposed through a GraphQL API, making it easier to query data with the GraphQL language. +**Subgraph Studio (اختبار endpoint)** -This page will guide you through the essential GraphQL language rules and GraphQL queries best practices. - ---- - -## Querying a GraphQL API - -### The anatomy of a GraphQL query - -Unlike REST API, a GraphQL API is built upon a Schema that defines which queries can be performed. - -For example, a query to get a token using the `token` query will look as follows: - -```graphql -query GetToken($id: ID!) { - token(id: $id) { - id - owner - } -} +```sh +استعلامات (HTTP) +https://api.studio.thegraph.com/query/// ``` -which will return the following predictable JSON response (*when passing the proper `$id` variable value*): +**Graph Explorer** -```json -{ - "token": { - "id": "...", - "owner": "..." - }, -} +```sh +استعلامات (HTTP) +https://gateway.thegraph.com/api//subgraphs/id/ ``` -GraphQL queries use the GraphQL language, which is defined upon [a specification](https://spec.graphql.org/). - -The above `GetToken` query is composed of multiple language parts (replaced below with `[...]` placeholders): +باستخدام GraphQL endpoint ، يمكنك استخدام العديد من مكتبات GraphQL Client للاستعلام عن ال Subgraph وملء تطبيقك بالبيانات المفهرسة بواسطة ال Subgraph. -```graphql -query [operationName]([variableName]: [variableType]) { - [queryName]([argumentName]: [variableName]) { - # "{ ... }" express a Selection-Set, we are querying fields from `queryName`. - [field] - [field] - } -} -``` +في ما يلي بعض عملاء GraphQL الأكثر شيوعا في النظام البيئي وكيفية استخدامها: -While the list of syntactic do's and don'ts is long, here are the essential rules to keep in mind when it comes to writing GraphQL queries: +### عميل Apollo -- Each `queryName` must only be used once per operation. -- Each `field` must be used only once in a selection (we cannot query `id` twice under `token`) -- Some `field`s 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 [The Graph Explorer](https://thegraph.com/docs/en/explorer/). -- Any variable assigned to an argument must match its type. -- In a given list of variables, each of them must be unique. -- All defined variables must be used. +[Apoolo client](https://www.apollographql.com/docs/) يدعم مشاريع الويب بما في ذلك ال framework مثل React و Vue ، بالإضافة إلى mobile clients مثل iOS و Android و React Native. -Failing to follow the above rules will end with an error from the Graph API. +لنلقِ نظرة على كيفية جلب البيانات من Subgraph وذلك باستخدام Apollo client في مشروع ويب. -For a complete list of rules with code examples, please look at our GraphQL Validations guide. +اولا قم بتثبيت `apollo/client@` و `graphql`: -
+```sh +npm install @apollo/client graphql +``` -### Sending a query to a GraphQL API +بعد ذلك يمكنك الاستعلام عن API بالكود التالي: -GraphQL is a language and set of conventions that transport over HTTP. +```javascript +import { ApolloClient, InMemoryCache, gql } from '@apollo/client' -It means that you can query a GraphQL API using standard `fetch` (natively or via `cross-undici-fetch` or `isomorphic-fetch`) as follows: +const APIURL = 'https://api.studio.thegraph.com/query///' -```tsx -const query = ` -query GetToken($id: ID!) { - token(id: $id) { - id - owner +const tokensQuery = ` + query { + tokens { + id + tokenID + contentURI + metadataURI + } } -} ` -const variables = { id: "1" } -const fetchResult = await fetch('http://example.com/graphql', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ query, variables }) +const client = new ApolloClient({ + uri: APIURL, + cache: new InMemoryCache(), }) -const result = await fetchResult.json() -``` - -Another lightweight alternative, best for back-end use-cases, is the famous [`graphql-request` library](https://github.com/prisma-labs/graphql-request). - -This lightweight GraphQL client comes with the essential features to query a GraphQL API: - -- Mutations validation -- Support for file upload -- Batching -- Promise-based API -- TypeScript support - -
- -A complete GraphQL client is [URQL](https://formidable.com/open-source/urql/) which is available within Node.js, React/Preact, Vue, Svelte environments, with more advanced features: - -- Flexible cache system -- Extensible design (easing adding new capabilities on top of it) -- Lightweight bundle (~5x lighter than Apollo Client) -- Support for file uploads and offline mode -
- -In the React ecosystem, [React Query](https://react-query.tanstack.com/graphql) brings a lightweight and agnostic solution for GraphQL. - -React Query is a great candidate if you are looking for an easy-to-use and lightweight multipurpose client (GraphQL-capable) that provides essential features such as: - -- Powerful cache (background refresh, window-focus refreshing) -- Advanced querying pattern (parallel queries, dependent queries, prefetching) -- UX patterns: optimistic updates, scroll restoration -- Great dev tools - -
- -Finally, Apollo Client is the ubiquitous GraphQL client on the front-end ecosystem. - -Available for React, Angular, Vue, Ember, iOS, and Android, Apollo Client, although the heaviest clients, brings many features to build advanced UI on-top of GraphQL: - -- advanced error handling -- pagination -- data prefetching -- optimistic UI -- local state management - -
- -Now that we covered the basic rules of GraphQL queries syntax, let's now look at the best practices of GraphQL query writing. - ---- - -## Writing GraphQL queries - -### Always write static queries - -A common (bad) practice is to dynamically build query strings as follows: - -```tsx -const id = params.id -const fields = ["id", "owner"] -const query = ` -query GetToken { - token(id: ${id}) { - ${fields.join("\n")} - } -} -` - -// Execute query... +client + .query({ + query: gql(tokensQuery), + }) + .then((data) => console.log('Subgraph data: ', data)) + .catch((err) => { + console.log('Error fetching data: ', err) + }) ``` -While the above snippet produces a valid GraphQL query, **it has many drawbacks**: - -- it makes it **harder to understand** the query as a whole -- developers are **responsible for safely sanitizing the string interpolation** -- not sending the values of the variables as part of the request parameters **prevent possible caching on server-side** -- it **prevents tools from statically analyzing the query** (ex: Linter, or type generations tools) - -For this reason, it is recommended to always write queries as static strings: - -```tsx -import { execute } from "your-favorite-graphql-client" +لاستخدام المتغيرات، يمكنك تمرير وسيطة (argument) الـ `variables` للاستعلام: -const id = params.id -const query = ` -query GetToken($id: ID!) { - token(id: $id) { - id - owner - } -} -` - -const result = await execute( - query, - { - variables: { +```javascript +const tokensQuery = ` + query($first: Int, $orderBy: BigInt, $orderDirection: String) { + tokens( + first: $first, orderBy: $orderBy, orderDirection: $orderDirection + ) { id + tokenID + contentURI + metadataURI } } -) -``` - -Doing so brings **many advantages**: - -- **Easy to read and maintain** queries -- The GraphQL **server handles variables sanitization** -- **Variables can be cached** at server-level -- **Queries can be statically analyzed by tools** (more on this in the following sections) - -**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: - -```tsx -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, - { +client + .query({ + query: gql(tokensQuery), variables: { - id, - includeOwner: true - } - } -) + first: 10, + orderBy: 'createdAtTimestamp', + orderDirection: 'desc', + }, + }) + .then((data) => console.log('Subgraph data: ', data)) + .catch((err) => { + console.log('Error fetching data: ', err) + }) ``` -Note: The opposite directive is `@skip(if: ...)`. - -
+### URQL -### Performance tips +هناك خيار آخر وهو [URQL](https://formidable.com/open-source/urql/) ، وهي مكتبة GraphQL client أخف وزنا إلى حد ما. -**"Ask for what you want"** +لنلقِ نظرة على كيفية جلب البيانات من Subgraph باستخدام URQL في مشروع ويب. -GraphQL became famous for its “Ask for what you want” tagline. +اولا قم بتثبيت `urql` و `graphql`: -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. - -Since some fields might be computed or proxied on the server-side, fetching fewer fields can make a big difference! - -```graphql -query getProtocols { - protocols(first: 5) { - id - inflation - inflationChange - maxEarningsClaimsRounds - numActiveTranscoders - } -} +```sh +npm install urql graphql ``` -`numActiveTranscoders` might be slower (computed) field to resolve. - -If not needed, removing it will improve the overall query performance (*especially in a list context:* `list: 5`). +بعد ذلك يمكنك الاستعلام عن API بالكود التالي: -**Combining multiple queries** +```javascript +import { createClient } from 'urql' -Your application might require querying multiple types of data as follows: - -```graphql -import { execute } from "your-favorite-graphql-client" +const APIURL = 'https://api.thegraph.com/subgraphs/name/username/subgraphname' 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: - -```graphql -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**. - -
- -### Leverage GraphQL Fragments - -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 (`{ ... }`): - -```graphql -query { - bondEvents { - id - newDelegate { - id - active - status - } - oldDelegate { + query { + tokens { id - active - status - } - } -} -``` - -Such repeated fields (`id`, `active`, `status`) bring many issues: - -- harder to read for more extensive queries -- 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: - -```graphql -query { - bondEvents { - id - newDelegate { - ...DelegateItem - } - oldDelegate { - ...DelegateItem + tokenID + contentURI + metadataURI } } -} - -# 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*). - -
- -### GraphQL Fragment do's and don'ts - -**Fragment base must be a type** - -A Fragment cannot be based on a non-applicable type, in short, **on type not having fields**: - -```graphql -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. - -Example: - -```graphql -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: - -- when fields of the same type are repeated in a query, group them in a Fragment -- when similar but not the same fields are repeated, create multiple fragments, ex: - -```graphql -# 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 - } -} -``` - ---- - -## The essential tools - -### GraphQL Linting - -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](https://github.com/dotansimha/graphql-eslint) will help you stay on top of GraphQL best practices with zero effort. - -[Setup the "operations-recommended"](https://github.com/dotansimha/graphql-eslint#available-configs) config will enforce essential rules such as: - -- `@graphql-eslint/fields-on-correct-type`: is a field used on a proper type? -- `@graphql-eslint/no-unused variables`: should a given variable stay unused? -- and more! - -This will allow you to **catch errors without even testing queries** on the playground or running them in production! - -
- -### IDE plugins - -**VSCode and GraphQL** - -The [GraphQL VSCode extension](https://marketplace.visualstudio.com/items?itemName=GraphQL.vscode-graphql) 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](https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint) is a must-have to visualize errors and warnings inlined in your code correctly. - - - -**WebStorm/Intellij and GraphQL** - -The [JS GraphQL plugin](https://plugins.jetbrains.com/plugin/8097-graphql/) will significantly improve your experience while working with GraphQL by providing: - -- syntax highlighting -- autocomplete suggestions -- validation against schema -- snippets - -More information on this [WebStorm article](https://blog.jetbrains.com/webstorm/2019/04/featured-plugin-js-graphql/) that showcases all the plugin's main features. - -
- -### TypeScript types generation - -Finally, the best GraphQL experience is reached when the TypeScript data types are generated from the defined GraphQL queries, as follows: - -```tsx -import { execute } from "your-favorite-graphql-client" -import { GetTokenQuery } from "./generated." - -const id = params.id -const query = ` -query GetToken($id: ID!) { - token(id: $id) { - id - owner - } -} ` -const result: GetTokenQuery = await execute( - query, - { - variables: { - id - } - } -) - -// `result` is typed! -``` - -Such a setup can be easily achieved by installing and configuring [GraphQL Code Generator](https://www.graphql-code-generator.com/docs/getting-started) as follows: - -```bash -yarn add graphql @graphql-codegen/cli @graphql-codegen/typescript @graphql-codegen/typescript-operations -``` - -Then update your `package.json` (or similar script configuration setup) as follows: - -```tsx -{ - // ... - "scripts": { - // ... - "generate": "graphql-codegen", - // ... - } - // ... -} -``` - -Add the following configuration file to your project: +const client = createClient({ + url: APIURL, +}) -```tsx -schema: "" -documents: './src/**/*.ts' -generates: - ./generated.ts: - plugins: - - typescript - - typescript-operations +const data = await client.query(tokensQuery).toPromise() ``` - -Finally, by simply running the configured script (`yarn generate`), GraphQL Code Generator will generate the proper TypeScript types in the `generated.ts` file: - -- Each query will get a corresponding `[QueryName]Query` type (ex: `GetToken` → `GetTokenQuery` type -- Each fragment will get a corresponding `[FragmentName]Fragment` type (ex: `DelegateItem` → `DelegateItemFragment` type diff --git a/pages/es/developer/querying-from-your-app.mdx b/pages/es/developer/querying-from-your-app.mdx index c575b4b809db..fb8c7895afaa 100644 --- a/pages/es/developer/querying-from-your-app.mdx +++ b/pages/es/developer/querying-from-your-app.mdx @@ -1,573 +1,136 @@ --- -title: Querying from an Application +title: Consultar desde una Aplicacion --- -The Graph provides a decentralized way to query data from blockchains. +Una vez que un subgrafo es desplegado en Subgraph Studio o en The Graph Explorer, se te dará el endpoint para tu API GraphQL que debería ser algo así: -The Graph network's data is exposed through a GraphQL API, making it easier to query data with the GraphQL language. +**Subgraph Studio (endpoint de prueba)** -This page will guide you through the essential GraphQL language rules and GraphQL queries best practices. - ---- - -## Querying a GraphQL API - -### The anatomy of a GraphQL query - -Unlike REST API, a GraphQL API is built upon a Schema that defines which queries can be performed. - -For example, a query to get a token using the `token` query will look as follows: - -```graphql -query GetToken($id: ID!) { - token(id: $id) { - id - owner - } -} +```sh +Queries (HTTP) +https://api.studio.thegraph.com/query/// ``` -which will return the following predictable JSON response (*when passing the proper `$id` variable value*): +**Graph Explorer** -```json -{ - "token": { - "id": "...", - "owner": "..." - }, -} +```sh +Queries (HTTP) +https://gateway.thegraph.com/api//subgraphs/id/ ``` -GraphQL queries use the GraphQL language, which is defined upon [a specification](https://spec.graphql.org/). - -The above `GetToken` query is composed of multiple language parts (replaced below with `[...]` placeholders): +Usando el endpoint de GraphQL, puedes usar varias librerías de Clientes de GraphQL para consultar el subgrafo y rellenar tu aplicación con los datos indexados por el subgrafo. -```graphql -query [operationName]([variableName]: [variableType]) { - [queryName]([argumentName]: [variableName]) { - # "{ ... }" express a Selection-Set, we are querying fields from `queryName`. - [field] - [field] - } -} -``` +A continuación se presentan un par de clientes GraphQL más populares en el ecosistema y cómo utilizarlos: -While the list of syntactic do's and don'ts is long, here are the essential rules to keep in mind when it comes to writing GraphQL queries: +### Cliente Apollo -- Each `queryName` must only be used once per operation. -- Each `field` must be used only once in a selection (we cannot query `id` twice under `token`) -- Some `field`s 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 [The Graph Explorer](https://thegraph.com/docs/en/explorer/). -- Any variable assigned to an argument must match its type. -- In a given list of variables, each of them must be unique. -- All defined variables must be used. +[Apollo client](https://www.apollographql.com/docs/) admite proyectos web que incluyen frameworks como React y Vue, así como clientes móviles como iOS, Android y React Native. -Failing to follow the above rules will end with an error from the Graph API. +Veamos cómo obtener datos de un subgrafo con el cliente Apollo en un proyecto web. -For a complete list of rules with code examples, please look at our GraphQL Validations guide. +Primero, instala `@apollo/client` y `graphql`: -
+```sh +npm install @apollo/client graphql +``` -### Sending a query to a GraphQL API +A continuación, puedes consultar la API con el siguiente código: -GraphQL is a language and set of conventions that transport over HTTP. +```javascript +import { ApolloClient, InMemoryCache, gql } from '@apollo/client' -It means that you can query a GraphQL API using standard `fetch` (natively or via `cross-undici-fetch` or `isomorphic-fetch`) as follows: +const APIURL = 'https://api.studio.thegraph.com/query///' -```tsx -const query = ` -query GetToken($id: ID!) { - token(id: $id) { - id - owner +const tokensQuery = ` + query { + tokens { + id + tokenID + contentURI + metadataURI + } } -} ` -const variables = { id: "1" } -const fetchResult = await fetch('http://example.com/graphql', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ query, variables }) +const client = new ApolloClient({ + uri: APIURL, + cache: new InMemoryCache(), }) -const result = await fetchResult.json() -``` - -Another lightweight alternative, best for back-end use-cases, is the famous [`graphql-request` library](https://github.com/prisma-labs/graphql-request). - -This lightweight GraphQL client comes with the essential features to query a GraphQL API: - -- Mutations validation -- Support for file upload -- Batching -- Promise-based API -- TypeScript support - -
- -A complete GraphQL client is [URQL](https://formidable.com/open-source/urql/) which is available within Node.js, React/Preact, Vue, Svelte environments, with more advanced features: - -- Flexible cache system -- Extensible design (easing adding new capabilities on top of it) -- Lightweight bundle (~5x lighter than Apollo Client) -- Support for file uploads and offline mode -
- -In the React ecosystem, [React Query](https://react-query.tanstack.com/graphql) brings a lightweight and agnostic solution for GraphQL. - -React Query is a great candidate if you are looking for an easy-to-use and lightweight multipurpose client (GraphQL-capable) that provides essential features such as: - -- Powerful cache (background refresh, window-focus refreshing) -- Advanced querying pattern (parallel queries, dependent queries, prefetching) -- UX patterns: optimistic updates, scroll restoration -- Great dev tools - -
- -Finally, Apollo Client is the ubiquitous GraphQL client on the front-end ecosystem. - -Available for React, Angular, Vue, Ember, iOS, and Android, Apollo Client, although the heaviest clients, brings many features to build advanced UI on-top of GraphQL: - -- advanced error handling -- pagination -- data prefetching -- optimistic UI -- local state management - -
- -Now that we covered the basic rules of GraphQL queries syntax, let's now look at the best practices of GraphQL query writing. - ---- - -## Writing GraphQL queries - -### Always write static queries - -A common (bad) practice is to dynamically build query strings as follows: - -```tsx -const id = params.id -const fields = ["id", "owner"] -const query = ` -query GetToken { - token(id: ${id}) { - ${fields.join("\n")} - } -} -` - -// Execute query... +client + .query({ + query: gql(tokensQuery), + }) + .then((data) => console.log('Subgraph data: ', data)) + .catch((err) => { + console.log('Error fetching data: ', err) + }) ``` -While the above snippet produces a valid GraphQL query, **it has many drawbacks**: - -- it makes it **harder to understand** the query as a whole -- developers are **responsible for safely sanitizing the string interpolation** -- not sending the values of the variables as part of the request parameters **prevent possible caching on server-side** -- it **prevents tools from statically analyzing the query** (ex: Linter, or type generations tools) - -For this reason, it is recommended to always write queries as static strings: - -```tsx -import { execute } from "your-favorite-graphql-client" +Para utilizar variables, puedes pasar un argumento `variables` a la consulta: -const id = params.id -const query = ` -query GetToken($id: ID!) { - token(id: $id) { - id - owner - } -} -` - -const result = await execute( - query, - { - variables: { +```javascript +const tokensQuery = ` + query($first: Int, $orderBy: BigInt, $orderDirection: String) { + tokens( + first: $first, orderBy: $orderBy, orderDirection: $orderDirection + ) { id + tokenID + contentURI + metadataURI } } -) -``` - -Doing so brings **many advantages**: - -- **Easy to read and maintain** queries -- The GraphQL **server handles variables sanitization** -- **Variables can be cached** at server-level -- **Queries can be statically analyzed by tools** (more on this in the following sections) - -**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: - -```tsx -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, - { +client + .query({ + query: gql(tokensQuery), variables: { - id, - includeOwner: true - } - } -) + first: 10, + orderBy: 'createdAtTimestamp', + orderDirection: 'desc', + }, + }) + .then((data) => console.log('Subgraph data: ', data)) + .catch((err) => { + console.log('Error fetching data: ', err) + }) ``` -Note: The opposite directive is `@skip(if: ...)`. - -
+### URQL -### Performance tips +Otra opción es [URQL](https://formidable.com/open-source/urql/), una libreria cliente de GraphQL algo más ligera. -**"Ask for what you want"** +Veamos cómo obtener datos de un subgrafo con URQL en un proyecto web. -GraphQL became famous for its “Ask for what you want” tagline. +Primero, instala `urql` and `graphql`: -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. - -Since some fields might be computed or proxied on the server-side, fetching fewer fields can make a big difference! - -```graphql -query getProtocols { - protocols(first: 5) { - id - inflation - inflationChange - maxEarningsClaimsRounds - numActiveTranscoders - } -} +```sh +npm install urql graphql ``` -`numActiveTranscoders` might be slower (computed) field to resolve. - -If not needed, removing it will improve the overall query performance (*especially in a list context:* `list: 5`). +A continuación, puedes consultar la API con el siguiente código: -**Combining multiple queries** +```javascript +import { createClient } from 'urql' -Your application might require querying multiple types of data as follows: - -```graphql -import { execute } from "your-favorite-graphql-client" +const APIURL = 'https://api.thegraph.com/subgraphs/name/username/subgraphname' 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: - -```graphql -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**. - -
- -### Leverage GraphQL Fragments - -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 (`{ ... }`): - -```graphql -query { - bondEvents { - id - newDelegate { - id - active - status - } - oldDelegate { + query { + tokens { id - active - status - } - } -} -``` - -Such repeated fields (`id`, `active`, `status`) bring many issues: - -- harder to read for more extensive queries -- 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: - -```graphql -query { - bondEvents { - id - newDelegate { - ...DelegateItem - } - oldDelegate { - ...DelegateItem + tokenID + contentURI + metadataURI } } -} - -# 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*). - -
- -### GraphQL Fragment do's and don'ts - -**Fragment base must be a type** - -A Fragment cannot be based on a non-applicable type, in short, **on type not having fields**: - -```graphql -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. - -Example: - -```graphql -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: - -- when fields of the same type are repeated in a query, group them in a Fragment -- when similar but not the same fields are repeated, create multiple fragments, ex: - -```graphql -# 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 - } -} -``` - ---- - -## The essential tools - -### GraphQL Linting - -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](https://github.com/dotansimha/graphql-eslint) will help you stay on top of GraphQL best practices with zero effort. - -[Setup the "operations-recommended"](https://github.com/dotansimha/graphql-eslint#available-configs) config will enforce essential rules such as: - -- `@graphql-eslint/fields-on-correct-type`: is a field used on a proper type? -- `@graphql-eslint/no-unused variables`: should a given variable stay unused? -- and more! - -This will allow you to **catch errors without even testing queries** on the playground or running them in production! - -
- -### IDE plugins - -**VSCode and GraphQL** - -The [GraphQL VSCode extension](https://marketplace.visualstudio.com/items?itemName=GraphQL.vscode-graphql) 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](https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint) is a must-have to visualize errors and warnings inlined in your code correctly. - - - -**WebStorm/Intellij and GraphQL** - -The [JS GraphQL plugin](https://plugins.jetbrains.com/plugin/8097-graphql/) will significantly improve your experience while working with GraphQL by providing: - -- syntax highlighting -- autocomplete suggestions -- validation against schema -- snippets - -More information on this [WebStorm article](https://blog.jetbrains.com/webstorm/2019/04/featured-plugin-js-graphql/) that showcases all the plugin's main features. - -
- -### TypeScript types generation - -Finally, the best GraphQL experience is reached when the TypeScript data types are generated from the defined GraphQL queries, as follows: - -```tsx -import { execute } from "your-favorite-graphql-client" -import { GetTokenQuery } from "./generated." - -const id = params.id -const query = ` -query GetToken($id: ID!) { - token(id: $id) { - id - owner - } -} ` -const result: GetTokenQuery = await execute( - query, - { - variables: { - id - } - } -) - -// `result` is typed! -``` - -Such a setup can be easily achieved by installing and configuring [GraphQL Code Generator](https://www.graphql-code-generator.com/docs/getting-started) as follows: - -```bash -yarn add graphql @graphql-codegen/cli @graphql-codegen/typescript @graphql-codegen/typescript-operations -``` - -Then update your `package.json` (or similar script configuration setup) as follows: - -```tsx -{ - // ... - "scripts": { - // ... - "generate": "graphql-codegen", - // ... - } - // ... -} -``` - -Add the following configuration file to your project: +const client = createClient({ + url: APIURL, +}) -```tsx -schema: "" -documents: './src/**/*.ts' -generates: - ./generated.ts: - plugins: - - typescript - - typescript-operations +const data = await client.query(tokensQuery).toPromise() ``` - -Finally, by simply running the configured script (`yarn generate`), GraphQL Code Generator will generate the proper TypeScript types in the `generated.ts` file: - -- Each query will get a corresponding `[QueryName]Query` type (ex: `GetToken` → `GetTokenQuery` type -- Each fragment will get a corresponding `[FragmentName]Fragment` type (ex: `DelegateItem` → `DelegateItemFragment` type diff --git a/pages/ja/developer/querying-from-your-app.mdx b/pages/ja/developer/querying-from-your-app.mdx index c575b4b809db..507ba4d02ad6 100644 --- a/pages/ja/developer/querying-from-your-app.mdx +++ b/pages/ja/developer/querying-from-your-app.mdx @@ -1,573 +1,136 @@ --- -title: Querying from an Application +title: アプリケーションからのクエリ --- -The Graph provides a decentralized way to query data from blockchains. +サブグラフが Subgraph Studio または Graph Explorer にデプロイされると、GraphQL API のエンドポイントが与えられ、以下のような形になります。 -The Graph network's data is exposed through a GraphQL API, making it easier to query data with the GraphQL language. +**Subgraph Studio (テスト用エンドポイント)** -This page will guide you through the essential GraphQL language rules and GraphQL queries best practices. - ---- - -## Querying a GraphQL API - -### The anatomy of a GraphQL query - -Unlike REST API, a GraphQL API is built upon a Schema that defines which queries can be performed. - -For example, a query to get a token using the `token` query will look as follows: - -```graphql -query GetToken($id: ID!) { - token(id: $id) { - id - owner - } -} +```sh +Queries (HTTP) +https://api.studio.thegraph.com/query/// ``` -which will return the following predictable JSON response (*when passing the proper `$id` variable value*): +**グラフエクスプローラ** -```json -{ - "token": { - "id": "...", - "owner": "..." - }, -} +```sh +Queries (HTTP) +https://gateway.thegraph.com/api//subgraphs/id/ ``` -GraphQL queries use the GraphQL language, which is defined upon [a specification](https://spec.graphql.org/). - -The above `GetToken` query is composed of multiple language parts (replaced below with `[...]` placeholders): +GraphQL エンドポイントを使用すると、さまざまな GraphQL クライアントライブラリを使用してサブグラフをクエリし、サブグラフによってインデックス化されたデータをアプリに入力することができます。 -```graphql -query [operationName]([variableName]: [variableType]) { - [queryName]([argumentName]: [variableName]) { - # "{ ... }" express a Selection-Set, we are querying fields from `queryName`. - [field] - [field] - } -} -``` +ここでは、エコシステムで人気のある GraphQL クライアントをいくつか紹介し、その使い方を説明します: -While the list of syntactic do's and don'ts is long, here are the essential rules to keep in mind when it comes to writing GraphQL queries: +### Apollo クライアント -- Each `queryName` must only be used once per operation. -- Each `field` must be used only once in a selection (we cannot query `id` twice under `token`) -- Some `field`s 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 [The Graph Explorer](https://thegraph.com/docs/en/explorer/). -- Any variable assigned to an argument must match its type. -- In a given list of variables, each of them must be unique. -- All defined variables must be used. +[Apollo クライアント](https://www.apollographql.com/docs/)は、React や Vue などのフレームワークを含む Web プロジェクトや、iOS、Android、React Native などのモバイルクライアントをサポートしています。 -Failing to follow the above rules will end with an error from the Graph API. +Web プロジェクトで Apollo クライアントを使ってサブグラフからデータを取得する方法を見てみましょう。 -For a complete list of rules with code examples, please look at our GraphQL Validations guide. +まず、`@apollo/client`と`graphql`をインストールします: -
+```sh +npm install @apollo/client graphql +``` -### Sending a query to a GraphQL API +その後、以下のコードで API をクエリできます: -GraphQL is a language and set of conventions that transport over HTTP. +```javascript +import { ApolloClient, InMemoryCache, gql } from '@apollo/client' -It means that you can query a GraphQL API using standard `fetch` (natively or via `cross-undici-fetch` or `isomorphic-fetch`) as follows: +const APIURL = 'https://api.studio.thegraph.com/query///' -```tsx -const query = ` -query GetToken($id: ID!) { - token(id: $id) { - id - owner +const tokensQuery = ` + query { + tokens { + id + tokenID + contentURI + metadataURI + } } -} ` -const variables = { id: "1" } -const fetchResult = await fetch('http://example.com/graphql', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ query, variables }) +const client = new ApolloClient({ + uri: APIURL, + cache: new InMemoryCache(), }) -const result = await fetchResult.json() -``` - -Another lightweight alternative, best for back-end use-cases, is the famous [`graphql-request` library](https://github.com/prisma-labs/graphql-request). - -This lightweight GraphQL client comes with the essential features to query a GraphQL API: - -- Mutations validation -- Support for file upload -- Batching -- Promise-based API -- TypeScript support - -
- -A complete GraphQL client is [URQL](https://formidable.com/open-source/urql/) which is available within Node.js, React/Preact, Vue, Svelte environments, with more advanced features: - -- Flexible cache system -- Extensible design (easing adding new capabilities on top of it) -- Lightweight bundle (~5x lighter than Apollo Client) -- Support for file uploads and offline mode -
- -In the React ecosystem, [React Query](https://react-query.tanstack.com/graphql) brings a lightweight and agnostic solution for GraphQL. - -React Query is a great candidate if you are looking for an easy-to-use and lightweight multipurpose client (GraphQL-capable) that provides essential features such as: - -- Powerful cache (background refresh, window-focus refreshing) -- Advanced querying pattern (parallel queries, dependent queries, prefetching) -- UX patterns: optimistic updates, scroll restoration -- Great dev tools - -
- -Finally, Apollo Client is the ubiquitous GraphQL client on the front-end ecosystem. - -Available for React, Angular, Vue, Ember, iOS, and Android, Apollo Client, although the heaviest clients, brings many features to build advanced UI on-top of GraphQL: - -- advanced error handling -- pagination -- data prefetching -- optimistic UI -- local state management - -
- -Now that we covered the basic rules of GraphQL queries syntax, let's now look at the best practices of GraphQL query writing. - ---- - -## Writing GraphQL queries - -### Always write static queries - -A common (bad) practice is to dynamically build query strings as follows: - -```tsx -const id = params.id -const fields = ["id", "owner"] -const query = ` -query GetToken { - token(id: ${id}) { - ${fields.join("\n")} - } -} -` - -// Execute query... +client + .query({ + query: gql(tokensQuery), + }) + .then((data) => console.log('Subgraph data: ', data)) + .catch((err) => { + console.log('Error fetching data: ', err) + }) ``` -While the above snippet produces a valid GraphQL query, **it has many drawbacks**: - -- it makes it **harder to understand** the query as a whole -- developers are **responsible for safely sanitizing the string interpolation** -- not sending the values of the variables as part of the request parameters **prevent possible caching on server-side** -- it **prevents tools from statically analyzing the query** (ex: Linter, or type generations tools) - -For this reason, it is recommended to always write queries as static strings: - -```tsx -import { execute } from "your-favorite-graphql-client" +変数を使うには、クエリの引数に`variables` を渡します。 -const id = params.id -const query = ` -query GetToken($id: ID!) { - token(id: $id) { - id - owner - } -} -` - -const result = await execute( - query, - { - variables: { +```javascript +const tokensQuery = ` + query($first: Int, $orderBy: BigInt, $orderDirection: String) { + tokens( + first: $first, orderBy: $orderBy, orderDirection: $orderDirection + ) { id + tokenID + contentURI + metadataURI } } -) -``` - -Doing so brings **many advantages**: - -- **Easy to read and maintain** queries -- The GraphQL **server handles variables sanitization** -- **Variables can be cached** at server-level -- **Queries can be statically analyzed by tools** (more on this in the following sections) - -**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: - -```tsx -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, - { +client + .query({ + query: gql(tokensQuery), variables: { - id, - includeOwner: true - } - } -) + first: 10, + orderBy: 'createdAtTimestamp', + orderDirection: 'desc', + }, + }) + .then((data) => console.log('Subgraph data: ', data)) + .catch((err) => { + console.log('Error fetching data: ', err) + }) ``` -Note: The opposite directive is `@skip(if: ...)`. - -
+### URQL -### Performance tips +もう一つの選択肢は[URQL](https://formidable.com/open-source/urql/)で、URQL は、やや軽量な GraphQL クライアントライブラリです。 -**"Ask for what you want"** +URQL は、やや軽量な GraphQL クライアントライブラリです。 -GraphQL became famous for its “Ask for what you want” tagline. +Web プロジェクトで URQL を使ってサブグラフからデータを取得する方法を見てみましょう。 まず、`urql`と`graphql`をインストールします。 -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. - -Since some fields might be computed or proxied on the server-side, fetching fewer fields can make a big difference! - -```graphql -query getProtocols { - protocols(first: 5) { - id - inflation - inflationChange - maxEarningsClaimsRounds - numActiveTranscoders - } -} +```sh +npm install urql graphql ``` -`numActiveTranscoders` might be slower (computed) field to resolve. - -If not needed, removing it will improve the overall query performance (*especially in a list context:* `list: 5`). +その後、以下のコードで API をクエリできます: -**Combining multiple queries** +```javascript +import { createClient } from 'urql' -Your application might require querying multiple types of data as follows: - -```graphql -import { execute } from "your-favorite-graphql-client" +const APIURL = 'https://api.thegraph.com/subgraphs/name/username/subgraphname' 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: - -```graphql -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**. - -
- -### Leverage GraphQL Fragments - -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 (`{ ... }`): - -```graphql -query { - bondEvents { - id - newDelegate { - id - active - status - } - oldDelegate { + query { + tokens { id - active - status - } - } -} -``` - -Such repeated fields (`id`, `active`, `status`) bring many issues: - -- harder to read for more extensive queries -- 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: - -```graphql -query { - bondEvents { - id - newDelegate { - ...DelegateItem - } - oldDelegate { - ...DelegateItem + tokenID + contentURI + metadataURI } } -} - -# 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*). - -
- -### GraphQL Fragment do's and don'ts - -**Fragment base must be a type** - -A Fragment cannot be based on a non-applicable type, in short, **on type not having fields**: - -```graphql -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. - -Example: - -```graphql -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: - -- when fields of the same type are repeated in a query, group them in a Fragment -- when similar but not the same fields are repeated, create multiple fragments, ex: - -```graphql -# 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 - } -} -``` - ---- - -## The essential tools - -### GraphQL Linting - -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](https://github.com/dotansimha/graphql-eslint) will help you stay on top of GraphQL best practices with zero effort. - -[Setup the "operations-recommended"](https://github.com/dotansimha/graphql-eslint#available-configs) config will enforce essential rules such as: - -- `@graphql-eslint/fields-on-correct-type`: is a field used on a proper type? -- `@graphql-eslint/no-unused variables`: should a given variable stay unused? -- and more! - -This will allow you to **catch errors without even testing queries** on the playground or running them in production! - -
- -### IDE plugins - -**VSCode and GraphQL** - -The [GraphQL VSCode extension](https://marketplace.visualstudio.com/items?itemName=GraphQL.vscode-graphql) 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](https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint) is a must-have to visualize errors and warnings inlined in your code correctly. - - - -**WebStorm/Intellij and GraphQL** - -The [JS GraphQL plugin](https://plugins.jetbrains.com/plugin/8097-graphql/) will significantly improve your experience while working with GraphQL by providing: - -- syntax highlighting -- autocomplete suggestions -- validation against schema -- snippets - -More information on this [WebStorm article](https://blog.jetbrains.com/webstorm/2019/04/featured-plugin-js-graphql/) that showcases all the plugin's main features. - -
- -### TypeScript types generation - -Finally, the best GraphQL experience is reached when the TypeScript data types are generated from the defined GraphQL queries, as follows: - -```tsx -import { execute } from "your-favorite-graphql-client" -import { GetTokenQuery } from "./generated." - -const id = params.id -const query = ` -query GetToken($id: ID!) { - token(id: $id) { - id - owner - } -} ` -const result: GetTokenQuery = await execute( - query, - { - variables: { - id - } - } -) - -// `result` is typed! -``` - -Such a setup can be easily achieved by installing and configuring [GraphQL Code Generator](https://www.graphql-code-generator.com/docs/getting-started) as follows: - -```bash -yarn add graphql @graphql-codegen/cli @graphql-codegen/typescript @graphql-codegen/typescript-operations -``` - -Then update your `package.json` (or similar script configuration setup) as follows: - -```tsx -{ - // ... - "scripts": { - // ... - "generate": "graphql-codegen", - // ... - } - // ... -} -``` - -Add the following configuration file to your project: +const client = createClient({ + url: APIURL, +}) -```tsx -schema: "" -documents: './src/**/*.ts' -generates: - ./generated.ts: - plugins: - - typescript - - typescript-operations +const data = await client.query(tokensQuery).toPromise() ``` - -Finally, by simply running the configured script (`yarn generate`), GraphQL Code Generator will generate the proper TypeScript types in the `generated.ts` file: - -- Each query will get a corresponding `[QueryName]Query` type (ex: `GetToken` → `GetTokenQuery` type -- Each fragment will get a corresponding `[FragmentName]Fragment` type (ex: `DelegateItem` → `DelegateItemFragment` type diff --git a/pages/ko/developer/querying-from-your-app.mdx b/pages/ko/developer/querying-from-your-app.mdx index c575b4b809db..e2216f6f75da 100644 --- a/pages/ko/developer/querying-from-your-app.mdx +++ b/pages/ko/developer/querying-from-your-app.mdx @@ -1,573 +1,136 @@ --- -title: Querying from an Application +title: 애플리케이션부터 쿼리하기 --- -The Graph provides a decentralized way to query data from blockchains. +서브그래프가 Subgraph Studio 또는 Graph Explorer에 배포되면 다음과 같은 GraphQL API용 엔드포인트가 제공됩니다. -The Graph network's data is exposed through a GraphQL API, making it easier to query data with the GraphQL language. +**Subgraph Studio (테스팅 엔드포인트)** -This page will guide you through the essential GraphQL language rules and GraphQL queries best practices. - ---- - -## Querying a GraphQL API - -### The anatomy of a GraphQL query - -Unlike REST API, a GraphQL API is built upon a Schema that defines which queries can be performed. - -For example, a query to get a token using the `token` query will look as follows: - -```graphql -query GetToken($id: ID!) { - token(id: $id) { - id - owner - } -} +```sh +Queries (HTTP) +https://api.studio.thegraph.com/query/// ``` -which will return the following predictable JSON response (*when passing the proper `$id` variable value*): +**그래프 탐색기** -```json -{ - "token": { - "id": "...", - "owner": "..." - }, -} +```sh +Queries (HTTP) +https://gateway.thegraph.com/api//subgraphs/id/ ``` -GraphQL queries use the GraphQL language, which is defined upon [a specification](https://spec.graphql.org/). - -The above `GetToken` query is composed of multiple language parts (replaced below with `[...]` placeholders): +GraphQL 엔드포인트를 사용하면 다양한 GraphQL 클라이언트 라이브러리를 사용하여 서브그래프를 쿼리하고 서브그래프에 의해 인덱싱된 데이터로 앱을 채울 수 있습니다. -```graphql -query [operationName]([variableName]: [variableType]) { - [queryName]([argumentName]: [variableName]) { - # "{ ... }" express a Selection-Set, we are querying fields from `queryName`. - [field] - [field] - } -} -``` +다음은 생태계에서 더 인기 있는 몇몇 GraphQL 클라이언트 및 사용 방법입니다: -While the list of syntactic do's and don'ts is long, here are the essential rules to keep in mind when it comes to writing GraphQL queries: +### 아폴로 클라이언트(Apollo client) -- Each `queryName` must only be used once per operation. -- Each `field` must be used only once in a selection (we cannot query `id` twice under `token`) -- Some `field`s 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 [The Graph Explorer](https://thegraph.com/docs/en/explorer/). -- Any variable assigned to an argument must match its type. -- In a given list of variables, each of them must be unique. -- All defined variables must be used. +[Apollo client](https://www.apollographql.com/docs/) 는 iOS, Android 및 React Native와 같은 모바일 클라이언트는 물론 React 및 Vue와 같은 프레임워크를 포함한 웹 프로젝트를 지원합니다. -Failing to follow the above rules will end with an error from the Graph API. +웹 프로젝트에서 Apollo 클라이언트를 사용하여 서브그래프에서 데이터를 가져오는 방법을 살펴보겠습니다. -For a complete list of rules with code examples, please look at our GraphQL Validations guide. +우선, `@apollo/client` 및 `graphql`을 설치합니다: -
+```sh +npm install @apollo/client graphql +``` -### Sending a query to a GraphQL API +그러면 여러분들은 다음의 코드로 API를 쿼리할 수 있습니다: -GraphQL is a language and set of conventions that transport over HTTP. +```javascript +import { ApolloClient, InMemoryCache, gql } from '@apollo/client' -It means that you can query a GraphQL API using standard `fetch` (natively or via `cross-undici-fetch` or `isomorphic-fetch`) as follows: +const APIURL = 'https://api.studio.thegraph.com/query///' -```tsx -const query = ` -query GetToken($id: ID!) { - token(id: $id) { - id - owner +const tokensQuery = ` + query { + tokens { + id + tokenID + contentURI + metadataURI + } } -} ` -const variables = { id: "1" } -const fetchResult = await fetch('http://example.com/graphql', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ query, variables }) +const client = new ApolloClient({ + uri: APIURL, + cache: new InMemoryCache(), }) -const result = await fetchResult.json() -``` - -Another lightweight alternative, best for back-end use-cases, is the famous [`graphql-request` library](https://github.com/prisma-labs/graphql-request). - -This lightweight GraphQL client comes with the essential features to query a GraphQL API: - -- Mutations validation -- Support for file upload -- Batching -- Promise-based API -- TypeScript support - -
- -A complete GraphQL client is [URQL](https://formidable.com/open-source/urql/) which is available within Node.js, React/Preact, Vue, Svelte environments, with more advanced features: - -- Flexible cache system -- Extensible design (easing adding new capabilities on top of it) -- Lightweight bundle (~5x lighter than Apollo Client) -- Support for file uploads and offline mode -
- -In the React ecosystem, [React Query](https://react-query.tanstack.com/graphql) brings a lightweight and agnostic solution for GraphQL. - -React Query is a great candidate if you are looking for an easy-to-use and lightweight multipurpose client (GraphQL-capable) that provides essential features such as: - -- Powerful cache (background refresh, window-focus refreshing) -- Advanced querying pattern (parallel queries, dependent queries, prefetching) -- UX patterns: optimistic updates, scroll restoration -- Great dev tools - -
- -Finally, Apollo Client is the ubiquitous GraphQL client on the front-end ecosystem. - -Available for React, Angular, Vue, Ember, iOS, and Android, Apollo Client, although the heaviest clients, brings many features to build advanced UI on-top of GraphQL: - -- advanced error handling -- pagination -- data prefetching -- optimistic UI -- local state management - -
- -Now that we covered the basic rules of GraphQL queries syntax, let's now look at the best practices of GraphQL query writing. - ---- - -## Writing GraphQL queries - -### Always write static queries - -A common (bad) practice is to dynamically build query strings as follows: - -```tsx -const id = params.id -const fields = ["id", "owner"] -const query = ` -query GetToken { - token(id: ${id}) { - ${fields.join("\n")} - } -} -` - -// Execute query... +client + .query({ + query: gql(tokensQuery), + }) + .then((data) => console.log('Subgraph data: ', data)) + .catch((err) => { + console.log('Error fetching data: ', err) + }) ``` -While the above snippet produces a valid GraphQL query, **it has many drawbacks**: - -- it makes it **harder to understand** the query as a whole -- developers are **responsible for safely sanitizing the string interpolation** -- not sending the values of the variables as part of the request parameters **prevent possible caching on server-side** -- it **prevents tools from statically analyzing the query** (ex: Linter, or type generations tools) - -For this reason, it is recommended to always write queries as static strings: - -```tsx -import { execute } from "your-favorite-graphql-client" +변수를 사용하기 위해서, 쿼리에 `variables` 인수를 전달할 수 있습니다. -const id = params.id -const query = ` -query GetToken($id: ID!) { - token(id: $id) { - id - owner - } -} -` - -const result = await execute( - query, - { - variables: { +```javascript +const tokensQuery = ` + query($first: Int, $orderBy: BigInt, $orderDirection: String) { + tokens( + first: $first, orderBy: $orderBy, orderDirection: $orderDirection + ) { id + tokenID + contentURI + metadataURI } } -) -``` - -Doing so brings **many advantages**: - -- **Easy to read and maintain** queries -- The GraphQL **server handles variables sanitization** -- **Variables can be cached** at server-level -- **Queries can be statically analyzed by tools** (more on this in the following sections) - -**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: - -```tsx -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, - { +client + .query({ + query: gql(tokensQuery), variables: { - id, - includeOwner: true - } - } -) + first: 10, + orderBy: 'createdAtTimestamp', + orderDirection: 'desc', + }, + }) + .then((data) => console.log('Subgraph data: ', data)) + .catch((err) => { + console.log('Error fetching data: ', err) + }) ``` -Note: The opposite directive is `@skip(if: ...)`. - -
+### URQL -### Performance tips +또 다른 옵션은 약간 더 가벼운 GraphQL 클라이언트 라이브러리인 [URQL](https://formidable.com/open-source/urql/)입니다. -**"Ask for what you want"** +웹 프로젝트에서 URQL을 사용하여 서브그래프에서 데이터를 가져오는 방법을 살펴보겠습니다. -GraphQL became famous for its “Ask for what you want” tagline. +우선, `urql` 및 `graphql`을 설치합니다: -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. - -Since some fields might be computed or proxied on the server-side, fetching fewer fields can make a big difference! - -```graphql -query getProtocols { - protocols(first: 5) { - id - inflation - inflationChange - maxEarningsClaimsRounds - numActiveTranscoders - } -} +```sh +npm install urql graphql ``` -`numActiveTranscoders` might be slower (computed) field to resolve. - -If not needed, removing it will improve the overall query performance (*especially in a list context:* `list: 5`). +그러면 여러분들은 다음의 코드로 API를 쿼리할 수 있습니다: -**Combining multiple queries** +```javascript +import { createClient } from 'urql' -Your application might require querying multiple types of data as follows: - -```graphql -import { execute } from "your-favorite-graphql-client" +const APIURL = 'https://api.thegraph.com/subgraphs/name/username/subgraphname' 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: - -```graphql -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**. - -
- -### Leverage GraphQL Fragments - -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 (`{ ... }`): - -```graphql -query { - bondEvents { - id - newDelegate { - id - active - status - } - oldDelegate { + query { + tokens { id - active - status - } - } -} -``` - -Such repeated fields (`id`, `active`, `status`) bring many issues: - -- harder to read for more extensive queries -- 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: - -```graphql -query { - bondEvents { - id - newDelegate { - ...DelegateItem - } - oldDelegate { - ...DelegateItem + tokenID + contentURI + metadataURI } } -} - -# 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*). - -
- -### GraphQL Fragment do's and don'ts - -**Fragment base must be a type** - -A Fragment cannot be based on a non-applicable type, in short, **on type not having fields**: - -```graphql -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. - -Example: - -```graphql -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: - -- when fields of the same type are repeated in a query, group them in a Fragment -- when similar but not the same fields are repeated, create multiple fragments, ex: - -```graphql -# 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 - } -} -``` - ---- - -## The essential tools - -### GraphQL Linting - -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](https://github.com/dotansimha/graphql-eslint) will help you stay on top of GraphQL best practices with zero effort. - -[Setup the "operations-recommended"](https://github.com/dotansimha/graphql-eslint#available-configs) config will enforce essential rules such as: - -- `@graphql-eslint/fields-on-correct-type`: is a field used on a proper type? -- `@graphql-eslint/no-unused variables`: should a given variable stay unused? -- and more! - -This will allow you to **catch errors without even testing queries** on the playground or running them in production! - -
- -### IDE plugins - -**VSCode and GraphQL** - -The [GraphQL VSCode extension](https://marketplace.visualstudio.com/items?itemName=GraphQL.vscode-graphql) 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](https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint) is a must-have to visualize errors and warnings inlined in your code correctly. - - - -**WebStorm/Intellij and GraphQL** - -The [JS GraphQL plugin](https://plugins.jetbrains.com/plugin/8097-graphql/) will significantly improve your experience while working with GraphQL by providing: - -- syntax highlighting -- autocomplete suggestions -- validation against schema -- snippets - -More information on this [WebStorm article](https://blog.jetbrains.com/webstorm/2019/04/featured-plugin-js-graphql/) that showcases all the plugin's main features. - -
- -### TypeScript types generation - -Finally, the best GraphQL experience is reached when the TypeScript data types are generated from the defined GraphQL queries, as follows: - -```tsx -import { execute } from "your-favorite-graphql-client" -import { GetTokenQuery } from "./generated." - -const id = params.id -const query = ` -query GetToken($id: ID!) { - token(id: $id) { - id - owner - } -} ` -const result: GetTokenQuery = await execute( - query, - { - variables: { - id - } - } -) - -// `result` is typed! -``` - -Such a setup can be easily achieved by installing and configuring [GraphQL Code Generator](https://www.graphql-code-generator.com/docs/getting-started) as follows: - -```bash -yarn add graphql @graphql-codegen/cli @graphql-codegen/typescript @graphql-codegen/typescript-operations -``` - -Then update your `package.json` (or similar script configuration setup) as follows: - -```tsx -{ - // ... - "scripts": { - // ... - "generate": "graphql-codegen", - // ... - } - // ... -} -``` - -Add the following configuration file to your project: +const client = createClient({ + url: APIURL, +}) -```tsx -schema: "" -documents: './src/**/*.ts' -generates: - ./generated.ts: - plugins: - - typescript - - typescript-operations +const data = await client.query(tokensQuery).toPromise() ``` - -Finally, by simply running the configured script (`yarn generate`), GraphQL Code Generator will generate the proper TypeScript types in the `generated.ts` file: - -- Each query will get a corresponding `[QueryName]Query` type (ex: `GetToken` → `GetTokenQuery` type -- Each fragment will get a corresponding `[FragmentName]Fragment` type (ex: `DelegateItem` → `DelegateItemFragment` type diff --git a/pages/vi/developer/querying-from-your-app.mdx b/pages/vi/developer/querying-from-your-app.mdx index c575b4b809db..8cd9e23757df 100644 --- a/pages/vi/developer/querying-from-your-app.mdx +++ b/pages/vi/developer/querying-from-your-app.mdx @@ -1,573 +1,136 @@ --- -title: Querying from an Application +title: Truy vấn từ một ứng dụng --- -The Graph provides a decentralized way to query data from blockchains. +Sau khi một subgraph được triển khai tới Subgraph Studio hoặc tới Graph Explorer, bạn sẽ được cung cấp điểm cuối cho API GraphQL của mình trông giống như sau: -The Graph network's data is exposed through a GraphQL API, making it easier to query data with the GraphQL language. +**Subgraph Studio (điểm cuối thử nghiệm)** -This page will guide you through the essential GraphQL language rules and GraphQL queries best practices. - ---- - -## Querying a GraphQL API - -### The anatomy of a GraphQL query - -Unlike REST API, a GraphQL API is built upon a Schema that defines which queries can be performed. - -For example, a query to get a token using the `token` query will look as follows: - -```graphql -query GetToken($id: ID!) { - token(id: $id) { - id - owner - } -} +```sh +Queries (HTTP) +https://api.studio.thegraph.com/query/// ``` -which will return the following predictable JSON response (*when passing the proper `$id` variable value*): +**Trình khám phá Graph** -```json -{ - "token": { - "id": "...", - "owner": "..." - }, -} +```sh +Queries (HTTP) +https://gateway.thegraph.com/api//subgraphs/id/ ``` -GraphQL queries use the GraphQL language, which is defined upon [a specification](https://spec.graphql.org/). - -The above `GetToken` query is composed of multiple language parts (replaced below with `[...]` placeholders): +Sử dụng điểm cuối GraphQL, bạn có thể sử dụng các thư viện GraphQL Client khác nhau để truy vấn subgraph và điền vào ứng dụng của bạn với dữ liệu được lập chỉ mục bởi subgraph. -```graphql -query [operationName]([variableName]: [variableType]) { - [queryName]([argumentName]: [variableName]) { - # "{ ... }" express a Selection-Set, we are querying fields from `queryName`. - [field] - [field] - } -} -``` +Dưới đây là một số ứng dụng khách GraphQL phổ biến hơn trong hệ sinh thái và cách sử dụng chúng: -While the list of syntactic do's and don'ts is long, here are the essential rules to keep in mind when it comes to writing GraphQL queries: +### Apollo client -- Each `queryName` must only be used once per operation. -- Each `field` must be used only once in a selection (we cannot query `id` twice under `token`) -- Some `field`s 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 [The Graph Explorer](https://thegraph.com/docs/en/explorer/). -- Any variable assigned to an argument must match its type. -- In a given list of variables, each of them must be unique. -- All defined variables must be used. +[Apollo client](https://www.apollographql.com/docs/) hỗ trợ các dự án web bao gồm các khuôn khổ như React và Vue, cũng như các ứng dụng khách di động như iOS, Android và React Native. -Failing to follow the above rules will end with an error from the Graph API. +Hãy xem cách tìm nạp dữ liệu từ một subgraph với Apollo client trong một dự án web. -For a complete list of rules with code examples, please look at our GraphQL Validations guide. +Đầu tiên, cài đặt `@apollo/client` và `graphql`: -
+```sh +npm install @apollo/client graphql +``` -### Sending a query to a GraphQL API +Sau đó, bạn có thể truy vấn API bằng mã sau: -GraphQL is a language and set of conventions that transport over HTTP. +```javascript +import { ApolloClient, InMemoryCache, gql } from '@apollo/client' -It means that you can query a GraphQL API using standard `fetch` (natively or via `cross-undici-fetch` or `isomorphic-fetch`) as follows: +const APIURL = 'https://api.studio.thegraph.com/query///' -```tsx -const query = ` -query GetToken($id: ID!) { - token(id: $id) { - id - owner +const tokensQuery = ` + query { + tokens { + id + tokenID + contentURI + metadataURI + } } -} ` -const variables = { id: "1" } -const fetchResult = await fetch('http://example.com/graphql', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ query, variables }) +const client = new ApolloClient({ + uri: APIURL, + cache: new InMemoryCache(), }) -const result = await fetchResult.json() -``` - -Another lightweight alternative, best for back-end use-cases, is the famous [`graphql-request` library](https://github.com/prisma-labs/graphql-request). - -This lightweight GraphQL client comes with the essential features to query a GraphQL API: - -- Mutations validation -- Support for file upload -- Batching -- Promise-based API -- TypeScript support - -
- -A complete GraphQL client is [URQL](https://formidable.com/open-source/urql/) which is available within Node.js, React/Preact, Vue, Svelte environments, with more advanced features: - -- Flexible cache system -- Extensible design (easing adding new capabilities on top of it) -- Lightweight bundle (~5x lighter than Apollo Client) -- Support for file uploads and offline mode -
- -In the React ecosystem, [React Query](https://react-query.tanstack.com/graphql) brings a lightweight and agnostic solution for GraphQL. - -React Query is a great candidate if you are looking for an easy-to-use and lightweight multipurpose client (GraphQL-capable) that provides essential features such as: - -- Powerful cache (background refresh, window-focus refreshing) -- Advanced querying pattern (parallel queries, dependent queries, prefetching) -- UX patterns: optimistic updates, scroll restoration -- Great dev tools - -
- -Finally, Apollo Client is the ubiquitous GraphQL client on the front-end ecosystem. - -Available for React, Angular, Vue, Ember, iOS, and Android, Apollo Client, although the heaviest clients, brings many features to build advanced UI on-top of GraphQL: - -- advanced error handling -- pagination -- data prefetching -- optimistic UI -- local state management - -
- -Now that we covered the basic rules of GraphQL queries syntax, let's now look at the best practices of GraphQL query writing. - ---- - -## Writing GraphQL queries - -### Always write static queries - -A common (bad) practice is to dynamically build query strings as follows: - -```tsx -const id = params.id -const fields = ["id", "owner"] -const query = ` -query GetToken { - token(id: ${id}) { - ${fields.join("\n")} - } -} -` - -// Execute query... +client + .query({ + query: gql(tokensQuery), + }) + .then((data) => console.log('Subgraph data: ', data)) + .catch((err) => { + console.log('Error fetching data: ', err) + }) ``` -While the above snippet produces a valid GraphQL query, **it has many drawbacks**: - -- it makes it **harder to understand** the query as a whole -- developers are **responsible for safely sanitizing the string interpolation** -- not sending the values of the variables as part of the request parameters **prevent possible caching on server-side** -- it **prevents tools from statically analyzing the query** (ex: Linter, or type generations tools) - -For this reason, it is recommended to always write queries as static strings: - -```tsx -import { execute } from "your-favorite-graphql-client" +Để sử dụng biến, bạn có thể bỏ `biến` vào truy vấn: -const id = params.id -const query = ` -query GetToken($id: ID!) { - token(id: $id) { - id - owner - } -} -` - -const result = await execute( - query, - { - variables: { +```javascript +const tokensQuery = ` + query($first: Int, $orderBy: BigInt, $orderDirection: String) { + tokens( + first: $first, orderBy: $orderBy, orderDirection: $orderDirection + ) { id + tokenID + contentURI + metadataURI } } -) -``` - -Doing so brings **many advantages**: - -- **Easy to read and maintain** queries -- The GraphQL **server handles variables sanitization** -- **Variables can be cached** at server-level -- **Queries can be statically analyzed by tools** (more on this in the following sections) - -**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: - -```tsx -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, - { +client + .query({ + query: gql(tokensQuery), variables: { - id, - includeOwner: true - } - } -) + first: 10, + orderBy: 'createdAtTimestamp', + orderDirection: 'desc', + }, + }) + .then((data) => console.log('Subgraph data: ', data)) + .catch((err) => { + console.log('Error fetching data: ', err) + }) ``` -Note: The opposite directive is `@skip(if: ...)`. - -
+### URQL -### Performance tips +Một tùy chọn khác là [URQL](https://formidable.com/open-source/urql/),một thư viện khách hàng GraphQL có trọng lượng nhẹ hơn một chút. -**"Ask for what you want"** +Hãy xem cách tìm nạp dữ liệu từ một subgraph với URQL trong một dự án web. -GraphQL became famous for its “Ask for what you want” tagline. +Đầu tiên, cài đặt `urql` và `graphql`: -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. - -Since some fields might be computed or proxied on the server-side, fetching fewer fields can make a big difference! - -```graphql -query getProtocols { - protocols(first: 5) { - id - inflation - inflationChange - maxEarningsClaimsRounds - numActiveTranscoders - } -} +```sh +npm install urql graphql ``` -`numActiveTranscoders` might be slower (computed) field to resolve. - -If not needed, removing it will improve the overall query performance (*especially in a list context:* `list: 5`). +Sau đó, bạn có thể truy vấn API bằng mã sau: -**Combining multiple queries** +```javascript +import { createClient } from 'urql' -Your application might require querying multiple types of data as follows: - -```graphql -import { execute } from "your-favorite-graphql-client" +const APIURL = 'https://api.thegraph.com/subgraphs/name/username/subgraphname' 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: - -```graphql -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**. - -
- -### Leverage GraphQL Fragments - -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 (`{ ... }`): - -```graphql -query { - bondEvents { - id - newDelegate { - id - active - status - } - oldDelegate { + query { + tokens { id - active - status - } - } -} -``` - -Such repeated fields (`id`, `active`, `status`) bring many issues: - -- harder to read for more extensive queries -- 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: - -```graphql -query { - bondEvents { - id - newDelegate { - ...DelegateItem - } - oldDelegate { - ...DelegateItem + tokenID + contentURI + metadataURI } } -} - -# 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*). - -
- -### GraphQL Fragment do's and don'ts - -**Fragment base must be a type** - -A Fragment cannot be based on a non-applicable type, in short, **on type not having fields**: - -```graphql -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. - -Example: - -```graphql -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: - -- when fields of the same type are repeated in a query, group them in a Fragment -- when similar but not the same fields are repeated, create multiple fragments, ex: - -```graphql -# 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 - } -} -``` - ---- - -## The essential tools - -### GraphQL Linting - -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](https://github.com/dotansimha/graphql-eslint) will help you stay on top of GraphQL best practices with zero effort. - -[Setup the "operations-recommended"](https://github.com/dotansimha/graphql-eslint#available-configs) config will enforce essential rules such as: - -- `@graphql-eslint/fields-on-correct-type`: is a field used on a proper type? -- `@graphql-eslint/no-unused variables`: should a given variable stay unused? -- and more! - -This will allow you to **catch errors without even testing queries** on the playground or running them in production! - -
- -### IDE plugins - -**VSCode and GraphQL** - -The [GraphQL VSCode extension](https://marketplace.visualstudio.com/items?itemName=GraphQL.vscode-graphql) 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](https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint) is a must-have to visualize errors and warnings inlined in your code correctly. - - - -**WebStorm/Intellij and GraphQL** - -The [JS GraphQL plugin](https://plugins.jetbrains.com/plugin/8097-graphql/) will significantly improve your experience while working with GraphQL by providing: - -- syntax highlighting -- autocomplete suggestions -- validation against schema -- snippets - -More information on this [WebStorm article](https://blog.jetbrains.com/webstorm/2019/04/featured-plugin-js-graphql/) that showcases all the plugin's main features. - -
- -### TypeScript types generation - -Finally, the best GraphQL experience is reached when the TypeScript data types are generated from the defined GraphQL queries, as follows: - -```tsx -import { execute } from "your-favorite-graphql-client" -import { GetTokenQuery } from "./generated." - -const id = params.id -const query = ` -query GetToken($id: ID!) { - token(id: $id) { - id - owner - } -} ` -const result: GetTokenQuery = await execute( - query, - { - variables: { - id - } - } -) - -// `result` is typed! -``` - -Such a setup can be easily achieved by installing and configuring [GraphQL Code Generator](https://www.graphql-code-generator.com/docs/getting-started) as follows: - -```bash -yarn add graphql @graphql-codegen/cli @graphql-codegen/typescript @graphql-codegen/typescript-operations -``` - -Then update your `package.json` (or similar script configuration setup) as follows: - -```tsx -{ - // ... - "scripts": { - // ... - "generate": "graphql-codegen", - // ... - } - // ... -} -``` - -Add the following configuration file to your project: +const client = createClient({ + url: APIURL, +}) -```tsx -schema: "" -documents: './src/**/*.ts' -generates: - ./generated.ts: - plugins: - - typescript - - typescript-operations +const data = await client.query(tokensQuery).toPromise() ``` - -Finally, by simply running the configured script (`yarn generate`), GraphQL Code Generator will generate the proper TypeScript types in the `generated.ts` file: - -- Each query will get a corresponding `[QueryName]Query` type (ex: `GetToken` → `GetTokenQuery` type -- Each fragment will get a corresponding `[FragmentName]Fragment` type (ex: `DelegateItem` → `DelegateItemFragment` type diff --git a/pages/zh/developer/querying-from-your-app.mdx b/pages/zh/developer/querying-from-your-app.mdx index c575b4b809db..0cd65bcc84c8 100644 --- a/pages/zh/developer/querying-from-your-app.mdx +++ b/pages/zh/developer/querying-from-your-app.mdx @@ -1,573 +1,136 @@ --- -title: Querying from an Application +title: 从应用程序中进行查询 --- -The Graph provides a decentralized way to query data from blockchains. +一旦子图被部署到子图工作室或 Graph Explorer,你将得到 GraphQL API 的端点,它应该看起来像这样。 -The Graph network's data is exposed through a GraphQL API, making it easier to query data with the GraphQL language. +**子图工作室(测试端点)** -This page will guide you through the essential GraphQL language rules and GraphQL queries best practices. - ---- - -## Querying a GraphQL API - -### The anatomy of a GraphQL query - -Unlike REST API, a GraphQL API is built upon a Schema that defines which queries can be performed. - -For example, a query to get a token using the `token` query will look as follows: - -```graphql -query GetToken($id: ID!) { - token(id: $id) { - id - owner - } -} +```sh +Queries (HTTP) +https://api.studio.thegraph.com/query/// ``` -which will return the following predictable JSON response (*when passing the proper `$id` variable value*): +**Graph 浏览器** -```json -{ - "token": { - "id": "...", - "owner": "..." - }, -} +```sh +Queries (HTTP) +https://gateway.thegraph.com/api//subgraphs/id/ ``` -GraphQL queries use the GraphQL language, which is defined upon [a specification](https://spec.graphql.org/). - -The above `GetToken` query is composed of multiple language parts (replaced below with `[...]` placeholders): +使用 GraphQL 端点,你可以使用各种 GraphQL 客户端库来查询子图,并用子图索引的数据来填充你的应用程序。 -```graphql -query [operationName]([variableName]: [variableType]) { - [queryName]([argumentName]: [variableName]) { - # "{ ... }" express a Selection-Set, we are querying fields from `queryName`. - [field] - [field] - } -} -``` +下面是生态系统中几个比较流行的 GraphQL 客户端以及如何使用它们。 -While the list of syntactic do's and don'ts is long, here are the essential rules to keep in mind when it comes to writing GraphQL queries: +### Apollo 客户端 -- Each `queryName` must only be used once per operation. -- Each `field` must be used only once in a selection (we cannot query `id` twice under `token`) -- Some `field`s 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 [The Graph Explorer](https://thegraph.com/docs/en/explorer/). -- Any variable assigned to an argument must match its type. -- In a given list of variables, each of them must be unique. -- All defined variables must be used. +[Apollo 客户端](https://www.apollographql.com/docs/) 支持网络项目,包括 React 和 Vue 等框架,以及 iOS、Android 和 React Native 等移动客户端。 -Failing to follow the above rules will end with an error from the Graph API. +让我们看看如何在一个网络项目中用 Apollo 客户端从子图中获取数据。 -For a complete list of rules with code examples, please look at our GraphQL Validations guide. +首先,安装`@apollo/client` 和`graphql`: -
+```sh +npm install @apollo/client graphql +``` -### Sending a query to a GraphQL API +然后你可以用以下代码查询 API: -GraphQL is a language and set of conventions that transport over HTTP. +```javascript +import { ApolloClient, InMemoryCache, gql } from '@apollo/client' -It means that you can query a GraphQL API using standard `fetch` (natively or via `cross-undici-fetch` or `isomorphic-fetch`) as follows: +const APIURL = 'https://api.studio.thegraph.com/query///' -```tsx -const query = ` -query GetToken($id: ID!) { - token(id: $id) { - id - owner +const tokensQuery = ` + query { + tokens { + id + tokenID + contentURI + metadataURI + } } -} ` -const variables = { id: "1" } -const fetchResult = await fetch('http://example.com/graphql', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ query, variables }) +const client = new ApolloClient({ + uri: APIURL, + cache: new InMemoryCache(), }) -const result = await fetchResult.json() -``` - -Another lightweight alternative, best for back-end use-cases, is the famous [`graphql-request` library](https://github.com/prisma-labs/graphql-request). - -This lightweight GraphQL client comes with the essential features to query a GraphQL API: - -- Mutations validation -- Support for file upload -- Batching -- Promise-based API -- TypeScript support - -
- -A complete GraphQL client is [URQL](https://formidable.com/open-source/urql/) which is available within Node.js, React/Preact, Vue, Svelte environments, with more advanced features: - -- Flexible cache system -- Extensible design (easing adding new capabilities on top of it) -- Lightweight bundle (~5x lighter than Apollo Client) -- Support for file uploads and offline mode -
- -In the React ecosystem, [React Query](https://react-query.tanstack.com/graphql) brings a lightweight and agnostic solution for GraphQL. - -React Query is a great candidate if you are looking for an easy-to-use and lightweight multipurpose client (GraphQL-capable) that provides essential features such as: - -- Powerful cache (background refresh, window-focus refreshing) -- Advanced querying pattern (parallel queries, dependent queries, prefetching) -- UX patterns: optimistic updates, scroll restoration -- Great dev tools - -
- -Finally, Apollo Client is the ubiquitous GraphQL client on the front-end ecosystem. - -Available for React, Angular, Vue, Ember, iOS, and Android, Apollo Client, although the heaviest clients, brings many features to build advanced UI on-top of GraphQL: - -- advanced error handling -- pagination -- data prefetching -- optimistic UI -- local state management - -
- -Now that we covered the basic rules of GraphQL queries syntax, let's now look at the best practices of GraphQL query writing. - ---- - -## Writing GraphQL queries - -### Always write static queries - -A common (bad) practice is to dynamically build query strings as follows: - -```tsx -const id = params.id -const fields = ["id", "owner"] -const query = ` -query GetToken { - token(id: ${id}) { - ${fields.join("\n")} - } -} -` - -// Execute query... +client + .query({ + query: gql(tokensQuery), + }) + .then((data) => console.log('Subgraph data: ', data)) + .catch((err) => { + console.log('Error fetching data: ', err) + }) ``` -While the above snippet produces a valid GraphQL query, **it has many drawbacks**: - -- it makes it **harder to understand** the query as a whole -- developers are **responsible for safely sanitizing the string interpolation** -- not sending the values of the variables as part of the request parameters **prevent possible caching on server-side** -- it **prevents tools from statically analyzing the query** (ex: Linter, or type generations tools) - -For this reason, it is recommended to always write queries as static strings: - -```tsx -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: { +```javascript +const tokensQuery = ` + query($first: Int, $orderBy: BigInt, $orderDirection: String) { + tokens( + first: $first, orderBy: $orderBy, orderDirection: $orderDirection + ) { id + tokenID + contentURI + metadataURI } } -) -``` - -Doing so brings **many advantages**: - -- **Easy to read and maintain** queries -- The GraphQL **server handles variables sanitization** -- **Variables can be cached** at server-level -- **Queries can be statically analyzed by tools** (more on this in the following sections) - -**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: - -```tsx -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, - { +client + .query({ + query: gql(tokensQuery), variables: { - id, - includeOwner: true - } - } -) + first: 10, + orderBy: 'createdAtTimestamp', + orderDirection: 'desc', + }, + }) + .then((data) => console.log('Subgraph data: ', data)) + .catch((err) => { + console.log('Error fetching data: ', err) + }) ``` -Note: The opposite directive is `@skip(if: ...)`. - -
+### URQL -### Performance tips +另一个选择是[URQL](https://formidable.com/open-source/urql/),一个有点轻量级的 GraphQL 客户端库。 -**"Ask for what you want"** +让我们看看如何在一个网络项目中用 URQL 从子图中获取数据。 -GraphQL became famous for its “Ask for what you want” tagline. +首先,安装`urql`和 `graphql`: -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. - -Since some fields might be computed or proxied on the server-side, fetching fewer fields can make a big difference! - -```graphql -query getProtocols { - protocols(first: 5) { - id - inflation - inflationChange - maxEarningsClaimsRounds - numActiveTranscoders - } -} +```sh +npm install urql graphql ``` -`numActiveTranscoders` might be slower (computed) field to resolve. - -If not needed, removing it will improve the overall query performance (*especially in a list context:* `list: 5`). +然后你可以用以下代码查询 API: -**Combining multiple queries** +```javascript +import { createClient } from 'urql' -Your application might require querying multiple types of data as follows: - -```graphql -import { execute } from "your-favorite-graphql-client" +const APIURL = 'https://api.thegraph.com/subgraphs/name/username/subgraphname' 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: - -```graphql -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**. - -
- -### Leverage GraphQL Fragments - -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 (`{ ... }`): - -```graphql -query { - bondEvents { - id - newDelegate { - id - active - status - } - oldDelegate { + query { + tokens { id - active - status - } - } -} -``` - -Such repeated fields (`id`, `active`, `status`) bring many issues: - -- harder to read for more extensive queries -- 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: - -```graphql -query { - bondEvents { - id - newDelegate { - ...DelegateItem - } - oldDelegate { - ...DelegateItem + tokenID + contentURI + metadataURI } } -} - -# 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*). - -
- -### GraphQL Fragment do's and don'ts - -**Fragment base must be a type** - -A Fragment cannot be based on a non-applicable type, in short, **on type not having fields**: - -```graphql -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. - -Example: - -```graphql -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: - -- when fields of the same type are repeated in a query, group them in a Fragment -- when similar but not the same fields are repeated, create multiple fragments, ex: - -```graphql -# 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 - } -} -``` - ---- - -## The essential tools - -### GraphQL Linting - -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](https://github.com/dotansimha/graphql-eslint) will help you stay on top of GraphQL best practices with zero effort. - -[Setup the "operations-recommended"](https://github.com/dotansimha/graphql-eslint#available-configs) config will enforce essential rules such as: - -- `@graphql-eslint/fields-on-correct-type`: is a field used on a proper type? -- `@graphql-eslint/no-unused variables`: should a given variable stay unused? -- and more! - -This will allow you to **catch errors without even testing queries** on the playground or running them in production! - -
- -### IDE plugins - -**VSCode and GraphQL** - -The [GraphQL VSCode extension](https://marketplace.visualstudio.com/items?itemName=GraphQL.vscode-graphql) 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](https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint) is a must-have to visualize errors and warnings inlined in your code correctly. - - - -**WebStorm/Intellij and GraphQL** - -The [JS GraphQL plugin](https://plugins.jetbrains.com/plugin/8097-graphql/) will significantly improve your experience while working with GraphQL by providing: - -- syntax highlighting -- autocomplete suggestions -- validation against schema -- snippets - -More information on this [WebStorm article](https://blog.jetbrains.com/webstorm/2019/04/featured-plugin-js-graphql/) that showcases all the plugin's main features. - -
- -### TypeScript types generation - -Finally, the best GraphQL experience is reached when the TypeScript data types are generated from the defined GraphQL queries, as follows: - -```tsx -import { execute } from "your-favorite-graphql-client" -import { GetTokenQuery } from "./generated." - -const id = params.id -const query = ` -query GetToken($id: ID!) { - token(id: $id) { - id - owner - } -} ` -const result: GetTokenQuery = await execute( - query, - { - variables: { - id - } - } -) - -// `result` is typed! -``` - -Such a setup can be easily achieved by installing and configuring [GraphQL Code Generator](https://www.graphql-code-generator.com/docs/getting-started) as follows: - -```bash -yarn add graphql @graphql-codegen/cli @graphql-codegen/typescript @graphql-codegen/typescript-operations -``` - -Then update your `package.json` (or similar script configuration setup) as follows: - -```tsx -{ - // ... - "scripts": { - // ... - "generate": "graphql-codegen", - // ... - } - // ... -} -``` - -Add the following configuration file to your project: +const client = createClient({ + url: APIURL, +}) -```tsx -schema: "" -documents: './src/**/*.ts' -generates: - ./generated.ts: - plugins: - - typescript - - typescript-operations +const data = await client.query(tokensQuery).toPromise() ``` - -Finally, by simply running the configured script (`yarn generate`), GraphQL Code Generator will generate the proper TypeScript types in the `generated.ts` file: - -- Each query will get a corresponding `[QueryName]Query` type (ex: `GetToken` → `GetTokenQuery` type -- Each fragment will get a corresponding `[FragmentName]Fragment` type (ex: `DelegateItem` → `DelegateItemFragment` type From 826b7ca663307951506fd49565c9238e19a6b422 Mon Sep 17 00:00:00 2001 From: Charly POLY Date: Wed, 16 Mar 2022 15:31:29 +0100 Subject: [PATCH 5/6] doc(developer): move new guide on GraphQL Query best practices out of the nav --- .../en/developer/querying-best-practices.mdx | 584 ++++++++++++++++++ 1 file changed, 584 insertions(+) create mode 100644 pages/en/developer/querying-best-practices.mdx diff --git a/pages/en/developer/querying-best-practices.mdx b/pages/en/developer/querying-best-practices.mdx new file mode 100644 index 000000000000..a3ff3b24151a --- /dev/null +++ b/pages/en/developer/querying-best-practices.mdx @@ -0,0 +1,584 @@ +--- +title: Querying best practices +--- + +The Graph provides a decentralized way to query data from blockchains. + +The Graph network's data is exposed through a GraphQL API, making it easier to query data with the GraphQL language. + +This page will guide you through the essential GraphQL language rules and GraphQL queries best practices. + +--- + +## Querying a GraphQL API + +### The anatomy of a GraphQL query + +Unlike REST API, a GraphQL API is built upon a Schema that defines which queries can be performed. + +For example, a query to get a token using the `token` query will look as follows: + +```graphql +query GetToken($id: ID!) { + token(id: $id) { + id + owner + } +} +``` + +which will return the following predictable JSON response (*when passing the proper `$id` variable value*): + +```json +{ + "token": { + "id": "...", + "owner": "..." + }, +} +``` + +GraphQL queries use the GraphQL language, which is defined upon [a specification](https://spec.graphql.org/). + +The above `GetToken` query is composed of multiple language parts (replaced below with `[...]` placeholders): + +```graphql +query [operationName]([variableName]: [variableType]) { + [queryName]([argumentName]: [variableName]) { + # "{ ... }" express a Selection-Set, we are querying fields from `queryName`. + [field] + [field] + } +} +``` + +While the list of syntactic do's and don'ts is long, here are the essential rules to keep in mind when it comes to writing GraphQL queries: + +- Each `queryName` must only be used once per operation. +- Each `field` must be used only once in a selection (we cannot query `id` twice under `token`) +- Some `field`s 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 [The Graph Explorer](https://thegraph.com/docs/en/explorer/). +- Any variable assigned to an argument must match its type. +- In a given list of variables, each of them must be unique. +- All defined variables must be used. + +Failing to follow the above rules will end with an error from the Graph API. + +For a complete list of rules with code examples, please look at our GraphQL Validations guide. + +
+ +### Sending a query to a GraphQL API + +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 `cross-undici-fetch` or `isomorphic-fetch`) as follows: + +```tsx +const query = ` +query GetToken($id: ID!) { + token(id: $id) { + id + owner + } +} +` +const variables = { id: "1" } + +const fetchResult = await fetch('http://example.com/graphql', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ query, variables }) +}) +const result = await fetchResult.json() +``` + +Another lightweight alternative, best for back-end use-cases, is the famous [`graphql-request` library](https://github.com/prisma-labs/graphql-request). + +This lightweight GraphQL client comes with the essential features to query a GraphQL API: + +- Mutations validation +- Support for file upload +- Batching +- Promise-based API +- TypeScript support + +
+ +A complete GraphQL client is [URQL](https://formidable.com/open-source/urql/) which is available within Node.js, React/Preact, Vue, Svelte environments, with more advanced features: + +- Flexible cache system +- Extensible design (easing adding new capabilities on top of it) +- Lightweight bundle (~5x lighter than Apollo Client) +- Support for file uploads and offline mode + +
+ +In the React ecosystem, [React Query](https://react-query.tanstack.com/graphql) brings a lightweight and agnostic solution for GraphQL. + +React Query is a great candidate if you are looking for an easy-to-use and lightweight multipurpose client (GraphQL-capable) that provides essential features such as: + +- Powerful cache (background refresh, window-focus refreshing) +- Advanced querying pattern (parallel queries, dependent queries, prefetching) +- UX patterns: optimistic updates, scroll restoration +- Great dev tools + +
+ +Finally, Apollo Client is the ubiquitous GraphQL client on the front-end ecosystem. + +Available for React, Angular, Vue, Ember, iOS, and Android, Apollo Client, although the heaviest clients, brings many features to build advanced UI on-top of GraphQL: + +- advanced error handling +- pagination +- data prefetching +- optimistic UI +- local state management + +
+ +Now that we covered the basic rules of GraphQL queries syntax, let's now look at the best practices of GraphQL query writing. + +--- + +## Writing GraphQL queries + +### Always write static queries + +A common (bad) practice is to dynamically build query strings as follows: + +```tsx +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**: + +- it makes it **harder to understand** the query as a whole +- developers are **responsible for safely sanitizing the string interpolation** +- not sending the values of the variables as part of the request parameters **prevent possible caching on server-side** +- it **prevents tools from statically analyzing the query** (ex: Linter, or type generations tools) + +For this reason, it is recommended to always write queries as static strings: + +```tsx +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**: + +- **Easy to read and maintain** queries +- The GraphQL **server handles variables sanitization** +- **Variables can be cached** at server-level +- **Queries can be statically analyzed by tools** (more on this in the following sections) + +**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: + +```tsx +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: ...)`. + +
+ +### Performance tips + +**"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: + +```graphql +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: + +```graphql +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: + +```graphql +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**. + +
+ +### Leverage GraphQL Fragments + +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 (`{ ... }`): + +```graphql +query { + bondEvents { + id + newDelegate { + id + active + status + } + oldDelegate { + id + active + status + } + } +} +``` + +Such repeated fields (`id`, `active`, `status`) bring many issues: + +- harder to read for more extensive queries +- 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: + +```graphql +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*). + +
+ +### GraphQL Fragment do's and don'ts + +**Fragment base must be a type** + +A Fragment cannot be based on a non-applicable type, in short, **on type not having fields**: + +```graphql +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. + +Example: + +```graphql +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: + +- when fields of the same type are repeated in a query, group them in a Fragment +- when similar but not the same fields are repeated, create multiple fragments, ex: + +```graphql +# 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 + } +} +``` + +--- + +## The essential tools + +### GraphQL web-based explorers + +Iterating over queries by running them in your application can be cumbersome. +For this reason, don't hesitate to use [The Graph Explorer](https://thegraph.com/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](https://altair.sirmuel.design/) and [GraphiQL](https://graphiql-online.com/graphiql). + +
+ +### GraphQL Linting + +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](https://github.com/dotansimha/graphql-eslint) will help you stay on top of GraphQL best practices with zero effort. + +[Setup the "operations-recommended"](https://github.com/dotansimha/graphql-eslint#available-configs) config will enforce essential rules such as: + +- `@graphql-eslint/fields-on-correct-type`: is a field used on a proper type? +- `@graphql-eslint/no-unused variables`: should a given variable stay unused? +- and more! + +This will allow you to **catch errors without even testing queries** on the playground or running them in production! + +
+ +### IDE plugins + +**VSCode and GraphQL** + +The [GraphQL VSCode extension](https://marketplace.visualstudio.com/items?itemName=GraphQL.vscode-graphql) 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](https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint) is a must-have to visualize errors and warnings inlined in your code correctly. + + + +**WebStorm/Intellij and GraphQL** + +The [JS GraphQL plugin](https://plugins.jetbrains.com/plugin/8097-graphql/) will significantly improve your experience while working with GraphQL by providing: + +- syntax highlighting +- autocomplete suggestions +- validation against schema +- snippets + +More information on this [WebStorm article](https://blog.jetbrains.com/webstorm/2019/04/featured-plugin-js-graphql/) that showcases all the plugin's main features. + +
+ +### TypeScript types generation + +Finally, the best GraphQL experience is reached when the TypeScript data types are generated from the defined GraphQL queries, as follows: + +```tsx +import { execute } from "your-favorite-graphql-client" +import { GetTokenQuery } from "./generated." + +const id = params.id +const query = ` +query GetToken($id: ID!) { + token(id: $id) { + id + owner + } +} +` + +const result: GetTokenQuery = await execute( + query, + { + variables: { + id + } + } +) + +// `result` is typed! +``` + +Such a setup can be easily achieved by installing and configuring [GraphQL Code Generator](https://www.graphql-code-generator.com/docs/getting-started) as follows: + +```bash +yarn add graphql @graphql-codegen/cli @graphql-codegen/typescript @graphql-codegen/typescript-operations +``` + +Then update your `package.json` (or similar script configuration setup) as follows: + +```tsx +{ + // ... + "scripts": { + // ... + "generate": "graphql-codegen", + // ... + } + // ... +} +``` + +Add the following configuration file to your project: + +```tsx +schema: "" +documents: './src/**/*.ts' +generates: + ./generated.ts: + plugins: + - typescript + - typescript-operations +``` + +Finally, by simply running the configured script (`yarn generate`), GraphQL Code Generator will generate the proper TypeScript types in the `generated.ts` file: + +- Each query will get a corresponding `[QueryName]Query` type (ex: `GetToken` → `GetTokenQuery` type +- Each fragment will get a corresponding `[FragmentName]Fragment` type (ex: `DelegateItem` → `DelegateItemFragment` type From 18533736bb0dafc8e4f4caf3399ce1aac184eba7 Mon Sep 17 00:00:00 2001 From: Charly POLY Date: Wed, 16 Mar 2022 15:33:01 +0100 Subject: [PATCH 6/6] Revert "doc(developer): complete beginner guide to querying a GraphQL API" This reverts commit 5a4cfa2e696989d861f1356ee62123383d486df8. --- pages/en/developer/querying-from-your-app.mdx | 612 +++--------------- 1 file changed, 82 insertions(+), 530 deletions(-) diff --git a/pages/en/developer/querying-from-your-app.mdx b/pages/en/developer/querying-from-your-app.mdx index 75c1d4030992..c09c44efee72 100644 --- a/pages/en/developer/querying-from-your-app.mdx +++ b/pages/en/developer/querying-from-your-app.mdx @@ -2,583 +2,135 @@ title: Querying from an Application --- -The Graph provides a decentralized way to query data from blockchains. +Once a subgraph is deployed to the Subgraph Studio or to the Graph Explorer, you will be given the endpoint for your GraphQL API that should look something like this: -The Graph network's data is exposed through a GraphQL API, making it easier to query data with the GraphQL language. +**Subgraph Studio (testing endpoint)** -This page will guide you through the essential GraphQL language rules and GraphQL queries best practices. - ---- - -## Querying a GraphQL API - -### The anatomy of a GraphQL query - -Unlike REST API, a GraphQL API is built upon a Schema that defines which queries can be performed. - -For example, a query to get a token using the `token` query will look as follows: - -```graphql -query GetToken($id: ID!) { - token(id: $id) { - id - owner - } -} +```sh +Queries (HTTP) +https://api.studio.thegraph.com/query/// ``` -which will return the following predictable JSON response (*when passing the proper `$id` variable value*): +**Graph Explorer** -```json -{ - "token": { - "id": "...", - "owner": "..." - }, -} +```sh +Queries (HTTP) +https://gateway.thegraph.com/api//subgraphs/id/ ``` -GraphQL queries use the GraphQL language, which is defined upon [a specification](https://spec.graphql.org/). - -The above `GetToken` query is composed of multiple language parts (replaced below with `[...]` placeholders): +Using the GraphQL endpoint, you can use various GraphQL Client libraries to query the subgraph and populate your app with the data indexed by the subgraph. -```graphql -query [operationName]([variableName]: [variableType]) { - [queryName]([argumentName]: [variableName]) { - # "{ ... }" express a Selection-Set, we are querying fields from `queryName`. - [field] - [field] - } -} -``` +Here are a couple of the more popular GraphQL clients in the ecosystem and how to use them: -While the list of syntactic do's and don'ts is long, here are the essential rules to keep in mind when it comes to writing GraphQL queries: +### Apollo client -- Each `queryName` must only be used once per operation. -- Each `field` must be used only once in a selection (we cannot query `id` twice under `token`) -- Some `field`s 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 [The Graph Explorer](https://thegraph.com/docs/en/explorer/). -- Any variable assigned to an argument must match its type. -- In a given list of variables, each of them must be unique. -- All defined variables must be used. +[Apollo client](https://www.apollographql.com/docs/) supports web projects including frameworks like React and Vue, as well as mobile clients like iOS, Android, and React Native. -Failing to follow the above rules will end with an error from the Graph API. +Let's look at how fetch data from a subgraph with Apollo client in a web project. -For a complete list of rules with code examples, please look at our GraphQL Validations guide. +First, install `@apollo/client` and `graphql`: -
+```sh +npm install @apollo/client graphql +``` -### Sending a query to a GraphQL API +Then you can query the API with the following code: -GraphQL is a language and set of conventions that transport over HTTP. +```javascript +import { ApolloClient, InMemoryCache, gql } from '@apollo/client' -It means that you can query a GraphQL API using standard `fetch` (natively or via `cross-undici-fetch` or `isomorphic-fetch`) as follows: +const APIURL = 'https://api.studio.thegraph.com/query///' -```tsx -const query = ` -query GetToken($id: ID!) { - token(id: $id) { - id - owner +const tokensQuery = ` + query { + tokens { + id + tokenID + contentURI + metadataURI + } } -} ` -const variables = { id: "1" } -const fetchResult = await fetch('http://example.com/graphql', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ query, variables }) +const client = new ApolloClient({ + uri: APIURL, + cache: new InMemoryCache(), }) -const result = await fetchResult.json() -``` - -Another lightweight alternative, best for back-end use-cases, is the famous [`graphql-request` library](https://github.com/prisma-labs/graphql-request). - -This lightweight GraphQL client comes with the essential features to query a GraphQL API: - -- Mutations validation -- Support for file upload -- Batching -- Promise-based API -- TypeScript support - -
- -A complete GraphQL client is [URQL](https://formidable.com/open-source/urql/) which is available within Node.js, React/Preact, Vue, Svelte environments, with more advanced features: - -- Flexible cache system -- Extensible design (easing adding new capabilities on top of it) -- Lightweight bundle (~5x lighter than Apollo Client) -- Support for file uploads and offline mode - -
-In the React ecosystem, [React Query](https://react-query.tanstack.com/graphql) brings a lightweight and agnostic solution for GraphQL. - -React Query is a great candidate if you are looking for an easy-to-use and lightweight multipurpose client (GraphQL-capable) that provides essential features such as: - -- Powerful cache (background refresh, window-focus refreshing) -- Advanced querying pattern (parallel queries, dependent queries, prefetching) -- UX patterns: optimistic updates, scroll restoration -- Great dev tools - -
- -Finally, Apollo Client is the ubiquitous GraphQL client on the front-end ecosystem. - -Available for React, Angular, Vue, Ember, iOS, and Android, Apollo Client, although the heaviest clients, brings many features to build advanced UI on-top of GraphQL: - -- advanced error handling -- pagination -- data prefetching -- optimistic UI -- local state management - -
- -Now that we covered the basic rules of GraphQL queries syntax, let's now look at the best practices of GraphQL query writing. - ---- - -## Writing GraphQL queries - -### Always write static queries - -A common (bad) practice is to dynamically build query strings as follows: - -```tsx -const id = params.id -const fields = ["id", "owner"] -const query = ` -query GetToken { - token(id: ${id}) { - ${fields.join("\n")} - } -} -` - -// Execute query... +client + .query({ + query: gql(tokensQuery), + }) + .then((data) => console.log('Subgraph data: ', data)) + .catch((err) => { + console.log('Error fetching data: ', err) + }) ``` -While the above snippet produces a valid GraphQL query, **it has many drawbacks**: +To use variables, you can pass in a `variables` argument to the query: -- it makes it **harder to understand** the query as a whole -- developers are **responsible for safely sanitizing the string interpolation** -- not sending the values of the variables as part of the request parameters **prevent possible caching on server-side** -- it **prevents tools from statically analyzing the query** (ex: Linter, or type generations tools) - -For this reason, it is recommended to always write queries as static strings: - -```tsx -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: { +```javascript +const tokensQuery = ` + query($first: Int, $orderBy: BigInt, $orderDirection: String) { + tokens( + first: $first, orderBy: $orderBy, orderDirection: $orderDirection + ) { id + tokenID + contentURI + metadataURI } } -) -``` - -Doing so brings **many advantages**: - -- **Easy to read and maintain** queries -- The GraphQL **server handles variables sanitization** -- **Variables can be cached** at server-level -- **Queries can be statically analyzed by tools** (more on this in the following sections) - -**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: - -```tsx -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, - { +client + .query({ + query: gql(tokensQuery), variables: { - id, - includeOwner: true - } - } -) + first: 10, + orderBy: 'createdAtTimestamp', + orderDirection: 'desc', + }, + }) + .then((data) => console.log('Subgraph data: ', data)) + .catch((err) => { + console.log('Error fetching data: ', err) + }) ``` -Note: The opposite directive is `@skip(if: ...)`. - -
- -### Performance tips - -**"Ask for what you want"** +### URQL -GraphQL became famous for its "Ask for what you want" tagline. +Another option is [URQL](https://formidable.com/open-source/urql/), a somewhat lighter weight GraphQL client library. -For this reason, there is no way, in GraphQL, to get all available fields without having to list them individually. +Let's look at how fetch data from a subgraph with URQL in a web project. -When querying GraphQL APIs, always think of querying only the fields that will be actually used. +First, install `urql` and `graphql`: -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: - -```graphql -query listTokens { - tokens { # will fetch up to 100 tokens - id - transactions { # will fetch up to 100 transactions - id - } - } -} +```sh +npm install urql graphql ``` -The response could contain 100 transactions for each of the 100 tokens. +Then you can query the API with the following code: -If the application only needs 10 transactions, the query should explicitly set `first: 10` on the transactions field. +```javascript +import { createClient } from 'urql' -**Combining multiple queries** - -Your application might require querying multiple types of data as follows: - -```graphql -import { execute } from "your-favorite-graphql-client" +const APIURL = 'https://api.thegraph.com/subgraphs/name/username/subgraphname' 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: - -```graphql -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**. - -
- -### Leverage GraphQL Fragments - -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 (`{ ... }`): - -```graphql -query { - bondEvents { - id - newDelegate { - id - active - status - } - oldDelegate { + query { + tokens { id - active - status - } - } -} -``` - -Such repeated fields (`id`, `active`, `status`) bring many issues: - -- harder to read for more extensive queries -- 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: - -```graphql -query { - bondEvents { - id - newDelegate { - ...DelegateItem - } - oldDelegate { - ...DelegateItem + tokenID + contentURI + metadataURI } } -} - -# 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*). - -
- -### GraphQL Fragment do's and don'ts - -**Fragment base must be a type** - -A Fragment cannot be based on a non-applicable type, in short, **on type not having fields**: - -```graphql -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. - -Example: - -```graphql -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: - -- when fields of the same type are repeated in a query, group them in a Fragment -- when similar but not the same fields are repeated, create multiple fragments, ex: - -```graphql -# 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 - } -} -``` - ---- - -## The essential tools - -### GraphQL web-based explorers - -Iterating over queries by running them in your application can be cumbersome. -For this reason, don't hesitate to use [The Graph Explorer](https://thegraph.com/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](https://altair.sirmuel.design/) and [GraphiQL](https://graphiql-online.com/graphiql). - -
- -### GraphQL Linting - -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](https://github.com/dotansimha/graphql-eslint) will help you stay on top of GraphQL best practices with zero effort. - -[Setup the "operations-recommended"](https://github.com/dotansimha/graphql-eslint#available-configs) config will enforce essential rules such as: - -- `@graphql-eslint/fields-on-correct-type`: is a field used on a proper type? -- `@graphql-eslint/no-unused variables`: should a given variable stay unused? -- and more! - -This will allow you to **catch errors without even testing queries** on the playground or running them in production! - -
- -### IDE plugins - -**VSCode and GraphQL** - -The [GraphQL VSCode extension](https://marketplace.visualstudio.com/items?itemName=GraphQL.vscode-graphql) 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](https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint) is a must-have to visualize errors and warnings inlined in your code correctly. - - - -**WebStorm/Intellij and GraphQL** - -The [JS GraphQL plugin](https://plugins.jetbrains.com/plugin/8097-graphql/) will significantly improve your experience while working with GraphQL by providing: - -- syntax highlighting -- autocomplete suggestions -- validation against schema -- snippets - -More information on this [WebStorm article](https://blog.jetbrains.com/webstorm/2019/04/featured-plugin-js-graphql/) that showcases all the plugin's main features. - -
- -### TypeScript types generation - -Finally, the best GraphQL experience is reached when the TypeScript data types are generated from the defined GraphQL queries, as follows: - -```tsx -import { execute } from "your-favorite-graphql-client" -import { GetTokenQuery } from "./generated." - -const id = params.id -const query = ` -query GetToken($id: ID!) { - token(id: $id) { - id - owner - } -} ` -const result: GetTokenQuery = await execute( - query, - { - variables: { - id - } - } -) - -// `result` is typed! -``` - -Such a setup can be easily achieved by installing and configuring [GraphQL Code Generator](https://www.graphql-code-generator.com/docs/getting-started) as follows: - -```bash -yarn add graphql @graphql-codegen/cli @graphql-codegen/typescript @graphql-codegen/typescript-operations -``` - -Then update your `package.json` (or similar script configuration setup) as follows: - -```tsx -{ - // ... - "scripts": { - // ... - "generate": "graphql-codegen", - // ... - } - // ... -} -``` - -Add the following configuration file to your project: +const client = createClient({ + url: APIURL, +}) -```tsx -schema: "" -documents: './src/**/*.ts' -generates: - ./generated.ts: - plugins: - - typescript - - typescript-operations +const data = await client.query(tokensQuery).toPromise() ``` - -Finally, by simply running the configured script (`yarn generate`), GraphQL Code Generator will generate the proper TypeScript types in the `generated.ts` file: - -- Each query will get a corresponding `[QueryName]Query` type (ex: `GetToken` → `GetTokenQuery` type -- Each fragment will get a corresponding `[FragmentName]Fragment` type (ex: `DelegateItem` → `DelegateItemFragment` type