Technology

Generating Code From Your GraphQL Schemas

Kevin Em
July 17th 2020

Why should you generate code from your schemas?

Generating code from your GraphQL schemas can help improve your development workflow. By doing so, there's less amount of code you'd need to write, which in turn saves you time and is easier to maintain. You can detect errors when there are changes to the schema, making the codebase less error prone.

How can you use your schemas to generate code?

Initial Setup

Start off with creating a basic React app using typescript.
npx create-react-app my-app --template typescript
Generating code from your GraphQL schemas can be done using graphql-code-generator. Using this tools, you're able to configure it in many different ways to fit your needs and workflow.
For this example, I’ll be using it to generate code for ReactJS, Typescript, and Apollo.
Add graphql and graphql-code-generator packages:
yarn add graphql

yarn add -D @graphql-codegen/typescript-operations @graphql-codegen/typescript @graphql-codegen/cli
Update package.json:
{
    "scripts": {
                ...
        "generate": "graphql-codegen"
    }
}

Using Github GraphQL API

We’ll need a GraphQL backend server to query against. I’ll be using Github’s GraphQL API. Download Github's schema.docs.graphql file and place it at the root of the project to be used with code generation.
Add codegen.yml to the root of the project.
schema: schema.docs.graphql
generates:
  ./src/generated/graphql.tsx:
    plugins:
      - typescript
  • schema: can be set to point to wherever you have your GraphQL schema located. It can be set to point to a backend GraphQL server so that whenever you generate code they’re in sync with the server. Here it's set to read locally from our schema file.
  • generates: is set to output code to ./src/generated/graphql.tsx.
Generate code from the Github schema by running:
yarn generate
Inspect ./src/generated/graphql.tsx, you’ll see all the types that were created from the schema. In a matter of a few seconds, all of it was created for you saving you time from having to manually create them. You don't have to maintain any typings you'd have to write yourself. Any changes to the schema can be regenerated and reflected back in the code. It also gives you the benefit of detecting errors early on if there was a change to the schema and a mismatch in typings when generating code from the schema.

Adding Apollo Client

Now we’ll need a GraphQL client to make queries to the server. We'll use Apollo client for that and need to configure it to generate code to use Apollo.
All apollo client and typescript-react-apollo:
yarn add @apollo/client

yarn add -D @graphql-codegen/typescript-react-apollo
Update codegen.yml:
schema: schema.docs.graphql
documents: 'src/**/*.graphql'
generates:
  ./src/generated/graphql.tsx:
    plugins:
      - 'typescript'
      - 'typescript-operations'
      - 'typescript-react-apollo'
    config:
      reactApolloVersion: 3
      withHOC: false
      withComponent: false
      withHooks: true
  • documents: is set to look for any GraphLQ files within the directory to be used for code generation.
  • withHooks: we’ll also set the configuration to only generate hooks that we can use.
Additional configurations can be found here.

Query Github API

Create a graphql file src/queries/CurrentUser.graphql:
query CurrentUser {
  viewer {
    avatarUrl
    repositories(first: 10) {
      totalCount
      nodes {
        name
      }
    }
  }
}
Then we’ll run yarn generate to generate the hook for the query. Inspect src/generated/graphql.tsx and you should see that it generated this portion of code you can use.
...

export function useCurrentUserQuery(baseOptions?: ApolloReactHooks.QueryHookOptions<CurrentUserQuery, CurrentUserQueryVariables>) {
        return ApolloReactHooks.useQuery<CurrentUserQuery, CurrentUserQueryVariables>(CurrentUserDocument, baseOptions);
      }
export function useCurrentUserLazyQuery(baseOptions?: ApolloReactHooks.LazyQueryHookOptions<CurrentUserQuery, CurrentUserQueryVariables>) {
          return ApolloReactHooks.useLazyQuery<CurrentUserQuery, CurrentUserQueryVariables>(CurrentUserDocument, baseOptions);
        }
export type CurrentUserQueryHookResult = ReturnType<typeof useCurrentUserQuery>;
export type CurrentUserLazyQueryHookResult = ReturnType<typeof useCurrentUserLazyQuery>;
export type CurrentUserQueryResult = ApolloReactCommon.QueryResult<CurrentUserQuery, CurrentUserQueryVariables>;

...
Next we’ll need to setup the apollo client.
Add src/apolloClient.ts
import { ApolloClient, InMemoryCache } from '@apollo/client';

export default new ApolloClient({
  uri: 'https://api.github.com/graphql',
  cache: new InMemoryCache(),
  headers: {
    authorization: `bearer <GITHUB_ACCESS_TOKEN>`,
  },
});
You’ll need a personal access token from GitHub that can be obtained by follow these instructions. Once you have your token, replace <GITHUBACCESSTOKEN> with it.
Next we’ll add ApolloProvider in src/index.tsx.
import apolloClient from ‘./apolloClient';

...

