Generate a DocumentNode automatically for GraphQL operations, and the typescript signature it represents for better use with GraphQL clients.
You've probably used plugins to automatically generate the hooks for your GraphQL client with the GraphQL Code Generator, and the available plugins.
I recommend watching this video on GraphQL Code Generator with Apollo Client 3 to see the benefits of GraphQL Code Generator.
Plugins such as typescript-react-apollo
, typescript-urql
, and typescript-graphql-request
generate a lot of boilerplate, and wrapper code that often goes unused. This boilerplate isn't always the easiest to use across GraphQL clients if you're working in a monorepo.
We'll explore using the typed-document-node
plugin which aims to reduce this boilerplate by generating a DocumentNode
. A DocumentNode
is a GraphQL operation AST that can be used interchangable with any GraphQL client that supports it.
This reduces the need for generating client specific hooks with the GraphQL Code Generator, and instead use the client hooks directly with the generated DocumentNode
.
Here's a snippet of the GraphQL schema we'll be using:
type Query {
cart(id: ID!): Cart!
}
type Cart {
id: ID!
totalItems: Int!
isEmpty: Boolean!
subTotal: Money!
}
type Money {
formatted: String!
amount: Int!
}
We'll be using the CartQL API throughout this schema for our example API — View full schema.
Then inside of our project we can create .graphql
files for our operations. Here is a query:
query GetCartById($id: ID!) {
cart(id: $id) {
id
totalItems
subTotal {
formatted
amount
}
}
}
The typed-document-node
plugin will generate types for the data, and variables for the GraphQL operation based on the query file above, once we install the plugin.
Now let's install GraphQL Code Generator:
npm install --save-dev @graphql-codegen/cli @graphql-codegen/typescript @graphql-codegen/typescript-operations @graphql-codegen/typed-document-node
You'll also want to install graphql
itself, cross-fetch
, and a GraphQL client library. We'll install 3 client libraries to see how this works with graphql-request
, @apollo/client
, and @urql/core
.
Inside your project install the following:
npm install graphql graphql-request @apollo/client @urql/core cross-fetch
Once these have been installed we'll next need to create a codegen.yml
file, and add a script to our package.json
to run it.
Inside a new file in the root of your project, create the file codegen.yml
and add:
overwrite: true
schema: https://api.cartql.com
documents: "**/*.graphql
generates:
types.ts:
plugins:
- typescript
- typescript-operations
- typed-document-node
Here we are telling the GraphQL Code Generator to generate the file types.ts
in the root of our project, and use the listed plugins
against our schema. It will run these plugins for any document found in **/*.graphql
.
Now inside of package.json
within scripts
add the following:
{
"codegen": "graphql-codegen"
}
Then run the script:
npm run codegen
Once completed, you should see a new file in the root of your project created called types.ts
. Inside here you should see some code that was generated using the plugins we defined above. You'll see the types for our GraphQL types, and arguments.
You'll want to pay special attention to the following generated objects:
GetCartByIdDocument
GetCartByIdQuery
GetCartByIdQueryVariables
Now that we have these generated, we can go ahead and use the GetCartByIdDocument
"Node" with your GraphQL client.
@apollo/client
We can import @apollo/client
and pass it the generated GetCartByIdDocument
without needing to codegen, or configure anything special. It just works, and it fully type-safe thanks to TypeScript generics.
import fetch from "cross-fetch";
import { ApolloClient, InMemoryCache, HttpLink } from "@apollo/client";
import { GetCartByIdDocument } from "./types";
const client = new ApolloClient({
link: new HttpLink({ uri: "https://api.cartql.com", fetch }),
cache: new InMemoryCache(),
});
client
.query({
query: GetCartByIdDocument,
variables: {
id: "test",
},
})
.then(({ data }) => console.log(data.cart));
@urql/core
Finally with urql we can do the same by importing the DocumentNode
, and use it to make a request. The generics inside of the generated type document node make it easy to work with the response, and variables.
import fetch from "cross-fetch";
import { createClient } from "@urql/core";
import { GetCartByIdDocument } from "./types";
const client = createClient({
url: "https://api.cartql.com",
fetch,
});
client
.query(GetCartByIdDocument, {
id: "test",
})
.toPromise()
.then(({ data }) => console.log(data.cart));
graphql-request
Even if your GraphQL client doesn't fully support DocumentNode, in the case with graphql-request
, you often pass to the request generics the shape of your data, and variables.
Let's import graphql-request
, cross-fetch
and instantiate a new GraphQLClient
that we can use to send GraphQL operations to.
import fetch from "cross-fetch";
import { GraphQLClient } from "graphql-request";
const client = new GraphQLClient("https://api.cartql.com", { fetch });
Then we'll want to import our generated objects shown above:
import {
GetCartByIdDocument,
GetCartByIdQuery,
GetCartByIdQueryVariables,
} from "./types";
Then to use this with graphql-request
, we invoke client.request
and pass it our GetCartByIdQuery
, and GetCartByIdQueryVariables
as generics.
Then pass the GetCartByIdDocument
as the first argument, and the variables for the second:
client
.request<GetCartByIdQuery, GetCartByIdQueryVariables>(GetCartByIdDocument, {
id: "test",
})
.then(({ cart }) => console.log(cart));
useQuery
, useMutation
hooks that GraphQL Code Generator creates.Published on 9 May 2022 by Jamie Barton