Type-Safe GraphQL in React with TypeScript and Codegen

GraphQL makes it easy for frontend developers to ask for exactly the data they need. The flip side is that queries can get out of sync with the backend schema, and TypeScript can’t always catch those issues on its own. That’s where a GraphQL Code Generator can help. By generating types and hooks straight from your schema, you can connect GraphQL and TypeScript in a way that catches errors early and improves the development experience.
The Problem: Runtime Surprises
Here’s a common scenario:
const { data } = useQuery(GET_USER)
console.log(data.user.fullName) // compiles fine, fails at runtime
The fullName
field doesn’t exist in the schema, but you only find out once the code runs. Without generated types, TypeScript doesn’t know anything about your GraphQL queries.
Adding GraphQL Codegen
To fix this, add Codegen and a few plugins:
npm install -D @graphql-codegen/cli \
@graphql-codegen/typescript \
@graphql-codegen/typescript-operations \
@graphql-codegen/typescript-react-apollo
@graphql-codegen/typescript
: generates base TypeScript types from your GraphQL schema.@graphql-codegen/typescript-operations
: generates types for your queries and mutations.@graphql-codegen/typescript-react-apollo
: generates fully typed React hooks (likeuseGetUserQuery
).
Then, create the config file (codegen.yml
):
schema: http://localhost:4000/graphql
documents: src/**/*.graphql
generates:
src/generated/graphql.tsx:
plugins:
- typescript
- typescript-operations
- typescript-react-apollo
Then run:
npx graphql-codegen
Now every query you write comes with matching types and hooks.
Writing a Typed Query
Example query in src/queries/getUser.graphql
:
query GetUser {
user {
id
name
email
}
}
Codegen turns this into a hook:
export function useGetUserQuery(
baseOptions?: Apollo.QueryHookOptions<GetUserQuery, GetUserQueryVariables>
): Apollo.QueryResult<GetUserQuery, GetUserQueryVariables>
Using it in React:
import { useGetUserQuery } from '../generated/graphql'
export function UserCard() {
const { data, loading } = useGetUserQuery()
if (loading) return <p>Loading...</p>
return (
<div>
<h2>{data?.user?.name}</h2>
<p>{data?.user?.email}</p>
</div>
)
}
The key difference: data.user
is now strongly typed, so accessing a field that doesn’t exist is flagged at compile time.
Why This Helps
- Compile-time safety: Mistyped fields are caught before your code runs.
- Better IDE support: Autocomplete and inline documentation come for free.
- Confidence when refactoring: Schema changes show up as compiler errors, not production bugs.
- Less boilerplate: You don’t have to maintain manual interfaces that can go stale.
Things to Watch Out For
- Regenerating types: Make sure to rerun Codegen when the schema changes (add it to your dev script or CI pipeline).
- Null handling: GraphQL fields can resolve to
null
; your TypeScript types will reflect that, so handle it explicitly. - Large schemas: Break queries into smaller fragments to keep generated files manageable.
GraphQL and TypeScript work best when they’re connected. Codegen bridges the gap by ensuring your queries and your types are always aligned. It takes a little setup, but the payoff is fewer runtime surprises, safer refactors, and a smoother workflow overall. If you’re building React apps with GraphQL, it’s worth adding to your toolkit.
Credits
Photo by Thomas Stephan on Unsplash.