ReactDOM.render(
  <React.StrictMode>
    <ApolloProvider client={apolloClient}>
      <App/>
    </ApolloProvider>
  </React.StrictMode>,
  document.getElementById('root')
);

...
That’s it for setting up Apollo. Next we'll update src/App.tsx to use the hook to query our data.
Import useCurrentUserQuery from ./generated/graphql and display the data.
import React from 'react';
import { useCurrentUserQuery } from './generated/graphql';

function App() {
  const { loading, error, data } = useCurrentUserQuery();

  if (loading) {
    return <p>Loading...</p>;
  }

  if (error) {
    return <p>{error.message}</p>;
  }

  return (
    <pre>
      <code>
        {JSON.stringify(data?.viewer, null, 4)}
      </code>
    </pre>
  );
}

export default App;
Viewed in the browser results in:
{
    "__typename": "User",
    "avatarUrl": "https://avatars1.githubusercontent.com/u/9222632?u=71f2ce221ca3e1cee709d5b93983d649cf20ba2b&v=4",
    "repositories": {
        "__typename": "RepositoryConnection",
        "totalCount": 40,
        "nodes": [
            {
                "__typename": "Repository",
                "name": "Laravel-CampaignMonitor"
            },
            {
                "__typename": "Repository",
                "name": "places-scout-php"
            },
            {
                "__typename": "Repository",
                "name": "places-scout-laravel"
            },
            {
                "__typename": "Repository",
                "name": "oauth2-adobe-sign"
            },
            {
                "__typename": "Repository",
                "name": "adobe-sign-php"
            },
            {
                "__typename": "Repository",
                "name": "adobe-sign-laravel"
            },
            {
                "__typename": "Repository",
                "name": "ProofLoyaltyDeploy"
            },
            {
                "__typename": "Repository",
                "name": "livenearcollege"
            },
            {
                "__typename": "Repository",
                "name": "yext-php"
            },
            {
                "__typename": "Repository",
                "name": "yext-laravel"
            }
        ]
    }
}
Next we’ll update src/queries/CurrentUser.graphql with some additional data.
query CurrentUser {
  viewer {
    avatarUrl
    id
    company
    repositories(first: 10) {
      totalCount
      nodes {
        name
        description
        createdAt
      }
    }
  }
}
Then run yarn generate and refresh the page. The code will reflect the new schema changes and the page should display the new information.
{
    "__typename": "User",
    "avatarUrl": "https://avatars1.githubusercontent.com/u/9222632?u=71f2ce221ca3e1cee709d5b93983d649cf20ba2b&v=4",
    "id": "MDQ6VXNlcjkyMjI2MzI=",
    "company": "Vincit",
    "repositories": {
        "__typename": "RepositoryConnection",
        "totalCount": 40,
        "nodes": [
            {
                "__typename": "Repository",
                "name": "Laravel-CampaignMonitor",
                "description": "A Laravel 5 wrapper for CampaignMonitor APIs",
                "createdAt": "2016-05-06T23:43:37Z"
            },
            {
                "__typename": "Repository",
                "name": "places-scout-php",
                "description": "PHP client for Places Scout's API",
                "createdAt": "2016-06-20T20:31:49Z"
            },
            {
                "__typename": "Repository",
                "name": "places-scout-laravel",
                "description": "Places Scout Client for Laravel 5",
                "createdAt": "2016-06-20T21:14:59Z"
            },
            {
                "__typename": "Repository",
                "name": "oauth2-adobe-sign",
                "description": "AdobeSign Provider for the OAuth 2.0 Client",
                "createdAt": "2016-07-14T23:58:24Z"
            },
            {
                "__typename": "Repository",
                "name": "adobe-sign-php",
                "description": "AdobeSign PHP Client",
                "createdAt": "2016-07-19T19:26:00Z"
            },
            {
                "__typename": "Repository",
                "name": "adobe-sign-laravel",
                "description": "Adobe Sign Client for Laravel 5",
                "createdAt": "2016-07-20T00:58:31Z"
            },
            {
                "__typename": "Repository",
                "name": "ProofLoyaltyDeploy",
                "description": "ProofLoyalty Circle CI and Convox Deployment",
                "createdAt": "2016-07-26T03:47:37Z"
            },
            {
                "__typename": "Repository",
                "name": "livenearcollege",
                "description": "A web portal to view properties and management companies near college campuses.",
                "createdAt": "2016-07-27T06:56:00Z"
            },
            {
                "__typename": "Repository",
                "name": "yext-php",
                "description": "Yext PHP Client",
                "createdAt": "2016-07-28T00:22:28Z"
            },
            {
                "__typename": "Repository",
                "name": "yext-laravel",
                "description": "Yext Client for Laravel 5",
                "createdAt": "2016-07-28T22:26:07Z"
            }
        ]
    }
}

Conclusion

Consider generating code from your GraphQL schemas in your next project. It can improve your development workflow by having less code you’d need to write which is less code you’d need to maintain and in turn would be less error prone. You can checkout the full source code here.

Want invites to cool events and things?

Boom, Newsletter sign up form.