Learn something new with GraphQL every week

Project Setup

#4 Install and configure GraphQL Code Generator

Install and configure GraphQL Code Generator
Julian Mayorga
Instructor
Julian
Jamie Barton
Instructor
Jamie
githubSource code

As we seen previously, the resolvers we defined aren't type-safe, which often leads to bugs at runtime.

Now install and configure the GraphQL Code Generator to automatically create some types we can use throughout our project.

The GraphQL Code Generator works by invoking plugins across our defined file(s). We'll be using the following plugins:

  • @graphql-codegen/typescript
  • @graphql-codegen/typescript-resolvers

We'll need to add further plugins later when we build the frontend. Let's use the initialization wizard to generate a config file.

At the terminal, run the following:

npx graphql-codegen init

The wizard will ask us a bit more about our application:

  • What type of application are you building?Backend - API or Server

  • Where is your schema?schema.graphql

  • Pick pluginsTypeScript, TypeScript Resolvers

  • Where to write the outputtypes.ts

  • Do you want to generate an introspection file?No

  • How to name the config file?codegen.yml

  • What script in package.json should run the codegen?codegen

Once you provide the answers to all of these questions, you should see the new file codegen.yml in the root of your project, and codegen inside of scripts and some new devDependencies within package.json.

If you open codegen.yml it should look something like:

overwrite: true
schema: schema.graphql
documents: null
generates:
  types.ts:
    plugins:
      - "typescript"
      - "typescript-resolvers"

Before we continue, let's install the dependencies we configured.

npm install

Now invoke the codegen script to generate our types.ts file.

At the command line, run the following:

npm run codegen

You should now see the file types.ts in the root of the project.

Preview file contents
import { GraphQLResolveInfo } from 'graphql';
export type Maybe<T> = T | null;
export type InputMaybe<T> = Maybe<T>;
export type Exact<T extends { [key: string]: unknown }> = { [K in keyof T]: T[K] };
export type MakeOptional<T, K extends keyof T> = Omit<T, K> & { [SubKey in K]?: Maybe<T[SubKey]> };
export type MakeMaybe<T, K extends keyof T> = Omit<T, K> & { [SubKey in K]: Maybe<T[SubKey]> };
export type RequireFields<T, K extends keyof T> = Omit<T, K> & { [P in K]-?: NonNullable<T[P]> };
/** All built-in and custom scalars, mapped to their actual values */
export type Scalars = {
  ID: string;
  String: string;
  Boolean: boolean;
  Int: number;
  Float: number;
};

export type Cart = {
\_\_typename?: 'Cart';
id: Scalars['ID'];
totalItems: Scalars['Int'];
};

export type Query = {
\_\_typename?: 'Query';
cart?: Maybe<Cart>;
};

export type QueryCartArgs = {
id: Scalars['ID'];
};

export type ResolverTypeWrapper<T> = Promise<T> | T;

export type ResolverWithResolve<TResult, TParent, TContext, TArgs> = {
resolve: ResolverFn<TResult, TParent, TContext, TArgs>;
};
export type Resolver<TResult, TParent = {}, TContext = {}, TArgs = {}> = ResolverFn<TResult, TParent, TContext, TArgs> | ResolverWithResolve<TResult, TParent, TContext, TArgs>;

export type ResolverFn<TResult, TParent, TContext, TArgs> = (
parent: TParent,
args: TArgs,
context: TContext,
info: GraphQLResolveInfo
) => Promise<TResult> | TResult;

export type SubscriptionSubscribeFn<TResult, TParent, TContext, TArgs> = (
parent: TParent,
args: TArgs,
context: TContext,
info: GraphQLResolveInfo
) => AsyncIterable<TResult> | Promise<AsyncIterable<TResult>>;

export type SubscriptionResolveFn<TResult, TParent, TContext, TArgs> = (
parent: TParent,
args: TArgs,
context: TContext,
info: GraphQLResolveInfo
) => TResult | Promise<TResult>;

export interface SubscriptionSubscriberObject<TResult, TKey extends string, TParent, TContext, TArgs> {
subscribe: SubscriptionSubscribeFn<{ [key in TKey]: TResult }, TParent, TContext, TArgs>;
resolve?: SubscriptionResolveFn<TResult, { [key in TKey]: TResult }, TContext, TArgs>;
}

