DEV Community

Cover image for Integrating GraphQL Code Generator in your frontend applications
TheGuildBot for The Guild

Posted on • Updated on • Originally published at the-guild.dev

Integrating GraphQL Code Generator in your frontend applications

This article was published on Wednesday, May 20, 2020 by Dotan Simha @ The Guild Blog

In this article we'll try to explain and demonstrate common patterns for frontend development with
GraphQL and GraphQL Code Generator.

Most patterns are general and can be applied to most popular frontend frameworks (React, Angular,
Vue, Stencil), and to popular GraphQL client libraries (Apollo / urql), due to extended support of
GraphQL Code Generator, and it's flexibility.

In this article, we'll cover the development workflow of frontend applications with TypeScript and
GraphQL Code Generator, suggest best-practices for GraphQL development for frontend developers,
and try to explain the idea behind it.

Why Do I Need GraphQL Code Generator in My Project?

Let's start by understanding the need for GraphQL Code Generator in your project.

If you are using TypeScript for frontend development, you probably aim to get the most out of the
TypeScript type-system, that means, that your preference should be to have typed variables all
across your application.

It starts with the code you write - UI components, services and business logic. You can also have
type safety for your third-party libraries (some built-in, and some with @types/... packages).

The idea behind type-safety is to make sure that your code can be statically analyzed and built,
before running it. It's useful because this way your can detect potential bugs before they happen in
runtime.

But What about the Data Your Fetch from External Services?

So if you are already using GraphQL, you probably know that your GraphQL API is typed, and built as
a GraphQL schema.

And it doesn't matter which language or platform is used to write your GraphQL API or schema - you
fetch it the same way into your frontend application - with GraphQL operations (query / mutation
/ subscriptions, and fragment) and probably over HTTP.

So if your GraphQL schema is typed already, and your GraphQL operations allow you to choose specific
fields from it (called Selection Set), why not leverage the schema and selection set and turn it
into TypeScript types?

Basic Data Fetching with GraphQL

Let's assume that we have the following simple GraphQL schema:

scalar Date

type Query {
  upcomingEvents: [Event!]!
}

type Event {
  id: ID!
  title: String!
  date: Date!
  location: Location!
}

type Location {
  name: String!
  address: String!
}
Enter fullscreen mode Exit fullscreen mode

And the client-side application consumes it with the following query:

query listEvents {
  upcomingEvents {
    id
    title
    date
  }
}
Enter fullscreen mode Exit fullscreen mode

If you client-side application only needs id, title and date from the Event type - you can
expect to have those fields in your GraphQL response.

You can also use it in your component code:

export const ListEvents = listEvents => {
  return (
    <ul className="list-events">
      {listEvents.map(event => (
        <li key={event.id}>
          {event.title} ({event.date})
        </li>
      ))}
    </ul>
  )
}
Enter fullscreen mode Exit fullscreen mode

In the example above we have a few issues that might be bugs in the future:

  1. We don't know the type of listEvents - and we can't really know it without creating a type for it manually (but this could also break, because the API could change).
  2. We can't be sure what are the actual types of id, title and date fields - it's any.
  3. We can't count of the fields to be there because they GraphQL query can change, and it's not connected to our code at all.
  4. If you'll try to access location of the event - you'll just get undefined because it's not part of the selection set.

With GraphQL Code Generator, you can have a full type safety, based on your GraphQL schema and
your GraphQL operations, and that means:

  1. You can tell what is the exact structure of listEvents, what could be null and enjoy auto-complete in your IDE.
  2. You can tell what is the data type of all fields.
  3. If your selection set changes, it being reflected automatically and you can detect issues while developing or building (instead while running).
  4. Trying to access fields that are not defined in your selection set will show an error in build time and in your IDE.

So those are the basic types that codegen can generate for your, and you can get those by using the
@graphql-codegen/typescript and @graphql-codegen/typescript-operations plugins of GraphQL Code
Generator.

But that's not all - you can generate much more - you can get React Hooks, Angular Services and
more.

How Do I Start?

You can start by
trying GraphQL Code Generator plugin in the live-demo here and
with the
Getting started with GraphQL Code Generator.

Tips & Best Practices When Using GraphQL Code Generator and TypeScript

Now that you understand why and how GraphQL Code Generator can help you, it's time to learn new
concepts that might simplify the way your consume GraphQL API, and improve your code quality.

Watch Mode

GraphQL Code Generator also comes with a built-in watch mode. You can use it from the CLI:

graphql-codegen --watch
Enter fullscreen mode Exit fullscreen mode

Or, set it in your codegen.yml file:

watch: true
schema: ...
Enter fullscreen mode Exit fullscreen mode

This way, each time you have changes for your GraphQL schema or GraphQL operations, GraphQL Code
Generator will be executed again and update the generated files.

Generate More than Just Types

