Overview
We're missing a bit of type safety in our GraphQL server. We need a way to translate our GraphQL schema types into TypeScript types we can pass to our ListingAPI
class and resolver 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 GraphQL schema. Fortunately, the GraphQL Code Generator is here to help!
In this lesson, we will:
- Generate the server-side types for our resolver functions and data source
GraphQL Codegen
The GraphQL Code Generator reads in a GraphQL schema 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 GraphQL server'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
.
"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 GraphQL 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.
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.
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.
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.
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
.
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:
- The type of data that enters each of our resolvers
- The properties on that data that the resolvers have access to
- The type of data that each resolver 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.
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 resolvers!
Adding type annotations in listing-api.ts
Our codegen output took care of generating precise TypeScript types for the object types in our schema—giving us a Listing
type that we can use in our server code.
/** 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 data source file: datasources/listing-api.ts
. At the top of the file, we'll import the Listing
type from types.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 variable we pass into the this.get
call to be Listing[]
as well.
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 resolver functions actually get access to the data source we've just created? In other words, how do we populate that third positional argument 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
Key takeaways
- Using the GraphQL Code Generator, we can generate types from our schema fields and use them directly in our code. Hello, type safety!
- To run codegen on our GraphQL schema, 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 GraphQL schemagenerates
, which specifies the path where the generated types should be outputtedplugins
, 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 GraphQL schema. - The
typescript-resolvers
codegen plugin will augment the codegen process to include aResolvers
type we can use to annotate our resolvers with a single, comprehensive type.
Up next
Schema, data sources, and resolvers? 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 resolvers! Let's make sure all the pieces know about each other, and send our first queries for real data in the next lesson.
Share your questions and comments about this lesson
This course is currently in
You'll need a GitHub account to post below. Don't have one? Post in our Odyssey forum instead.