Learning LogoLearning Logo
Learning LogoLearning Logo
Copy article linkShare Article on TwitterShare article on LinkedInShare article on FacebookShare article on Pinterest

Generate Javascript static types from GraphQL: TypeScript and Flow

David Mráz
David Mráz@davidm_ai
Author's Linkedin accountAuthor's Instagram accountAuthor's Twitter accountAuthor's Twitter account
Development

Introduction

Javascript is usually considered as an 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 seems to me that the ecosystem is much better. TypeScript has better support with typings for more libraries, especially on the backend. In this article we will use solely TypeScript in our examples.

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 start with this simple schema:

type Subscription {
id: ID!
email: String!
}
input SubscribeInput {
email: String!
}
type Mutation {
subscribe(input: SubscribeInput!): Subscription!
}
type Query {
subscriptions: [Subscription]
}

We would like to fetch the list of subscribed users. If you have your development server running you can move to GraphQL Playground. We can then execute the following GraphQL document:

query getSubscriptions {
subscriptions {
id
email
}
}

Now if you use our example repository. Let's say we would like to include generate our TypeScript types each time we change our GraphQL schema and propagate these changes to your development workflow, so that you can use it directly in your frontend components We can execute this query in GraphiQL and we will receive something like this

{
"data": {
"subscriptions": [
{
"id": "02b7d240-0d44-11ea-bbff-1b2383f1b30b",
"email": "david@atheros.ai"
}
]
}
}

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 Subscriptions query as follows:

export interface Subscribe {
id: string;
email: string;
}
export interface GetSubscriptions {
subscriptions: Subscribe[];
}

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 source that will be typed as an enum value. The updated Subscription type in SDL (Schema Definition Language) will then be as follows:

enum SourceEnum {
ARTICLE
HOME_PAGE
}
type Subscription {
id: ID!
email: String!
source: SourceEnum!
}

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

query getSubscriptions {
subscriptions {
id
email
source
}
}

But what about our typings? We need to update the affected typings wherever they are being used. I believe that the biggest trade off 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 only 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.

How would we achieve that with GraphQL?

So now that we have talked about adding typings in our TypeScript 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 type system and either the TypeScript or Flow type systems. GraphQL’s type system is strongly typed, and we can perform transformations from GraphQL type system to TypeScript type systems.

To get a better idea of how this works in practice let’s visualize how to transform the GraphQL types into TypeScript types. 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 GraphQL CodeGen

Let’s generate TypeScript types for our responses from the GraphQL server. We will use a library called GraphQL CodeGen.

We will use our example repository. In order to execute the code you can clone the repository with

git clone git@github.com:atherosai/next-react-graphql-apollo-hooks.git

install dependencies with

npm i

and start the server in development with

npm run dev

GraphQL CodeGen yaml file

GraphQLCodeGen works on modular bases. There is a lot of plug-ins that enables you apply GraphQL CodeGen library to many different applications. For now we will use just two plug-ins

  • TypeScript operations plugin: enables to generate types for mutations and queries
  • TypeScript plugin: generate basic types from the schema
schema: 'server/schema/typeDefs.ts'
documents: 'components/**/*.graphql'
generates:
generated/typescript-operations.ts:
- typescript-operations
- typescript

We can see that we need to first define a way how to retrieve the information about the schema. This is done in the schema field and in our case we used typeDef file, where schema in SDL is written. GraphQLCodeGen will then apply schema introspection and uses the results to generate TypeScript types. If your GraphQL server is running on port 3000, you can also perform introspection directly on the endpoint. Please note that for security purposes you should disable introspection in production; it should therefore only work in a development environment. We have also defined our path to GraphQL documents. In the example repository we store our GraphQL queries and mutations at our React component and the pattern above will validate all of them against our GraphQL schema and then generate TypeScript types for frontend. The last lines in the our GraphQLCodeGen config defines output path of the generated types and plug-ins used.

If you have installed graphql-codegen globally and you are in the folder of our example repository you can just execute:

graphql-codegen

otherwise you can use our npm script command:

npm run gen:schema

This command will run a schema introspection query, take every *.graphql file that matches the specified pattern and validate it with our GraphQL schema. Based on each GraphQL file we will generate a new TypeScript types.

TypeScript output and how to use it in your React components

GraphQLCodeGen generated .ts, .d.ts files with types for each *.graphql requests into generated folder 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

