November 8th 2018 (8 days ago)

Generate Javascript static types from GraphQL: TypeScript and Flow

Author profile picture
David Mráz (@david_mraz1)

Introduction

Javascript is usually considered as a untyped or weakly-typed language. I will not go into the discussion about this topic in this article. You can check out for example this stackoverflow thread for more information. We currently cannot prove the correlation between using statically/dynamically typed languages and number of defects in the system, but there are some evidences that errors occur less when using statically typed language. You can go more deeply into the topic in the following study. In addition statically typed languages can offer smart tooling integrated in your IDE, which enables you to perform more complex autocompletion and linting. Javascript is one of the most widely spread and demanding language. You can use it for frontend, backend or even mobile development. Javascript has definitely a lot of advantages, but as it is untyped it does not support static typings by default. Fortunately, we can enhance the Javascript language using the following tools to add static typings to our project:

Flow is open-sourced by Facebook and we are able to perform type checking with a Flow server while coding. On the other hand, TypeScript is maintained by Microsoft. TypeScript is older then Flow and even though Flow is evolving quite quickly, TypeScript still has better support with typings for more libraries, especially on the backend. In this article we will use TypeScript in our examples, as some libraries currently support only TypeScript generation.

Model example of manual static typing for GraphQL queries

Let’s first take a look at how to define our static typings manually. We will perform this operation with our example repository. In order to execute the code you can clone the repository with

git clone https://github.com/a7v8x/express-graphql-demo.git -b feature/6-static-typing

Then install the repository with npm

npm i

or with yarn

yarn install

and launch the GraphQL server with the following commands

yarn run dev

In the repository we use the following schema

input CreateUserInput {
  username: String!
  email: String
  phone: String
  firstName: String
  lastName: String
}
type Mutation {
  createUsers(input: [CreateUserInput]): [User]
}
type Query {
  user(id: ID!): User
  users: [User!]!
}
type User {
  id: ID!
  username: String!
  email: String
  phone: String
  firstName: String
  lastName: String
}

We would like to fetch the list of users. If you run your development server and you enter http://localhost:3000/graphql into the browser you will launch GraphiQL. We can then execute the following GraphQL document:

query getUsers {
  users {
    id
    firstName
    lastName
    username
    email
    phone
  }
}

Now let’s imagine that you have React-Apollo frontend and you want to include TypeScript type definitions for the response of users query to use them as your prop types. We can execute this query in GraphiQL and we will receive something like this

{
 "data": {
   "users": [
     {
        "id": "7b838108-3720-4c50-9de3-a7cc04af24f5",
        "firstName": "Berniece",
        "lastName": "Kris",
        "username": "Ana_Quigley",
        "email": null,
        "phone": "021.807.6991 x10598",
        "active": null
      },
      {
        "id": "66c9b0fd-7df6-4e2a-80c2-0e4f8cdd89b1",
        "firstName": "Bradly",
        "lastName": "Lind",
        "username": "Winona_Kulas12",
        "email": null,
        "phone": "863.803.0636 x9247",
        "active": null
      },
      {
        "id": "718590a1-33ac-4e61-9fef-b06916acd76b",
        "firstName": "Leila",
        "lastName": "Schowalter",
        "username": "Isabell.Kautzer",
        "email": null,
        "phone": "1-425-625-3887",
        "active": null
      }
   ]
 }
}

Then we will start writing our TypeScript type definitions. We will first need to manually check the schema so that our definitions are in sync with the data from the GraphQL server. We can write the definition for User type for example as follows:


export interface User {
  id: string;
  username: string;
  email?: string | null;
  phone?: string | null;
  firstName?: string | null;
  lastName?: string | null;
}

export interface getUsers {
  users: User[];
}

We need to manually check our schema to see what each type represents so that our static typings are in sync. Let’s say we want to add the required field active that will be typed as a Boolean. The updated User type in SDL (Schema Definition Language) will then be as follows:

type User {
  id: ID!
  username: String!
  email: String
  phone: String
  firstName: String
  lastName: String
  active: Boolean!
}

In order to fetch this field we will need to update our GraphQL query as well:

query getUsers {
  users {
    id
    firstName
    lastName
    username
    email
    phone
    active
  }
}