GraphQL Code Generator can generate more than just TypeScript types. It can automate some of your
GraphQL development workflow, generate common practices for data fetching, and add type-safety to
code you usually need to write manually.

Beside TypeScript types, here's a list and examples of part of GraphQL Codegen capabilities:

Dump Remote Schema to a Local File

If your GraphQL schema is only available for you using an HTTP endpoint, you can always get a copy
of it locally. This is useful for better IDE experience.

You can do it with the @graphql-codegen/schema-ast plugin, and the following configuration:

schema: http://YOUR_SERVER/graphql
generates:
  ./src/schema.graphql:
    plugins:
      - schema-ast
Enter fullscreen mode Exit fullscreen mode

Save Local GraphQL Introspection

GraphQL schema can be represented in many ways. One of the is
introspection.

You can save a local copy of your schema introspection using @graphql-codegen/introspection and
the following:

schema: YOUR_SCHEMA_PATH
generates:
  ./src/schema.json:
    plugins:
      - introspection
Enter fullscreen mode Exit fullscreen mode

Add Custom Content to Output Files

I wish you to add custom content to the codegen output files, you can use @graphql-codegen/add
plugin, and add your content this way:

schema: YOUR_SCHEMA_PATH
generates:
  ./src/types.ts:
    plugins:
      - add: '// THIS FILE IS GENERATED, DO NOT EDIT!'
      - typescript
Enter fullscreen mode Exit fullscreen mode

React & Apollo: Generate Hooks

You can generate ready-to-use React hooks for your GraphQL operations, with the following
configuration:

schema: SCHEMA_PATH_HERE
documents: './src/**/*.graphql'
generates:
  src/generated-types.tsx:
    config:
      withHooks: true
    plugins:
      - typescript
      - typescript-operations
      - typescript-react-apollo
Enter fullscreen mode Exit fullscreen mode

And then use it in your code:

import React from 'react'
import { useMyQuery } from './generated-types'

export const MyComponent: React.FC = () => {
  const { data, loading, error } = useMyQuery()

  // `data` is now fully typed based on your GraphQL query
  return <> ... </>
}
Enter fullscreen mode Exit fullscreen mode

React & Apollo: Generate HOC (High-Order-Component)

You can generate ready-to-use React HOC for your GraphQL operations, with the following
configuration:

schema: SCHEMA_PATH_HERE
documents: './src/**/*.graphql'
generates:
  src/generated-types.tsx:
    config:
      withHOC: true
    plugins:
      - typescript
      - typescript-operations
      - typescript-react-apollo
Enter fullscreen mode Exit fullscreen mode

And then use it in your code:

import React from 'react';
import { withMyQuery } from './generated-types';

const MyViewComponent: React.FC = ({ data, loading, error }) => {
  // `data` is now fully typed based on your GraphQL query

  return (<> ... </>);
};

export const MyComponent = withMyQuery({
  variables: { ... }
})(MyViewComponent);
Enter fullscreen mode Exit fullscreen mode

React & Apollo: Generate Components

You can generate ready-to-use React data components for your GraphQL operations, with the following
configuration:

schema: SCHEMA_PATH_HERE
documents: './src/**/*.graphql'
generates:
  src/generated-types.tsx:
    config:
      withComponent: true
    plugins:
      - typescript
      - typescript-operations
      - typescript-react-apollo
Enter fullscreen mode Exit fullscreen mode

And then use it in your code:

import React from 'react';
import { MyQueryComponent } from './generated-types';

export const MyComponent: React.FC = ({ data, loading, error }) => {

  return (
    <MyQueryComponent variables={...}>
      {
        ({ data, loading, error }) => {
          // `data` is now fully typed based on your GraphQL query

          return (<> ... </>)
        }
      }
    </MyQueryComponent>
  );
};
Enter fullscreen mode Exit fullscreen mode

Angular & Apollo: Generate Services

You can generate ready-to-use Angular Services for your GraphQL operations, with the following
configuration:

schema: SCHEMA_PATH_HERE
documents: './src/**/*.graphql'
generates:
  src/generated-types.ts:
    config:
      withHooks: true
    plugins:
      - typescript
      - typescript-operations
      - typescript-apollo-angular
Enter fullscreen mode Exit fullscreen mode

And then use it in your code:

import { MyFeedGQL, MyFeedQuery } from './generated-types'

@Component({
  selector: 'feed',
  template: `
    <h1>Feed:</h1>
    <ul>
      <li *ngFor="let item of feed | async">{{ item.id }}</li>
    </ul>
  `
})
export class FeedComponent {
  feed: Observable<MyFeedQuery['feed']>

  constructor(feedGQL: MyFeedGQL) {
    this.feed = feedGQL.watch().valueChanges.pipe(map(result => result.data.feed))
  }
}
Enter fullscreen mode Exit fullscreen mode