export type Maybe<T> = T | null;
export type SubscribeMutationVariables = {
input: SubscribeInput
};
export type SubscribeMutation = (
{ __typename?: 'Mutation' }
& { subscribe: (
{ __typename?: 'Subscription' }
& Pick<Subscription, 'id' | 'email' | 'source'>
) }
);
export type SubscriptionsQueryVariables = {};
export type SubscriptionsQuery = (
{ __typename?: 'Query' }
& { subscriptions: Maybe<Array<Maybe<(
{ __typename?: 'Subscription' }
& Pick<Subscription, 'id' | 'email' | 'source'>
)>>> }
);
/** 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 Mutation = {
__typename?: 'Mutation',
subscribe: Subscription,
};
export type MutationSubscribeArgs = {
input: SubscribeInput
};
export type Query = {
__typename?: 'Query',
subscriptions?: Maybe<Array<Maybe<Subscription>>>,
};
export enum SourceEnum {
Article = 'ARTICLE',
HomePage = 'HOME_PAGE'
}
export type SubscribeInput = {
email: Scalars['String'],
source: SourceEnum,
};
export type Subscription = {
__typename?: 'Subscription',
id: Scalars['ID'],
email: Scalars['String'],
source: SourceEnum,
};

I believe that the best way to operate is to generate type definitions every time you change your GraphQL schema. This will make your types up-to-date and you will avoid mismatches on your frontend. Now let's use our generated types for our React components in the repository. In our project we have one query for fetching subscriptions

query getSubscriptions {
subscriptions {
id
email
source
}
}

On the client we are rendering our results in the table with two columns email and source. We use Apollo client and React Hooks for our data fetching. React component is written as follows:

import React from 'react';
import get from 'lodash.get';
import uuid from 'uuid/v1';
import { useQuery } from '@apollo/react-hooks';
import SUBSCRIPTIONS_QUERY from './SUBSCRIPTIONS.graphql';
import { SubscriptionsQuery, SubscriptionsQueryVariables } from '../../../__generated__/typescript-operations';
import s from './SubscriptionsTable.scss';
const SubscriptionsTable: React.FunctionComponent = () => {
const { data, loading, error } = useQuery<SubscriptionsQuery,
SubscriptionsQueryVariables>(SUBSCRIPTIONS_QUERY);
if (loading) return <>Loading...</>;
if (error) return <>{`Error! ${error.message}`}</>;
return (
<div className={s.SubscriptionTable}>
<table>
<thead>
<tr>
<th>Email</th>
<th>Source</th>
</tr>
</thead>
<tbody>
{data && data.subscriptions && data.subscriptions.map((subscription) => (
<tr key={get(subscription, 'id', uuid())}>
<td>
{get(subscription, 'email')}
</td>
<td>
{get(subscription, 'source')}
</td>
</tr>
))}
</tbody>
</table>
</div>
);
};
export default SubscriptionsTable;

Apollo client is written in TypeScript so it has good support for handling your types. We are passing our generated types in the useQuery hook. Our second GraphQL operation is subscribe mutation. Our component is written as follows:

/* eslint-disable jsx-a11y/label-has-for */
import React from 'react';
import { useMutation } from '@apollo/react-hooks';
import {
Formik, ErrorMessage, Form, Field,
} from 'formik';
import * as Yup from 'yup';
// eslint-disable-next-line import/no-extraneous-dependencies
import { FetchResult } from 'apollo-link';
import get from 'lodash.get';
import s from './Subscription.scss';
import SUSCRIBE_MUTATION from './SUBSCRIBE.graphql';
import SUBSCRIPTIONS_QUERY from '../SubscriptionsTable/SUBSCRIPTIONS.graphql';
import { SubscribeMutation, SubscribeMutationVariables, Query } from '../../../__generated__/typescript-operations';
interface InitialValuesI {
email: string;
}
interface HandleSubscribeI {
values: InitialValuesI;
subscribeMutation: Function;
resetForm: Function;
}
const handleSubsribe: Function = async ({
values,
subscribeMutation,
resetForm,
}: HandleSubscribeI) => {
const subscribeResult: Promise<SubscribeMutation> = await subscribeMutation({
variables: {
input: {
source: 'HOME_PAGE',
...values,
},
},
});
if (get(subscribeResult, 'data.subscribe')) {
resetForm();
}
return subscribeResult;
};
const Subscription: React.FunctionComponent = () => {
const [subscribeMutation] = useMutation<SubscribeMutation, SubscribeMutationVariables>(
SUSCRIBE_MUTATION,
{
update: (cache, { data }: FetchResult): void => {
const dataResult = cache.readQuery<Query>({ query: SUBSCRIPTIONS_QUERY });
cache.writeQuery({
query: SUBSCRIPTIONS_QUERY,
data: {
subscriptions: dataResult
&& dataResult.subscriptions
&& dataResult.subscriptions.concat([data && data.subscribe]),
},
});
},
},
);
const initialValues: InitialValuesI = {
email: '',
};
return (
<div className={s.Subscription}>
<div className={s.Subscription__SubscriptionWrapper}>
<div>
<h2>
Lorem ipsum is place-holder text commonly used in the graphic, print, and publishing
industries for previewing layouts and visual mock-ups.
</h2>
<Formik
initialValues={initialValues}
onSubmit={async (values, { resetForm }): Promise<SubscribeMutation> => handleSubsribe({
values,
subscribeMutation,
resetForm,
})}
validationSchema={Yup.object().shape({
email: Yup.string()
.email()
.required('Before submitting you need to provide your email'),
})}
>
<Form>
<div className={s.Subscription__Row}>
<label htmlFor="email">Email</label>
<Field
id="email"
className={s.Carousel__EmailInput}
name="email"
placeholder="your@email.com"
type="email"
/>
<button type="submit" className={s.Subscription__SubscribeButton}>
Subscribe
</button>
</div>
<div className={s.Subscription__FieldErrorRow}>
<ErrorMessage
name="email"
component="div"
className={s.Subscription__FieldError}
/>
</div>
</Form>
</Formik>
</div>
</div>
</div>
);
};
export default Subscription;