But what about our typings? We need to update the affected typings wherever they are being used. I believe that the biggest tradeoff for static typing is the increased time for development, the duplication of data structure and the possible friction that can occur with versioning our APIs. We cannot just update our code; we also need to add our typings manually and then update them after each change. It can lead to wrong typings and false errors if developers do not sync up immediately. These problems can be solved with automatic generation of types with GraphQL. Our GraphQL gateway will serve as our single source of truth, and static typing will be synced immediately on both the frontend and backend.

But how would we achieve that with GraphQL?

So now that we have talked about adding typings in our Javascript code manually, how can GraphQL help us automate that? As we mentioned, one of the biggest problems when defining typings is that the manual static typing can get too time-consuming and it is hard to keep everything in sync through versioning. We could already notice the connection between GraphQL’s type system and either the TypeScriptor Flow type systems. GraphQL’s type system isstrongly typed, and we can perform transformations fromGraphQL's type system to Flow or TypeScript type systems.

Visualization of our workflow

To get a better idea of how this works in practice let’s visualize how to transform the GraphQL schema into TypeScript or Flow typings. First let’s take a look at this graph

We will first define our GraphQL schema on our server. Then we need to generate static typings on the frontend to type the results and arguments for queries and mutations. We also need to generate separate static typings on the backend for our resolvers. Every time our GraphQL schema changes we also need to update our affected static typings. The GraphQL gateway is now the single source of truth for typings, but in order to remove the friction between definitions we need to introduce automation. This way we will not have to keep everything in sync manually.

Generating types on the frontend with Apollo CLI

Let’s generate TypeScript types for our responses from the GraphQL server. We will use a library called Apollo CLI. Please note, that Apollo tools introduced new version 2 with new API for executing commands. This article will be updated to this version in the following days, meanwhile you can tak a look at docs in their repository. In the example repository all the libraries here are installed as development dependencies and you can run commands with scripts defined in package.json, however, in the following paragraphs we will use the exact commands as it is more understandable for reader who do not want to clone the repository. Let's start with installing Apollo CLI globally with

npm i -g apollo@1.7.0

or using yarn

yarn add global apollo@1.7.0

If your GraphQL server is still running on port 3000 in development, we can perform introspection of the schema with Apollo. Please note that for security purposes you should disable introspection in production; it should therefore only work in a development environment. In this tutorial we will save our introspection results in a schema.json file. Now let’s execute the following command

apollo schema:download --endpoint http://localhost:3000/graphql schema.json

This command will run a schema introspection query on your specified endpoint and save your schema as the JSON file in schema.json. We can then generate your typings. We will take every*.graphql file that matches the specified pattern and validate it with our schema.json file. Based on each GraphQL file we will generate a new TypeScript type. In our case we only have one query -getUsers. In order to generate it we need to execute the following command

apollo codegen:generate --schema schema.json --target typescript __generated__

Apollo then generates *.ts files with types for each *.graphql requests into __generated__ folders and we are able to import them into our React-Apollo components. Please note that for the sake of simplicity we did not implement React components in the repository. If you would like to generate Flow types or other supported types you can only change --target parameter. The following TypeScript file for the getUsers query should now be available in the queries/__generated__

/* tslint:disable */
// This file was automatically generated and should not be edited.

// ====================================================
// GraphQL query operation: getUsers
// ====================================================

export interface getUsers_users {
  id: string;
  firstName: string | null;
  lastName: string | null;
  username: string;
  email: string | null;
  phone: string | null;
  active: boolean | null;
}

export interface getUsers {
  users: getUsers_users[];
}

and also CreateUsers mutation type is available in the same folder

/* tslint:disable */
// This file was automatically generated and should not be edited.

import { CreateUserInput } from "./../../__generated__/globalTypes";

// ====================================================
// GraphQL mutation operation: createUsers
// ====================================================

export interface createUsers_createUsers {
  id: string;
  firstName: string | null;
  lastName: string | null;
  username: string;
  phone: string | null;
  email: string | null;
  active: boolean | null;
}

export interface createUsers {
  createUsers: (createUsers_createUsers | null)[] | null;
}

export interface createUsersVariables {
  input: CreateUserInput[];
}

There is also additional file which is responsible for storing global typescript files again in __generated__ folder placed in the root of the project

/* tslint:disable */
// This file was automatically generated and should not be edited.

//==============================================================
// START Enums and Input Objects
//==============================================================

