Guide: React and Vue
GraphQL Code Generator provides an unified way to get TypeScript types from GraphQL operations for most modern GraphQL clients and frameworks.
This guide is built using the Star wars films demo API (opens in a new tab).
We will build a simple GraphQL front-end app using the following Query to fetch the list of Star Wars films:
query allFilmsWithVariablesQuery($first: Int!) {
allFilms(first: $first) {
edges {
node {
...FilmItem
}
}
}
}
and its FilmItem
Fragment definition:
fragment FilmItem on Film {
id
title
releaseDate
producers
}
All the below code examples are available in our repository examples
folder (opens in a new tab).
Installation
For most GraphQL clients and frameworks (React, Vue), install the following packages:
For Yarn:
yarn add graphql
yarn add -D typescript ts-node @graphql-codegen/cli @graphql-codegen/client-preset
For npm:
npm i -S graphql
npm i -D typescript ts-node @graphql-codegen/cli @graphql-codegen/client-preset
For pnpm:
pnpm i graphql @graphql-typed-document-node/core
pnpm i -D typescript ts-node @graphql-codegen/cli @graphql-codegen/client-preset
Then provide the corresponding framework-specific configuration:
import { CodegenConfig } from '@graphql-codegen/cli'
const config: CodegenConfig = {
schema: 'https://swapi-graphql.netlify.app/.netlify/functions/index',
documents: ['src/**/*.tsx'],
ignoreNoDocuments: true, // for better experience with the watcher
generates: {
'./src/gql/': {
preset: 'client'
}
}
}
export default config
Each framework-specific lines are highlighted
Usage with @tanstack/react-query
If you are using @tanstack/react-query
, we recommend using it with graphql-request@^5.0.0
to get the best developer experience.
If you are willing to provide your own fetcher, you can directly jump to the "Appendix I: React Query with a custom fetcher setup" and continue the guide once React Query is properly setup.
Writing GraphQL Queries
First, start GraphQL Code Generator in watch mode:
yarn graphql-codegen --watch
Using GraphQL Code Generator will type your GraphQL Query and Mutations as you write them ⚡️
Now, we can start implementing our first query with the graphql()
function, generated in src/gql/
:
import React from 'react'
import { useQuery } from '@apollo/client'
import './App.css'
import Film from './Film'
import { graphql } from '../src/gql'
const allFilmsWithVariablesQueryDocument = graphql(/* GraphQL */ `
query allFilmsWithVariablesQuery($first: Int!) {
allFilms(first: $first) {
edges {
node {
...FilmItem
}
}
}
}
`)
function App() {
// `data` is typed!
const { data } = useQuery(allFilmsWithVariablesQueryDocument, { variables: { first: 10 } })
return (
<div className="App">
{data && <ul>{data.allFilms?.edges?.map((e, i) => e?.node && <Film film={e?.node} key={`film-${i}`} />)}</ul>}
</div>
)
}
export default App
Be cautious, anonymous Queries and Mutations will be ignored.
Simply use the provided graphql()
function (from ../src/gql/
) to define your GraphQL Query or Mutation, then, get instantly typed-variables and result just by passing your GraphQL document to your favorite client ✨
Let's now take a look at how to define our <Film>
component using the FilmItem
fragment and its corresponding TypeScript type.
Writing GraphQL Fragments
As showcased in one of our recent blog posts (opens in a new tab), GraphQL Fragments help build better isolated and reusable UI components.
Let's look at the implementation of our Film
UI component in React or Vue:
import { FragmentType, useFragment } from './gql/fragment-masking'
import { graphql } from '../src/gql'
export const FilmFragment = graphql(/* GraphQL */ `
fragment FilmItem on Film {
id
title
releaseDate
producers
}
`)
const Film = (props: {
/* `film` property has the correct type 🎉 */
film: FragmentType<typeof FilmFragment>
}) => {
const film = useFragment(FilmFragment, props.film)
return (
<div>
<h3>{film.title}</h3>
<p>{film.releaseDate}</p>
</div>
)
}
export default Film
Examples for SWR (React), graphql-request
and Villus (Vue) are available in our repository examples
folder (opens in a new tab).
You will notice that our <FilmItem>
component leverages 2 imports from our generated code (from ../src/gql
): the FragmentType<T>
type helper and the useFragment()
function.
- we use
FragmentType<typeof FilmFragment>
to get the corresponding Fragment TypeScript type - later on, we use
useFragment()
to retrieve thefilm
property
Leveraging FragmentType<typeof FilmFragment>
and useFragment()
helps keep your UI component isolated and avoids inheriting the parent GraphQL Query's typings.
By using GraphQL Fragments, you are explicitly declaring your UI component's data dependencies and safely accessing only the data it needs.
Finally, unlike most GraphQL Client setups, you don't need to append the Fragment definition document to the related Query. You simply need to reference it in your GraphQL Query, as shown below:
const allFilmsWithVariablesQueryDocument = graphql(/* GraphQL */ `
query allFilmsWithVariablesQuery($first: Int!) {
allFilms(first: $first) {
edges {
node {
...FilmItem
}
}
}
}
`)
Congratulations, you now have the best GraphQL front-end experience with fully-typed Queries and Mutations!
From simple Queries to more advanced Fragments-based ones, GraphQL Code Generator has you covered with a simple TypeScript configuration file, and without impact on your application bundle size! 🚀
What's next?
To get the best GraphQL development experience, we recommend installing the GraphQL VSCode extension (opens in a new tab) to get:
- syntax highlighting
- autocomplete suggestions
- validation against schema
- snippets
- go to definition for fragments and input types
Also, make sure to follow GraphQL best practices by using graphql-eslint
(opens in a new tab) and the ESLint VSCode extension (opens in a new tab) to visualize errors and warnings inlined in your code correctly.
Feel free to continue playing with this demo project, available in all flavors, in our repository examples
folder (opens in a new tab).
Config API
The client
preset allows the following config
options:
scalars
: Extends or overrides the built-in scalars and custom GraphQL scalars to a custom type.defaultScalarType
: Allows you to override the type that unknownscalars
will have. Defaults toany
.strictScalars
: Ifscalars
are found in the schema that are not defined in scalars an error will be thrown during codegen.namingConvention
: Available case functions inchange-case-all
arecamelCase
,capitalCase
,constantCase
,dotCase
,headerCase
,noCase
,paramCase
,pascalCase
,pathCase
,sentenceCase
,snakeCase
,lowerCase
,localeLowerCase
,lowerCaseFirst
,spongeCase
,titleCase
,upperCase
,localeUpperCase
andupperCaseFirst
.useTypeImports
: Will useimport type {}
rather thanimport {}
when importing only types. This gives compatibility with TypeScript's"importsNotUsedAsValues": "error"
(opens in a new tab) option.skipTypename
: Does not add__typename
to the generated types, unless it was specified in the selection set.arrayInputCoercion
: The GraphQL spec (opens in a new tab) allows arrays and a single primitive value for list input. This allows to deactivate that behavior to only accept arrays instead of single values.enumsAsTypes
: Generates enum as TypeScript string uniontype
instead of anenum
. Useful if you wish to generate.d.ts
declaration file instead of.ts
, or if you want to avoid using TypeScript enums due to bundle size concerns.dedupeFragments
: Removes fragment duplicates for reducing data transfer. It is done by removing sub-fragments imports from fragment definition.nonOptionalTypename
: Automatically adds__typename
field to the generated types, even when they are not specified in the selection set, and makes it non-optional.avoidOptionals
: This will cause the generator to avoid using TypeScript optionals (?
) on types.
Appendix I: React Query with a custom fetcher setup
The use of @tanstack/react-query
along with graphql-request@^5
is highly recommended due to GraphQL Code Generator integration with graphql-request@^5
.
Create a file with the following helper function within your project:
import request from 'graphql-request'
import { type TypedDocumentNode } from '@graphql-typed-document-node/core'
import { useQuery, type UseQueryResult } from '@tanstack/react-query'
export function useGraphQL<TResult, TVariables>(
document: TypedDocumentNode<TResult, TVariables>,
...[variables]: TVariables extends Record<string, never> ? [] : [TVariables]
): UseQueryResult<TResult> {
return useQuery([(document.definitions[0] as any).name.value, variables], async ({ queryKey }) =>
request(
'https://swapi-graphql.netlify.app/.netlify/functions/index',
document,
queryKey[1] ? queryKey[1] : undefined
)
)
}
Then write type-safe code like the following:
import { useGraphQL } from './use-graphql.js'
import { gql } from './generated/graphql.js'
const allFilmsWithVariablesQueryDocument = gql(/* GraphQL */ `
query allFilmsWithVariablesQuery($first: Int!) {
allFilms(first: $first) {
edges {
node {
title
}
}
}
}
`)
function App() {
// `data` is properly typed, inferred from `allFilmsWithVariablesQueryDocument` type
const { data } = useGraphQL(
allFilmsWithVariablesQueryDocument,
// variables are also properly type-checked.
{ first: 10 }
)
// ... further component code
}
In case you do not want to use graphql-request
with @tanstack/react-query
, you can write and type your own custom fetcher function.
GraphQL Code Generator, via the client
preset, generates GraphQL documents similar to the following:
const query: TypedDocumentNode<{ greetings: string }, never | Record<any, never>> = parse(/* GraphQL */ `
query greetings {
greetings
}
`)
A TypedDocumentNode<R, V>
type carry 2 Generic arguments: the type of the GraphQL result R
and the type of the GraphQL operation variables V
.
To implement your own React Query fetcher while preserving the GraphQL document type inference, it should implement a function signature that extract the result type and use it as a return type, as showcased below:
import { type TypedDocumentNode } from '@graphql-typed-document-node/core'
import { useQuery, type UseQueryResult } from '@tanstack/react-query'
import { print, type ExecutionResult } from 'graphql'
/** Your custom fetcher function */
async function customFetcher<TResult, TVariables>(
url: string,
document: TypedDocumentNode<TResult, TVariables>,
...[variables]: TVariables extends Record<string, never> ? [] : [TVariables]
): Promise<TResult> {
const response = await fetch(url, {
method: 'POST',
headers: {
'content-type': 'application/json'
},
body: JSON.stringify({
query: print(document),
variables
})
})
if (response.status !== 200) {
throw new Error(`Failed to fetch: ${response.statusText}. Body: ${await response.text()}`)
}
return await response.json()
}
export function useGraphQL<TResult, TVariables>(
document: TypedDocumentNode<TResult, TVariables>,
...[variables]: TVariables extends Record<string, never> ? [] : [TVariables]
): UseQueryResult<ExecutionResult<TResult>> {
return useQuery([(document.definitions[0] as any).name.value, variables], () =>
customFetcher('https://swapi-graphql.netlify.app/.netlify/functions/index', document, variables)
)
}
Then write type-safe code like the following:
import { useGraphQL } from './use-graphql.js'
import { gql } from './generated/graphql.js'
const allFilmsWithVariablesQueryDocument = gql(/* GraphQL */ `
query allFilmsWithVariablesQuery($first: Int!) {
allFilms(first: $first) {
edges {
node {
title
}
}
}
}
`)
function App() {
// `data` is properly typed, inferred from `allFilmsWithVariablesQueryDocument` type
const { data } = useGraphQL(
allFilmsWithVariablesQueryDocument,
// variables are also properly type-checked.
{ first: 10 }
)
// ... further component code
}
Appendix II: Compatibility
GraphQL Code Generator client
preset (@graphql-codegen/client-preset
) is compatible with the following GraphQL clients and frameworks:
-
React
@apollo/client
(since3.2.0
, not when using React Components (<Query>
))@urql/core
(since1.15.0
)@urql/preact
(since1.4.0
)urql
(since1.11.0
)graphql-request
(since5.0.0
)react-query
(withgraphql-request@5.x
)swr
(withgraphql-request@5.x
)
-
Vue
@vue/apollo-composable
(since4.0.0-alpha.13
)villus
(since1.0.0-beta.8
)@urql/vue
(since1.11.0
)
If your stack is not listed above, please refer to other guides (Angular, Svelte) or to our plugins directory.