In the ever-evolving landscape of web development, GraphQL has emerged as a powerful alternative to traditional REST APIs, offering clients the ability to request precisely the data they need. However, this flexibility can introduce a new set of challenges, particularly when it comes to maintaining type safety between the frontend and backend. This is where graphql-codegen
comes in, a revolutionary tool that automates the generation of typed code from your GraphQL schema, supercharging your development workflow and eliminating a whole class of runtime errors.
This article will serve as your guide to understanding and mastering graphql-codegen
. We will start with the fundamental concepts, walk through a practical, step-by-step example of setting up and configuring the tool, and explore best practices for integrating it into your projects. By the end of this guide, you'll be equipped to leverage graphql-codegen
to build more robust, maintainable, and type-safe applications.
Want an integrated, All-in-One platform for your Developer Team to work together with maximum productivity?
Apidog delivers all your demands, and replaces Postman at a much more affordable price!
What is graphql-codegen
and Why Do You Need It?
At its core, graphql-codegen
is a command-line tool that introspects your GraphQL schema and your GraphQL operations (queries, mutations, and subscriptions) and generates code in a variety of languages. For the purpose of this beginner's guide, we will focus on its most popular use case: generating TypeScript types and hooks for frontend applications.
The primary problem that graphql-codegen
solves is the tedious and error-prone process of manually writing TypeScript interfaces for your GraphQL API's data structures and the results of your queries. Without it, developers are left to either work with untyped data (losing the benefits of TypeScript) or spend significant time creating and maintaining types that can easily become outdated as the API evolves.
The benefits of adopting graphql-codegen
are manifold:
- End-to-End Type Safety: By generating types directly from your schema,
graphql-codegen
ensures that your frontend code is always in sync with your backend's data model. This means you catch type-related errors at compile time, long before they reach your users. - Improved Developer Experience: With generated types, you get features like autocompletion and intelligent suggestions in your code editor, making development faster and more efficient. No more guessing the shape of your API responses!
- Reduced Boilerplate:
graphql-codegen
can generate not just types, but also ready-to-use hooks for popular GraphQL clients like Apollo Client and React Query. This eliminates the need to write repetitive data-fetching logic. - Enhanced Maintainability: When your API schema changes, a simple command is all it takes to regenerate your types. This makes your codebase much easier to maintain and refactor over time.
This approach aligns perfectly with the "schema-first" development philosophy, where the GraphQL schema serves as the single source of truth for your API.
Getting Started: Your First graphql-codegen
Setup
Let's dive into the practical aspects of using graphql-codegen
. For this tutorial, we'll assume you have a basic understanding of GraphQL, TypeScript, and have Node.js and a package manager (like npm or yarn) installed.
Our goal is to set up graphql-codegen
for a simple React application that displays a list of blog posts.
Step 1: Project Initialization and Dependencies
First, let's create a new React project with TypeScript and install the necessary dependencies.Bash
npx create-react-app my-blog --template typescript
cd my-blog
npm install @apollo/client graphql
npm install -D @graphql-codegen/cli @graphql-codegen/client-preset typescript
Here's a breakdown of the dependencies we've installed:
@apollo/client
: A popular GraphQL client for React.graphql
: A peer dependency required by both Apollo Client andgraphql-codegen
.@graphql-codegen/cli
: The command-line interface forgraphql-codegen
.@graphql-codegen/client-preset
: A modern and streamlined preset that simplifies configuration for client-side applications.typescript
: The TypeScript compiler.
Step 2: The graphql-codegen
Initialization Wizard
The easiest way to get started with graphql-codegen
is by using its initialization wizard. Run the following command in your project's root directory:Bash
npx graphql-codegen init
The wizard will ask you a series of questions to help configure your project. Here's a typical set of answers for our blog application scenario:
- What type of application are you building?
Application built with React
- Where is your schema? Provide the URL to your GraphQL API endpoint. For this tutorial, we can use a publicly available example API:
https://swapi-graphql.netlify.app/.netlify/functions/index
- Where are your operations and fragments?
src/**/*.tsx
(This tellsgraphql-codegen
to look for GraphQL queries in all.tsx
files within thesrc
directory). - Pick plugins: The wizard will suggest a set of plugins. The defaults, including
typescript
,typescript-operations
, andtypescript-react-apollo
, are a great starting point. - Where to write the output?
src/gql/
(This will create a new directorysrc/gql
to store the generated files). - Do you want to generate an introspection file?
Yes
(This can be useful for local development and IDE extensions). - How to name the config file?
codegen.ts
- What script in package.json should run the codegen?
codegen
After answering these questions, the wizard will create a codegen.ts
file in your project's root and add a codegen
script to your package.json
.
Step 3: Understanding the Configuration File (codegen.ts
)
The codegen.ts
file is the heart of your graphql-codegen
setup. Let's take a look at a simplified version of what the wizard generates:TypeScript
import type { CodegenConfig } from '@graphql-codegen/cli';
const config: CodegenConfig = {
overwrite: true,
schema: "https://swapi-graphql.netlify.app/.netlify/functions/index",
documents: "src/**/*.tsx",
generates: {
"src/gql/": {
preset: "client",
plugins: []
}
}
};
export default config;
Let's break down the key configuration options:
overwrite: true
: This ensures that existing generated files are overwritten each time you run the command.schema
: This points to the source of your GraphQL schema. It can be a URL to a live endpoint, a local.graphql
or.json
file.documents
: This is a glob pattern that tellsgraphql-codegen
where to find your GraphQL operations (queries, mutations, and fragments).generates
: This is the most important part of the configuration. It's an object where each key represents an output file or directory, and the value defines what to generate.preset: "client"
: Theclient-preset
is a powerful and recommended way to configuregraphql-codegen
for frontend applications. It bundles several plugins and provides a streamlined experience. It generates agraphql
function that you'll use to write your queries.
Step 4: Writing Your First GraphQL Query
Now that graphql-codegen
is configured, let's write a GraphQL query to fetch our blog posts. Create a new file src/components/Posts.tsx
:TypeScript
import { gql } from '../gql/gql';
import { useQuery } from '@apollo/client';
const GET_POSTS = gql(`
query GetPosts {
allFilms {
films {
id
title
director
releaseDate
}
}
}
`);
const Posts = () => {
const { loading, error, data } = useQuery(GET_POSTS);
if (loading) return <p>Loading...</p>;
if (error) return <p>Error :(</p>;
return (
<div>
{data?.allFilms?.films?.map((film) => (
<div key={film?.id}>
<h2>{film?.title}</h2>
<p>Director: {film?.director}</p>
<p>Release Date: {film?.releaseDate}</p>
</div>
))}
</div>
);
};
export default Posts;
Notice that we're importing a gql
function from the generated src/gql/gql.ts
file. This is the gql
function provided by the client-preset
and it's what enables the magic of type inference.
Step 5: Running graphql-codegen
and Seeing the Magic
Now, run the codegen
script from your package.json
:Bash
npm run codegen
This command will introspect the schema, find your GET_POSTS
query, and generate the corresponding TypeScript types in the src/gql
directory.
If you now inspect the data
object returned by the useQuery
hook in Posts.tsx
, you'll see that it's fully typed! Your IDE will provide autocompletion for data.allFilms.films
, and TypeScript will warn you if you try to access a field that doesn't exist in the query.
A Deeper Dive: A Practical Blog Frontend Example
Let's expand on our example to solidify your understanding. Imagine our blog has a more traditional schema:GraphQL
type Author {
id: ID!
name: String!
}
type Post {
id: ID!
title: String!
content: String!
author: Author!
}
type Query {
posts: [Post!]!
post(id: ID!): Post
}
type Mutation {
createPost(title: String!, content: String!, authorId: ID!): Post!
}
Let's assume this schema is running on http://localhost:4000/graphql
. We would update our codegen.ts
accordingly:TypeScript
// codegen.ts
// ...
schema: "http://localhost:4000/graphql",
// ...
Now, let's create a component to display a single post and another to create a new one.
Fetching a Single PostTypeScript
// src/components/Post.tsx
import { gql } from '../gql/gql';
import { useQuery } from '@apollo/client';
import { useParams } from 'react-router-dom';
const GET_POST = gql(`
query GetPost($id: ID!) {
post(id: $id) {
id
title
content
author {
id
name
}
}
}
`);
const Post = () => {
const { id } = useParams<{ id: string }>();
const { loading, error, data } = useQuery(GET_POST, {
variables: { id },
});
if (loading) return <p>Loading...</p>;
if (error) return <p>Error :(</p>;
return (
<div>
<h2>{data?.post?.title}</h2>
<p>By {data?.post?.author?.name}</p>
<p>{data?.post?.content}</p>
</div>
);
};
export default Post;
After running npm run codegen
, the types for the GetPostQuery
and its variables will be generated, providing type safety for the useQuery
hook and its result.
Creating a New PostTypeScript
// src/components/CreatePost.tsx
import { gql } from '../gql/gql';
import { useMutation } from '@apollo/client';
import { useState } from 'react';
const CREATE_POST = gql(`
mutation CreatePost($title: String!, $content: String!, $authorId: ID!) {
createPost(title: $title, content: $content, authorId: $authorId) {
id
}
}
`);
const CreatePost = () => {
const [title, setTitle] = useState('');
const [content, setContent] = useState('');
const [createPost, { data, loading, error }] = useMutation(CREATE_POST);
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
createPost({ variables: { title, content, authorId: '1' } }); // Assuming authorId '1' for simplicity
};
return (
<form onSubmit={handleSubmit}>
<input
type="text"
placeholder="Title"
value={title}
onChange={(e) => setTitle(e.target.value)}
/>
<textarea
placeholder="Content"
value={content}
onChange={(e) => setContent(e.target.value)}
/>
<button type="submit" disabled={loading}>
{loading ? 'Creating...' : 'Create Post'}
</button>
{error && <p>Error creating post: {error.message}</p>}
{data && <p>Post created with ID: {data.createPost.id}</p>}
</form>
);
};
export default CreatePost;
Here, graphql-codegen
generates the types for the CreatePostMutation
and its variables. This ensures that when you call the createPost
function, you are passing the correct types for title
, content
, and authorId
.
Advanced Concepts and Best Practices
As you become more comfortable with graphql-codegen
, you'll encounter more advanced features and best practices:
Fragments: graphql-codegen
has excellent support for GraphQL fragments. You can define fragments in your .tsx
files and graphql-codegen
will correctly handle the types, promoting reusable and co-located data dependencies for your components.
Using .graphql
Files: For better separation of concerns, you can write your GraphQL queries, mutations, and fragments in dedicated .graphql
files. Simply update the documents
array in your codegen.ts
to include this file extension: documents: ["src/**/*.tsx", "src/**/*.graphql"]
.
Custom Scalars: If your GraphQL schema uses custom scalars (e.g., Date
, JSON
), you can map them to corresponding TypeScript types in your codegen.ts
file to maintain type safety.
Watch Mode: For a seamless development experience, you can run graphql-codegen
in watch mode. This will automatically regenerate your types whenever you save a file containing a GraphQL operation. Simply add a --watch
flag to your codegen
script in package.json
: "codegen": "graphql-codegen --watch"
.
Troubleshooting Common Issues
While graphql-codegen
is a powerful tool, you might encounter a few common issues as a beginner:
- "Unable to find schema": This error usually means that the
schema
path in yourcodegen.ts
is incorrect or the GraphQL server is not running at the specified address. - Plugin Configuration Errors: Ensure that you have installed all the necessary plugins and that their configurations in
codegen.ts
are correct. graphql
Peer Dependency Issues: Make sure you havegraphql
installed as a direct dependency in your project.
Conclusion: Embrace the Type-Safe Future
graphql-codegen
is more than just a code generation tool; it's a paradigm shift in how we build applications with GraphQL. By embracing automation and leveraging the power of your schema, you can create more robust, maintainable, and enjoyable-to-work-with codebases.
This tutorial has provided you with a solid foundation for getting started with graphql-codegen
. We've covered the core concepts, walked through a practical example, and touched upon best practices. The journey doesn't end here. The graphql-codegen
ecosystem is vast, with a rich collection of plugins that cater to various frameworks and use cases. I encourage you to explore the official documentation, experiment with different plugins, and discover how graphql-codegen
can revolutionize your GraphQL development workflow. Happy coding!
Want an integrated, All-in-One platform for your Developer Team to work together with maximum productivity?
Apidog delivers all your demands, and replaces Postman at a much more affordable price!