8. 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 SpotifyApi 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 Playlist.

src/types.ts
export type Resolvers<ContextType = any> = {
Playlist?: PlaylistResolvers<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: {
featuredPlaylists: (_, __, { dataSources }) => {
return dataSources.spotifyAPI.getFeaturedPlaylists();
},
},
};

This takes care of our red squiggly errors in our !

Adding type annotations in spotify-api.ts

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

types.ts
/** A curated collection of tracks designed for a specific activity or mood. */
export type Playlist = {
__typename?: "Playlist";
/** Describes the playlist, what to expect and entices the user to listen. */
description?: Maybe<Scalars["String"]["output"]>;
/** The ID for the playlist. */
id: Scalars["ID"]["output"];
/** The name of the playlist. */
name: Scalars["String"]["output"];
};

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

datasources/spotify-api.ts
import { Playlist } from "../types";

Using this type, we can add a little bit more detail to our getFeaturedPlaylists method:

  1. We'll annotate the getFeaturedPlaylists method with a return type of Promise<Playlist[]>.
  2. We'll add some additional types to the properties we get from destructuring the response: playlists and items.
spotify-api.ts
async getFeaturedPlaylists(): Promise<Playlist[]> {
const response = await this.get<{
playlists: {
items: Playlist[];
};
}>("browse/featured-playlists");
return response?.playlists?.items ?? [];
}

Great! We're all set with type safety in our spotify-api 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 SpotifyAPI 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!

Key takeaways

  • Using the Code Generator, we can generate types from our schema and use them directly in our functions. Hello, type safety!

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 SpotifyAPI—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.