React & urql: Generate Hooks

If you are using urql as your GraphQL client, you can
generate ready-to-use React hooks for your GraphQL operations, with the following configuration:

schema: SCHEMA_PATH_HERE
documents: './src/**/*.graphql'
generates:
  src/generated-types.tsx:
    config:
      withHooks: true
    plugins:
      - typescript
      - typescript-operations
      - typescript-urql
Enter fullscreen mode Exit fullscreen mode

And then use it in your code:

import React from 'react'
import { useMyQuery } from './generated-types'

export const MyComponent: React.FC = () => {
  const { data, loading, error } = useMyQuery()

  // `data` is now fully typed based on your GraphQL query

  return <> ... </>
}
Enter fullscreen mode Exit fullscreen mode

This plugin can also generate HOC or data Component, based on your preference ;)

Vue.js & Apollo: Generate Composition Functions

If you are using Vue.js with @vue/apollo-composable your GraphQL client,
you can generate composition functions based on your GraphQL operations:

schema: SCHEMA_PATH_HERE
documents: './src/**/*.graphql'
generates:
  src/generated-types.ts:
    config:
      withHooks: true
    plugins:
      - typescript
      - typescript-operations
      - typescript-vue-apollo
Enter fullscreen mode Exit fullscreen mode

And then use it in your code:

<template>
  <div>
    {{ result.feed.id }}
  </div>
</template>

<script lang="ts">
import { createComponent } from '@vue/composition-api'
import { useTestQuery } from '../generated-types'

export default createComponent({
  setup() {
    const { result } = useMessagesQuery()

    return { result }
  }
})
</script>
Enter fullscreen mode Exit fullscreen mode

Apollo: Type-Safe refetchQueries

If you are using Apollo Client, and you wish to refetch a query when a mutation is done, you can do
add
@graphql-codegen/named-operations-object
plugin to your setup.

It will generate a const object that contains a list of your GraphQL operation names, as found by
the codegen. This is useful because if you'll change the name of your operation, you'll know about
it in build time, and you'll be able to update it:

This is how to configure it:

schema: SCHEMA_PATH_HERE
documents: './src/**/*.graphql'
generates:
  src/generated-types.ts:
    plugins:
      - typescript
      - typescript-operations
      - named-operations-object
Enter fullscreen mode Exit fullscreen mode

And then use it in your code:

import { client } from './apollo'; // this is your Apollo Client instance, for example
import { addTodoMutation, namedOperations } from './generated-types';

client.mutate({
  query: addTodoMutation,
  variables: { ... },
  refetchQueries: [
    // If you'll change or remove that operation, this will fail during build time!
    namedOperations.Query.listTodo,
  ]
})
Enter fullscreen mode Exit fullscreen mode


You can use it with any other wrapper of Apollo-Client, such as apollo-angular or
react-apollo.

Apollo: Auto-Generated fragmentMatcher / possibleTypes

If you are using Apollo-Client and your schema contains GraphQL union or interface, you'll need
to provide fragmentMatcher to your Apollo store instance.

This is needed in order to improve performance of Apollo store.
You can read more about this here.

You can generate it using the following configuration:

schema: YOUR_SCHEMA_PATH
generates:
  ./src/fragment-matcher.ts:
    plugins:
      - fragment-matcher
Enter fullscreen mode Exit fullscreen mode

And then pass it directly to your Apollo instance:

import { InMemoryCache } from '@apollo/client'
// generated by Fragment Matcher plugin
import introspectionResult from '../fragment-matcher'

const cache = new InMemoryCache({
  possibleTypes: introspectionResult.possibleTypes
})
Enter fullscreen mode Exit fullscreen mode

Name Your Operations

It's highly important to name your GraphQL operations, because otherwise it will be difficult for
your GraphQL client to cache and manage it. It will also make it difficult for the codegen to create
easy-to-use types, and it will fallback to Unnamed_Operation_.

✅ Do:

query myOperationNameHere {
  ...
}
Enter fullscreen mode Exit fullscreen mode

❌ Don't:

query {
  ...
}
Enter fullscreen mode Exit fullscreen mode


Tip: Duplicate Names

Ensure you have unique names for your operations.

Libraries like Apollo Client will have issues and unexpected behavior if you re-use the same
operation name, and GraphQL Code Generator will throw an error in case of name duplications.

Write Your Operations and Fragments in .graphql Files

You can manage your GraphQL operations in .graphql files, without worrying about loading it into
your application with Webpack loaders or anything else. Also, Most IDEs has better support for
autocomplete inside .graphql files.

GraphQL Code Generator plugins for frontend frameworks integrations (such as
typescript-react-apollo / typescript-apollo-angular) are automatically creates an executable
copy (DocumentNode) of your GraphQL operations in the generated code file, and it will
automatically include it withing your wrapper call.