In this case we used useMutation hook and again passed our generated types into useMutation function. These steps enabled us to use generated types on the client and each time we will change our GraphQL schema we will get up to date TypeScript suggestions.

Generating typed-safe resolvers on your server with GraphQLCodeGen

In order to generate server-side typings for your resolvers we need to use additional plugin. After updating our codegen.yaml we will get the following:

schema: 'server/schema/typeDefs.ts'
documents: 'components/**/*.graphql'
generates:
__generated__/typescript-operations.ts:
- typescript-operations
- typescript
server/__generated__/resolver-types.ts:
- typescript
- typescript-resolvers

We can generate our types again with:

npm run gen:schema

Now we have generated also types for our resolvers to server/generated/resolver-types.ts. We can now type all of our resolvers as follows:

import { getSubscriptions, createSubscription } from '../requests/subscription-requests';
import { Resolvers } from '../__generated__/resolver-types';
interface StringIndexSignatureInterface {
[index: string]: any;
}
type StringIndexed<T> = T & StringIndexSignatureInterface
const resolvers: StringIndexed<Resolvers> = {
Query: {
subscriptions: () => getSubscriptions(),
},
Mutation: {
subscribe: async (__, args) => createSubscription({}, args),
},
};
export default resolvers;

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 with plug-ins. For our project the most relevant plugin is for React Apollo. This can help you to skip one additional manual step of creating React Apollo components for mutations and queries.

Summary

I believe that automatic type and code generation is one of the biggest trends in GraphQL ecosystem. We are having great ecosystem for development especially for TypeScript and GraphQLCodeGen. You can use our starter project to speed up your set-up. This will help you diminish unnecessary friction between your static typing on the frontend with the GraphQL API. You can inject the command to regenerate types after each change in your GraphQL schema files. This way you will have your types automatically in sync with your API. 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 is a promising step for more efficient work-flows. I believe that automatic type generation of static types using GraphQL not only in TypeScript 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.

Did you like this post? You can clone the repository with the examples and project set-up. Feel free to send any questions about the topic to david@atheros.ai.

Atheros Learning Logo

Atheros Newsletter

Subscribe to our newsletter, and don't miss a beat!

message

* Signing up for Atheros newsletter indicates you agree with

Terms and Conditions and Privacy Policy including our Cookie Policy.

Ready to take next step?

Unlock your potential and master the art of development and design by joining our Classes today.

Don't miss out on the opportunity to enhance your skills and create a bright future in the digital world like thousands of others.