CodeGen: How it simplify your life

Imagine yourself as a chef with a collection of famous dishes. Everyone is asking for the recipes, requiring you to specify the ingredients, quantities, cooking time, preparation time, and much more. Are you gonna tell everyone from scratch?

Hmm.. definitely no. So the answer to this solution is to write a recipe book or post it on the blog. Even if there is any change in the recipe you can update it in one place.

That's exactly what Codegen solves if you're using typescript for frontend development, connecting frontend and backend type, schema and much more in sync.

Our Goal-

At the end of this article, we will strive to attain an enhanced and seamless development experience something like this-

But before we start doing crazy stuff with our code let's first try to understand

Why Do I need Graphql codegen in my project?

If you're using typescript for your frontend, you aim to get every variable to be strictly typed. Let's start with the UI, services and business logic. You can type safely because most of the framework comes with @types/..packages.

But what about the data you fetch from external services?

Ok, so you know the external data structure and its types, we can manually define its types no big deal, right? Here is a catch, now imagine if the backend team change any variable type or adds, or deletes new variables, it is almost impossible to keep track and update everywhere.

If you're using GraphQL, you probably know that GraphQL API is typed, and build as a graphQL schema. With the help of graphQL operations (query/mutation/fragment/ subscriptions), it's the same way we can fetch data on the client-side.

Before using generated types, let's first try to understand how it works under the hood.

How does GraphQL Code Generator work?

In order to generate code and types, GraphQL Code Generator depends on 2 main core concepts GraphQL Introspection and GraphQL AST.

GraphQL Introspection:

Introspection is the ability to query which resources are available in the current API schema. Given the API, via introspection, we can see the queries, types, fields, and directives it supports.

__Schema

The type __Schema is the one that defines the Schema the API provides. It returns which types the Schema has, the queryType, the mutationType, and the subscriptionType.

Let's take an example using Gitthub API:

__type

It represents the type we define in your system. A simple User type is illustrated below-

__typename

It is a reserved field in GraphQL that returns the name of the object type for a particular GraphQL object. __typename and id plays a significant role as popular clients like Apollo and Relay use it to cache on client-side.

GraphQL AST :

Abstract Syntax tree is a structure that is used in compilers to parse the code we write and convert it into a tree structure that we can traverse programmatically.

When a user makes a request, GraphQL combines the query document that the user requested with the schema definition that we defined for our resolver in the form of an AST. This AST is used to determine which fields were requested, what arguments were included and much more.

After Identifying GraphQL types (schema types and operations ) code generator relies on different sets of plugins to generate specific code snippets and types.

This process applied to the @graphql-codegen/typescript plugin, is illustrated below:

GraphQL code generator flow chart

In the flow chart, we leveraged TypeScript along with TypeScript-Operations and TypeScript-React-Apollo for GraphQL implementation. Additionally, a plethora of other valuable GraphQL plugins are available to further enhance our development experience.

Now we know what GraphQL code generator is, why we need it and how it works let's add it to our project.

Getting Started

Step 1: Setup

  • Create a new project react/nextJs by running the following command

    npx create-next-app or,

Clone the project by running these commands and skip the next steps-

git clone <git@github.com:RathiAnkxrx/codegen-setup.git>
cd codegen
yarn install
yarn dev

Step 2: Install dependencies

Applications that use Apollo Client require two main packages apollo/client and graphQL. Run the following command to install both packages-

yarn add @apollo/client graphql

In our example application, we're going to use Star Wars API

Step 3: Initialize Apollo Client

import { ApolloClient, InMemoryCache, ApolloProvider } from '@apollo/client';

Next, we'll ApolloClient and connect out react using ApolloProvider, here uri is our graphQL server URL, and the cache is an instance of InMemoryCache, which Apollo Client use to cache data after fetching them.

export default function App({ Component, pageProps }: any) {
  const client = new ApolloClient({
    uri: "https://swapi-graphql.netlify.app/.netlify/functions/index",
    cache: new InMemoryCache(),
  });
  return (
    <ApolloProvider client={client}>
      <Component {...pageProps} />
    </ApolloProvider>
  );
}

Apollo Client has been configured. Now to generate relevant types of Query and Mutation we need GraphQL Codegen CLI as we discussed earlier.

Step: 4 Instal graphQL Codegen dependencies

Here we're also installing some extra packages for plugins which help to generate query types, ready-to-use hooks and functions.

yarn add @graphql-codegen/cli @graphql-codegen/typescript @graphql-codegen/typescript-operations @graphql-codegen/typescript-react-apollo

Step 5: Create a codegen.ts file

In the root directory of your project create a codegen.ts file and copy the contents below into this file.

import { CodegenConfig } from "@graphql-codegen/cli";

const config: CodegenConfig = {
  schema: "https://swapi-graphql.netlify.app/.netlify/functions/index",
  documents: ["src/services/graphql/*.graphql"], // where all graphql queries are written
  ignoreNoDocuments: true, // for better experience with the watcher
  generates: {
    "src/types/graphql.tsx": {
      plugins: [
        "typescript",
        "typescript-operations",
        "typescript-react-apollo",
      ],
      config: {
        skipTypename: false,
        withHooks: true,
        withHOC: false,
        withComponent: false,
      },
    },
  },
};

export default config;