export interface CreateUserInput {
  username: string;
  email?: string | null;
  phone?: string | null;
  firstName?: string | null;
  lastName?: string | null;
}

//==============================================================
// END Enums and Input Objects
//==============================================================

I believe that the best way to operate is to generate type definitions every time you start your development server. The URL for the schema should be your staging server. Whenever you deploy a new version of your GraphQL schema on the backend you will fetch theschema.json again and regenerate your TypeScript typings. You will have static typings and *.graphql files close to your React/Angular components, and you will have everything structured by features with a unified folder structure for each feature. I believe that this is the best way to structure your project. Apollo CLI currently supports generating type definitions for TypeScript, Flow,Swift or Scala.

Generating typed-safe resolvers on your server with GraphQLCodeGen

As we have already mentioned, the type definitions generated from Apollo CLI are made by taking your *.graphql documents, validating them against your GraphQL schema stored in the JSON file, and then generating files with types definitions. Apollo CLIcurrently supports mainly generating type definitions based on your GraphQL documents. In order to generate server-side typings for your resolvers we need to use the library called GraphQLCodeGen . If we use this package we need to install two libraries: the GraphQLCodeGen itself and the corresponding custom template. The GraphQLCodeGen currently only supports TypeScript. We will use graphql-codegen-typescript-template. With yarn we are able to install both packages as follows

yarn add graphql-code-generator graphql-codegen-typescript-resolvers-template --dev

Now we should be able to execute the following command to generate our typings. Even though you can perform introspection with GraphQLCodeGen as well, we will again use our already saved schema.json file previously obtained by the apollo:download command.

gql-gen -s schema.json -t graphql-codegen-typescript-resolvers-template — out types/resolver-templates.gen.ts

These typings are then stored in resolver-types.gen.ts and look in our case as follows:

import { GraphQLResolveInfo } from "graphql";

export type Resolver<Result, Parent = any, Context = any, Args = never> = (
  parent: Parent,
  args: Args,
  context: Context,
  info: GraphQLResolveInfo
) => Promise<Result> | Result;

export interface ISubscriptionResolverObject<Result, Parent, Context, Args> {
  subscribe<R = Result, P = Parent>(
    parent: P,
    args: Args,
    context: Context,
    info: GraphQLResolveInfo
  ): AsyncIterator<R | Result>;
  resolve?<R = Result, P = Parent>(
    parent: P,
    args: Args,
    context: Context,
    info: GraphQLResolveInfo
  ): R | Result | Promise<R | Result>;
}

export type SubscriptionResolver<
  Result,
  Parent = any,
  Context = any,
  Args = never
> =
  | ((
      ...args: any[]
    ) => ISubscriptionResolverObject<Result, Parent, Context, Args>)
  | ISubscriptionResolverObject<Result, Parent, Context, Args>;

// ====================================================
// START: Typescript template
// ====================================================

// ====================================================
// Types
// ====================================================

export interface Query {
  user?: User | null;

  users: User[];
}
/** User type definition */
export interface User {
  id: string;

  username: string;

  email?: string | null;

  phone?: string | null;

  firstName?: string | null;

  lastName?: string | null;

  active?: boolean | null;
}

export interface Mutation {
  createUsers?: (User | null)[] | null;
}

// ====================================================
// InputTypes
// ====================================================

export interface CreateUserInput {
  username: string;

  email?: string | null;

  phone?: string | null;

  firstName?: string | null;

  lastName?: string | null;
}

// ====================================================
// Arguments
// ====================================================

export interface UserQueryArgs {
  id: string;
}
export interface CreateUsersMutationArgs {
  input?: (CreateUserInput | null)[] | null;
}

// ====================================================
// END: Typescript template
// ====================================================

// ====================================================
// Resolvers
// ====================================================

export namespace QueryResolvers {
  export interface Resolvers<Context = any, TypeParent = never> {
    user?: UserResolver<User | null, TypeParent, Context>;

    users?: UsersResolver<User[], TypeParent, Context>;
  }

  export type UserResolver<
    R = User | null,
    Parent = never,
    Context = any
  > = Resolver<R, Parent, Context, UserArgs>;
  export interface UserArgs {
    id: string;
  }

  export type UsersResolver<
    R = User[],
    Parent = never,
    Context = any
  > = Resolver<R, Parent, Context>;
}
/** User type definition */
export namespace UserResolvers {
  export interface Resolvers<Context = any, TypeParent = User> {
    id?: IdResolver<string, TypeParent, Context>;