It will add that to the output file with Document suffix, and FragmentDoc for fragments.

So you can maintain your operations in .graphql files, but import it from generated code file:

// MyQueryDocument and MyUserFragmentDoc are parsed `DocumentNode`
import { MyQueryDocument, MyUserFragmentDoc } from './generated-types'
Enter fullscreen mode Exit fullscreen mode


Tip: No need to handle imports

If you have a query that uses a fragment, you can just use the fragment spread as-is, without the
need to import it or maintain it in the same file.

For example:

```graphql filename="user.query.graphql"
query user {
userById {
...UserFields # We don't need to import this, just use the name
}
}






  ```graphql filename="userfields.fragment.graphql"
  fragment UserFields on User {
    id
    name
  }
Enter fullscreen mode Exit fullscreen mode

And if you'll import UserQueryDocument from your generated file, it will have the fragment
concatenated automatically.

Fragment per Component

If you wish to have a simple way to manage your application complexity with multiple queries and
fragments, consider to have small fragments that defines the needs of your components.

Consider the following structure for example (for a list and item implementation):

src/
├── generated-types.tsx
├── list/
├──── todo-list.tsx
├──── todo-list.query.graphql
├── list-item/
├──── todo-item.tsx
├──── todo-item.fragment.graphql
├── todo-details/
├──── todo-details.tsx
├──── todo-details.fragment.graphql
├── user-profile/
├──── profile-page.tsx
├──── me.query.graphql
├──── authenticated-user.fragment.graphql
Enter fullscreen mode Exit fullscreen mode

Then, your GraphQL query files can just build it's self based on the nested fragments it needs:

```graphql filename="todo-list.query.graphql"
query todoList {
todos {
...TodoItemFields
...TodoDetailsFields
}
}






```graphql filename="me.query.graphql"
query me {
  me {
    ...AuthenticatedUserFields
  }
}
Enter fullscreen mode Exit fullscreen mode

And then, GraphQL Code Generator will generate a matching TypeScript type per each component, based
on the fragment or query that it needs.

So you can use the generated fragment type as the input for your components, and pass it directly
from the parent component easily, with type-safety:

```tsx filename="todo-list.tsx"
import React from 'react'
import { useTodoList } from '../generated-types'
import { TodoItem } from './todo-item'

export const TodoList: React.FC = () => {
const { data, loading, error } = useTodoList()

return (
<>
{data.todos.map(todo => (

))}
</>
)
}






```tsx filename="todo-item.tsx"
import React from 'react'
import { TodoItemFieldsFragment } from '../generated-types'

export const TodoItem: React.FC = (todo: TodoItemFieldsFragment) => {
  return <div>{todo.title}</div>
}
Enter fullscreen mode Exit fullscreen mode


Please have some judgment before creating fragments, it should represent data structure that is
specific per component. Don't abuse this mechanism by creating fragments with a single field. Try
to group it in a way that matches your components needs.

Access to Nested Generated Types

If you are already familiar with plugins such as @graphql-codegen/typescript-operations output
structure, you probably already know that it's built on operations and fragments.

It means that each GraphQL query and each GraphQL fragment that you have, will be converted into
a single TypeScript type.

That means, that accessing nested fields in your generated TypeScript types might looks a bit
complex at the beginning.

Consider the following query:

query userById($userId: ID!) {
  user(id: $userId) {
    id
    profile {
      age
      name {
        first
        last
      }
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

The @graphql-codegen/typescript-operations plugin output for that query will be:

export type UserByIdQuery = { __typename?: 'Query' } & {
  user?: Maybe<
    { __typename?: 'User' } & Pick<User, 'id'> & {
        profile?: Maybe<
          { __typename?: 'Profile' } & Pick<Profile, 'age'> & {
              name: { __typename?: 'Name' } & Pick<Name, 'first' | 'last'>
            }
        >
      }
  >
}
Enter fullscreen mode Exit fullscreen mode

Accessing the actual TypeScript type of user.profile.name.first might look a bit intimidating, but
there are several things you can do to simplify the access to it:

  • Best solution: use fragments - if you'll use fragments for the User fields and for Profile fields, you'll break down the types into smaller pieces (see previous tip).
  • Use TypeScript type system: type FirstName = UserByIdQuery['user']['profile']['name']['first'].
  • You can also do it with Pick: type FirstName = Pick<UserByIdQuery, ['user', 'profile', 'name', 'first']>.


Tip: Hate Pick in your generated files?

The @graphql-codegen/typescript-operations is the TypeScript representation of your GraphQL
selection set. Just like selection set chooses fields from the GraphQL schema,
typescript-operations picks fields from typescript plugin (which is the representation of your
GraphQL schema).

If you wish to have simpler TypeScript output, you can set preResolveTypes: true in your
configuration, and it will prefer to use the primitive TypeScript type when possible.

Top comments (0)