Let's first understand what CodegeConfig file contains:

  • schema your GraphQL server URL

  • documents for larger apps we could have hundreds of queries, to make our code clean we're creating a pagewise query file system.

  • src/types/graphql.tsx where all generated data is stored.

  • plugins following set of plugins are responsible to generate type, hooks and components. Check out more Codegen Plugins available.

But what if your API requires a token or you have more than one API, where generated types can't exist at the same folder? The following config will be helpful to achieve that.

import type { CodegenConfig } from "@graphql-codegen/cli";
const SCHEMA_URL = "http:/anyUrl/graphql";
const config: CodegenConfig = {
  generates: {
    "src/types/__generated__/withToken/graphql.tsx": {
      documents: "src/services/graphql/withToken/**/*.graphql",
      schema: [
        {
          [SCHEMA_URL]: {
            headers: {
              Authorization:
                "Bearer " + process.env.AUTH_TOKEN,
            },
          },
        },
      ],
      plugins: [...], //same as of previous example
      config: {...},
    },
    "src/types/__generated__/withoutToken/graphql.tsx": {
      documents: "src/services/graphql/withoutToken/**/*.graphql",
      schema: SCHEMA_URL,
      plugins: [...],
      config: {...},
    },
  },
};

export default config;

Step 6: Head to package.json and add this scripts

 "scripts": {
    ...
    "generate": "graphql-codegen --config codegen.ts"
  },

It's finally time to generate types, go ahead and open your terminal and run yarn generate or npm generate.

If you see something like this, Congratulations, you now have the best GraphQL front-end experience with fully-typed Queries and Mutations! Now let's see what we achieved and how to use it.

Writing GraphQL Queries

Earlier we used useQuery hook from @apollo/client and pass query inside hook to get data something like this-

import { useQuery } from "@apollo/client";
import { gql } from "@apollo/client";

export default function Home() {
  const GET_ALLFILMS = gql`
    query AllFilms {
      allFilms {
        films {
            ...
        }
      }
    }
  `;
  const { data, loading , error } = useQuery(GET_ALLFILMS);
  return <>...</>
}

And with the help of generated hooks, we can directly use them instead of useQuery

import styles from "./styles/page.module.css";
import { useAllFilmsQuery } from "../types/graphql";
export default function Home() {
  const { data , loading , error} = useAllFilmsQuery();
  return  <>...</>
}
  • useAllFilmsQuery() hook get triggered as soon as page URL hits.

  • If there is a case when you only want to hit query after some action like a button click we have useAllFilmsLazyQuery

import styles from "./styles/page.module.css";
import { useAllFilmsLazyQuery } from "../types/graphql";

export default function Home() {
  const [fetchFilms, {data, loading , error}] = useAllFilmsLazyQuery();
  const handleClick = () => {
    fetchFilms({
      variables : { /// if variable is required 
        }
    });
  };

  return (
    <main className={styles.main}>
      <button onClick={handleClick}>Fetch Films</button>
    </main>
  );
}

As data can be deeply nested while traversing down it's very easy to lose track of key pair or due to null | undefined value possibility we can easily hit an error which is hard to debug.

The biggest advantage in my opinion of using this approach is type safety and time. Now our IDE’s IntelliSense is able to give us hints about the shape of the data response. This helps us build apps fast and avoids bugs for they’d be caught really early when writing the code.

Generate more than just types

As we discussed earlier we have codegen plugins which help us to generate more than just type and hooks. One of them is typescript-react-apollo which is responsible to generate HOC(Higher Order Components) and Query Components.

React& Apollo: Generate Components

Go to the codegen.ts file and change withComponent to true. Run yarn generate in terminal

 config: {
  skipTypename: false,
  withHooks: true,
  withHOC: false,
  withComponent: true,
  },

And then use it in your code:

<AllFilmsComponent>
{({ data, loading, error }) => {
  return (
    <div className={styles.grid}>
      {data?.allFilms?.films?.map((film) => {
        return (
          <div key={+Math.random()}>
            <p>{film?.director}</p>
            ...
          </div>
        );
      })}
    </div>
  );
}}
</AllFilmsComponent>

Type destructuring tips:

To access the particular key value of an object there are many ways to do it one of which is object["someKey"] using the same concept we are going to destructure types.

In Our case we have allFilms inside data to get its type we will first start with data type which is AllFilmsQuery (same as your query name which is a query + "QUERY" after the name) and the type of allFilms will be AllFilmsQuery["allFilms"].

But there are times when your external data is deeply nested and some parents could have null | undefined. In this very particular example of allFilms is an array of something and could be null or undefined.

Then how to get what would be the type of films object. For this, we can not simply use AllFilmsQyery["allFilms"][0].

  • First, select NonNullable value of AllFilmsQuery and pick allFilms something like this-

      NonNullable<AllFilmsQuery['allFilms']>
    
  • And then using the same logic select films

      NonNullable<NonNullable<AllFilmsQuery["allFilms"]>["films"]>
    

This method is very useful while looping through data or passing nested values in child components.

Conclusion:

In this article, we tried to explain why exactly we need codegen in your project, how it works and filled the gap between GraphQL and typescript.

My aim while creating apps is to make the code readable and simple with the bare minimum of hard work. And this setup helps me to achieve that.

Let me know if you have any questions, suggestions or tips, and share with other people if you find this interesting and can help others.