    username?: UsernameResolver<string, TypeParent, Context>;

    email?: EmailResolver<string | null, TypeParent, Context>;

    phone?: PhoneResolver<string | null, TypeParent, Context>;

    firstName?: FirstNameResolver<string | null, TypeParent, Context>;

    lastName?: LastNameResolver<string | null, TypeParent, Context>;

    active?: ActiveResolver<boolean | null, TypeParent, Context>;
  }

  export type IdResolver<R = string, Parent = User, Context = any> = Resolver<
    R,
    Parent,
    Context
  >;
  export type UsernameResolver<
    R = string,
    Parent = User,
    Context = any
  > = Resolver<R, Parent, Context>;
  export type EmailResolver<
    R = string | null,
    Parent = User,
    Context = any
  > = Resolver<R, Parent, Context>;
  export type PhoneResolver<
    R = string | null,
    Parent = User,
    Context = any
  > = Resolver<R, Parent, Context>;
  export type FirstNameResolver<
    R = string | null,
    Parent = User,
    Context = any
  > = Resolver<R, Parent, Context>;
  export type LastNameResolver<
    R = string | null,
    Parent = User,
    Context = any
  > = Resolver<R, Parent, Context>;
  export type ActiveResolver<
    R = boolean | null,
    Parent = User,
    Context = any
  > = Resolver<R, Parent, Context>;
}

export namespace MutationResolvers {
  export interface Resolvers<Context = any, TypeParent = never> {
    createUsers?: CreateUsersResolver<
      (User | null)[] | null,
      TypeParent,
      Context
    >;
  }

  export type CreateUsersResolver<
    R = (User | null)[] | null,
    Parent = never,
    Context = any
  > = Resolver<R, Parent, Context, CreateUsersArgs>;
  export interface CreateUsersArgs {
    input?: (CreateUserInput | null)[] | null;
  }
}

and we are now ready to import them in our code. The GraphQLCodeGen is not the only promising tool for generating typings for resolvers. Recently, Prisma introduced the tool called GraphQLGen. It is still in the early stages and right now also only supportsTypeScript, though support for Flow and Reason are coming soon. There are a number of other libraries to help you generate static typings, but I have found these libraries to be the most promising. You need to take your time and decide what library will be best for your needs.

How to take it even further?

But what about not just generating static types? What about generating your own code? This is something the GraphQLCodeGen library can also accomplish. You can also generate your React/Angular components, scaffold resolvers, and more via custom templates. One template that I would like to mention is the graphql-codegen-typescript-react-apollo-template, which you can use to generate React-Apollo components immediately with TypeScript typings included. This can help you optimize your workflow even more. From the shallow point of view it seems to me that GraphQLCodeGen has more broad application then apollo-tools and with its custom templates it is more scalable, but currently only support TypeScript. We need take our time and see what will be the winner in the long term.

Summary

I believe that automatic type and code generation will be one of the biggest trends in GraphQL and Javascript. I especially recommend that you implement Apollo CLI , as in my experience is good enough to use in day-to-day schema versioning, and helps diminish unnecessary friction while syncing your static typing on the frontend with the GraphQL gateway. You can inject the command to regenerate types each time you launch your frontend development server. This way you will have your schema automatically in sync with your staging environment. A further advantage is that no extra communication between backend and frontend team members is required, since frontend engineers are notified about the changes in their types. We are additionally able to validate your queries and mutations in CI to avoid deploying queries and mutations on the frontend that do not comply to the current GraphQL schema. There is definitely space for improvement in libraries, especially for server side typings, but current implementations using GraphQLCodeGen or recent GraphQLGen is a promising step for more efficient workflows. I believe that automatic type generation of static types using GraphQL not only in Javascript has a bright future. It will allow us to spend less time writing boilerplate code and updating our types and more time on shipping high-quality typed products.

You can take a look at the repository with the examples from this article. I will try to keep this article and repository up-to-date with current developments and update it regularly with my experiences. If you are interested in GraphQL you can also check out our upcoming free GraphQL Language course. I am happy to receive any feedback, questions, requests or tips by email at david@atheros.ai.

Join our newsletter to get notified about new articles and get instant access to our upcoming free GraphQL Course