export interface SubscriptionResolverObject<TResult, TParent, TContext, TArgs> {
subscribe: SubscriptionSubscribeFn<any, TParent, TContext, TArgs>;
resolve: SubscriptionResolveFn<TResult, any, TContext, TArgs>;
}

export type SubscriptionObject<TResult, TKey extends string, TParent, TContext, TArgs> =
| SubscriptionSubscriberObject<TResult, TKey, TParent, TContext, TArgs>
| SubscriptionResolverObject<TResult, TParent, TContext, TArgs>;

export type SubscriptionResolver<TResult, TKey extends string, TParent = {}, TContext = {}, TArgs = {}> =
| ((...args: any[]) => SubscriptionObject<TResult, TKey, TParent, TContext, TArgs>)
| SubscriptionObject<TResult, TKey, TParent, TContext, TArgs>;

export type TypeResolveFn<TTypes, TParent = {}, TContext = {}> = (
parent: TParent,
context: TContext,
info: GraphQLResolveInfo
) => Maybe<TTypes> | Promise<Maybe<TTypes>>;

export type IsTypeOfResolverFn<T = {}, TContext = {}> = (obj: T, context: TContext, info: GraphQLResolveInfo) => boolean | Promise<boolean>;

export type NextResolverFn<T> = () => Promise<T>;

export type DirectiveResolverFn<TResult = {}, TParent = {}, TContext = {}, TArgs = {}> = (
next: NextResolverFn<TResult>,
parent: TParent,
args: TArgs,
context: TContext,
info: GraphQLResolveInfo
) => TResult | Promise<TResult>;

/\*_ Mapping between all available schema types and the resolvers types _/
export type ResolversTypes = {
Boolean: ResolverTypeWrapper<Scalars['Boolean']>;
Cart: ResolverTypeWrapper<Cart>;
ID: ResolverTypeWrapper<Scalars['ID']>;
Int: ResolverTypeWrapper<Scalars['Int']>;
Query: ResolverTypeWrapper<{}>;
String: ResolverTypeWrapper<Scalars['String']>;
};

/\*_ Mapping between all available schema types and the resolvers parents _/
export type ResolversParentTypes = {
Boolean: Scalars['Boolean'];
Cart: Cart;
ID: Scalars['ID'];
Int: Scalars['Int'];
Query: {};
String: Scalars['String'];
};

export type CartResolvers<ContextType = any, ParentType extends ResolversParentTypes['Cart'] = ResolversParentTypes['Cart']> = {
id?: Resolver<ResolversTypes['ID'], ParentType, ContextType>;
totalItems?: Resolver<ResolversTypes['Int'], ParentType, ContextType>;
\_\_isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
};

export type QueryResolvers<ContextType = any, ParentType extends ResolversParentTypes['Query'] = ResolversParentTypes['Query']> = {
cart?: Resolver<Maybe<ResolversTypes['Cart']>, ParentType, ContextType, RequireFields<QueryCartArgs, 'id'>>;
};

export type Resolvers<ContextType = any> = {
Cart?: CartResolvers<ContextType>;
Query?: QueryResolvers<ContextType>;
};

If you inspect this file closely, you'll see all of the types generated for our GraphQL schema, as well as the resolvers.

You should see the following type Resolvers:

export type Resolvers<ContextType = any> = {
  Cart?: CartResolvers<ContextType>;
  Query?: QueryResolvers<ContextType>;
};
Warning

You'll notice the use of any for the ContextType above. We'll fix this later when we define our server context.

We'll now update pages/api/index.ts to import this type:

import { Resolvers } from "../../types";

Then update the resolvers object to use Resolvers as its type:

const resolvers: Resolvers = {
  Query: {
    cart: (_, { id }) => {
      return {
        id,
        totalItems: 0,
      };
    },
  },
};

You will now see the TypeScript warnings are gone! 🥳

That's all that we need from GraphQL Code Generator for now. We'll see how something as simple as typing the resolvers benefits us when we add more to our schema and resolvers, later.