9. Codegen
15m

Overview

We're missing a bit of type safety in our . We need a way to translate our types into TypeScript types we can pass to our ListingAPI class and functions. Writing these types out by hand takes a lot of work, and it's easy for them to get out of sync with our . Fortunately, the GraphQL Code Generator is here to help!

In this lesson, we will:

  • Generate the server-side types for our functions and

GraphQL Codegen

The GraphQL Code Generator reads in a and generates TypeScript types we can use throughout our server. It keeps our TypeScript types from getting outdated as we make changes to the schema—allowing us to focus on developing our schema, rather than constantly updating type definitions! Our schema should be our 's source of truth, and the GraphQL Code Generator lets us treat it as such.

Installing dependencies

Let's start by navigating to a terminal in the root of our project, and installing three development dependencies: @graphql-codegen/cli, @graphql-codegen/typescript and @graphql-codegen/typescript-resolvers.

npm install -D @graphql-codegen/cli @graphql-codegen/typescript @graphql-codegen/typescript-resolvers

Next, let's create a codegen command that we can run from our package.json file. Add a new entry called generate under the scripts object, and set it to graphql-codegen.

package.json
"scripts": {
"compile": "tsc",
"dev": "ts-node-dev --respawn ./src/index.ts",
"start": "npm run compile && nodemon ./dist/index.js",
"test": "jest",
"generate": "graphql-codegen"
},

To run this command successfully, we need a file that will contain the instructions the Code Generator can follow.

The codegen file

Let's create a file called codegen.ts in the root of our project.

📦 intro-typescript
┣ 📂 src
┣ 📄 codegen.ts
┣ 📄 package.json
┣ 📄 README.md
┗ 📄 tsconfig.json

This file will import CodegenConfig from @graphql-codegen/cli, which we'll use to set all of our codegen details.

codegen.ts
import type { CodegenConfig } from "@graphql-codegen/cli";
const config: CodegenConfig = {};
export default config;

The first property we can set in the config object is where our schema lives. We'll pass in the file path relative to this current directory.

codegen.ts
const config: CodegenConfig = {
schema: "./src/schema.graphql",
};

Next, we define where the generated types should be outputted. Under a key called generates we'll add an object containing our desired path of src/types.ts. This will create a new file called types.ts in our server's src folder.

codegen.ts
const config: CodegenConfig = {
schema: "./src/schema.graphql",
generates: {
"./src/types.ts": {},
},
};

Lastly, let's tell the Code Generator which plugins to use, under a plugins key. This is an array that contains typescript and typescript-resolvers, to point to our two plugins.

codegen.ts
generates: {
"./src/types.ts": {
plugins: ["typescript", "typescript-resolvers"],
},
},

Generating types

We have everything we need to run the codegen command. Open up a terminal in the root directory and run the following command.

npm run generate

After a few moments, we should see that a new file has been added to our server's src directory: types.ts! Let's take a look.

At the bottom of the file, we'll find type Resolvers. This type is generated from looking at the objects in our schema; we have just two so far, Query and Listing.

src/types.ts
export type Resolvers<ContextType = any> = {
Listing?: ListingResolvers<ContextType>;
Query?: QueryResolvers<ContextType>;
};

The code starts to look a bit complex, but under the hood TypeScript has done all of the validation against our schema to provide us with types we can use to represent the following:

  1. The type of data that enters each of our s
  2. The properties on that data that the have access to
  3. The type of data that each function returns

As a result, we can use this Resolvers type to bring type safety—and some helpful code validation—to our resolvers object.

Back in resolvers.ts, we'll import this type and apply it to our resolvers object.

resolvers.ts
import { Resolvers } from "./types";
export const resolvers: Resolvers = {
Query: {
featuredListings: (_, __, { dataSources }) => {
return dataSources.listingAPI.getFeaturedListings();
},
},
};

This takes care of our red squiggly errors in our !

Adding type annotations in listing-api.ts

Our codegen output took care of generating precise TypeScript types for the in our schema—giving us a Listing type that we can use in our server code.

types.ts
/** A particular intergalactic location available for booking */
export type Listing = {
__typename?: "Listing";
/** Indicates whether listing is closed for bookings (on hiatus) */
closedForBookings?: Maybe<Scalars["Boolean"]["output"]>;
/** The cost per night */
costPerNight?: Maybe<Scalars["Float"]["output"]>;
id: Scalars["ID"]["output"];
/** The number of beds available */
numOfBeds?: Maybe<Scalars["Int"]["output"]>;
/** The listing's title */
title: Scalars["String"]["output"];
};

Let's return to our file: datasources/listing-api.ts. At the top of the file, we'll import the Listing type from types.ts.

datasources/listing-api.ts
import { Listing } from "../types";

Using this type, we can annotate the getFeaturedListings method with a return type of Promise<Listing[]>. We'll also update the type we pass into the this.get call to be Listing[] as well.

listing-api.ts
getFeaturedListings(): Promise<Listing[]> {
return this.get<Listing[]>("featured-listings");
}

Great! We're all set with type safety in our listing-api.ts file as well.

Now, you might be asking yourself, how do our functions actually get access to the we've just created? In other words, how do we populate that third positional they receive, contextValue, with the ListingAPI class we want to use?

Fantastic question! The answer is that we actually haven't hooked up that piece of our server just yet. We'll tackle this in the next lesson!

Practice

Which of the following is NOT included in the codegen output?

Key takeaways

  • Using the Code Generator, we can generate types from our schema and use them directly in our code. Hello, type safety!
  • To run codegen on our , we define a codegen.ts file in our project.
  • The codegen.ts file should specify the following properties at minimum:
    • schema, which specifies the path to the
    • generates, which specifies the path where the generated types should be outputted
    • plugins, which lists the plugins that should be incorporated into the codegen process
  • The typescript codegen plugin is the base plugin needed to generate TypeScript types from a .
  • The typescript-resolvers codegen plugin will augment the codegen process to include a Resolvers type we can use to annotate our with a single, comprehensive type.

Up next

Schema, , and ? We've got them all. But they're not yet working together. If we ran our server, it would have no clue about ListingAPI—and it also doesn't know about our ! Let's make sure all the pieces know about each other, and send our first queries for real data in the next lesson.

Previous

Share your questions and comments about this lesson

This course is currently in

beta
. Your feedback helps us improve! If you're stuck or confused, let us know and we'll help you out. All comments are public and must follow the Apollo Code of Conduct. Note that comments that have been resolved or addressed may be removed.

You'll need a GitHub account to post below. Don't have one? Post in our Odyssey forum instead.