Continuously generate TypeScript typings from GraphQL schema across multiple repositories 🚀
Tags: graphql, typescript, github
Published: 2021-01-28 17:47:50

[GraphQL Code Generator](https://graphql-code-generator.com/) (`graphql-codegen`) is very useful when you use GraphQL on the server-side and TypeScript on the client-side. If you use GraphQL and TypeScript, you will declare TypeScript typings based on your GraphQL schema for sure. Instead of writing TypeScript types one by one, `graphql-codegen` generates type declarations out of the GraphQL schema which is 1:1 to the schema.  
However, you may have problem generating type declarations from the latest GraphQL schema using `graphql-codegen` when you develop API and client app in the different GitHub repositories. How should it be solved?

## Basic usage of `graphql-codegen`
A minimal example is:

```yaml
schema: schema.graphql
generates:
  ./src/graphql-schema.ts:
    plugins:
      - typescript
```

`schema` field accepts 1) URL of GraphQL endpoint, 2) local schema file in `*.graphql` or JSON format, 3) remote `*.graphql` file in GitHub repository, and so on.

Refer to [GraphQL Code Generator's documentation](https://graphql-code-generator.com/docs/getting-started/codegen-config) for more details.

## Prerequisite
Before proceeding to the next section, ensure that you have the following:

#### 1. GraphQL project and its corresponding GitHub repository

Mine is a Rails application along with `graphql-ruby` gem, and the app is named `cont-codegen-sandbox-api`.

#### 2. Application made with TypeScript and its corresponding GitHub repository

Mine is `cont-codegen-sandbox-client`.

#### 3. GraphQL schema generator

A script to generate GraphQL schema file such as `schema.graphql` on the server-side. I'm using [lib/graphql/rake_task.rb](https://github.com/rmosolgo/graphql-ruby/blob/792f276444e1dd6004fcafe3820d65fdbbe285f0/lib/graphql/rake_task.rb) (`rake graphql:schema:idl`) for this.

I recommend that you automate this file generation by using git pre-commit hook or GitHub application.

#### 4. GitHub personal access token

Set your GitHub personal access token to your repository's secret. In my case, the secret's name is `PERSONAL_ACCESS_TOKEN`.

The token should have access to scope `repo`.

![new_personal_access_token](https://dev-to-uploads.s3.amazonaws.com/i/x0fjewhe5txs0v85r30r.png) 

`graphql-codegen` and GitHub Action use the token whether the repo is private or not.

## Generate typings across repos

If your GraphQL project and client app are in the same directory or in the same GitHub repository, the basic example above should be enough. On the other hand, when your applications are managed in different repositories, how should you do?

In that case, you can specify the remote `schema.graphql` in a GitHub repository (including private repository :sparkles:). `codegen.yml` should be following syntax.

```yaml
schema:
  - user/repo#branchName:path/to/schema.graphql:
      token: ${PERSONAL_ACCESS_TOKEN}
```

`token` field is always required because `graphql-codegen` can access to your repository, which is not described in the official documentation. Even your repo is public, it requires `token` as of today, Jan 2021. I don't know if this is a bug.

If you're using dotenv, codegen command is `graphql-codegen -r dotenv/config`.

Here is my `codegen.yml`:

```yaml
schema:
  - github:ayumitamai97/cont-codegen-sandbox-api#main:schema.graphql:
      token: ${PERSONAL_ACCESS_TOKEN}

generates:
  ./types/graphql-schema.ts:
    plugins:
      - typescript
    config:
      apolloClientVersion: 2
```

Now, we can generate type declation for TypeScript by hitting `yarn graphql-codegen -r dotenv/config` :fire:

## Continuously generate typings across repos

The previous section describes how to setup `graphql-codegen` when having GraphQL and TypeScript projects in different repositories. Then, how can this process be automated?

GitHub Action may be the best way to do that. We need to create three GitHub workflows:
1. Notify client-side repo when GraphQL schema is updated (server-side repo)
2. Execute `graphql-codegen` and create Pull Request (client-side repo)
3. Automerge Pull Request (client-side repo) (optional)

### 1. Notify client-side repo when GraphQL schema is updated

To generate TypeScript typings out of GraphQL schema almost synchronously, server-side repo has to notify client-side repo when GraphQL schema is updated.

The configuration below allows the repo to trigger `repository_dispatch` event in the other repo when a commit is pushed to `main` branch and `schema.graphql` has file diff.

Caveat! The third step (`Dispatch update-graphql-schema`) requires your personal access token since [`GITHUB_TOKEN` cannot trigger `repository_dispatch` event](https://docs.github.com/en/actions/reference/events-that-trigger-workflows#triggering-new-workflows-using-a-personal-access-token).

My `.github/workflows/dispatch_graphql_schema_update.yml` in server-side repo:

```yaml
name: Dispatch GraphQL Schema Update

on:
  push:
    branches: [main]

jobs:
  build:
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v2
      - uses: technote-space/get-diff-action@v4.0.1
        with:
          PATTERNS: schema.graphql

      - name: Dispatch update-graphql-schema
        uses: peter-evans/repository-dispatch@v1
        with:
          repository: ayumitamai97/cont-codegen-sandbox-client
          token: ${{ secrets.PERSONAL_ACCESS_TOKEN }}
          event-type: update-graphql-schema
        if: env.GIT_DIFF
```

### 2. Execute `graphql-codegen` and create Pull Request

When `repository_dispatch` event is triggered in the client-side repo, we want `graphql-codegen` to generate TypeScript types based on the latest GraphQL schema.

When the Pull Request is created, one wants to check & approve the PR, while another wants PR to be merged automatically. I like the latter idea, so I added some configurations to prepare for `pascalgn/automerge-action`.

Caveat! The 4th step (`Create Pull Request`) requires `PERSONAL_ACCESS_TOKEN` if you want to automerge the Pull Request. Otherwise, `GITHUB_TOKEN` is enough.

My `.github/workflows/graphql_schema_updated.yml` on client-side repo:

```yaml
name: Update GraphQL Schema

on:
  repository_dispatch:
    types: [update-graphql-schema]

jobs:
  update-graphql-schema:
    runs-on: ubuntu-latest
    steps:
      - name: checkout
        uses: actions/checkout@v2

      - name: Install dependencies
        run: yarn install

      - name: Update GraphQL Schema
        run: PERSONAL_ACCESS_TOKEN=${{ secrets.PERSONAL_ACCESS_TOKEN }} yarn codegen

      - name: Create Pull Request
        uses: peter-evans/create-pull-request@v3
        with:
          token: ${{ secrets.PERSONAL_ACCESS_TOKEN }} # `GITHUB_TOKEN` unless using automerge-action
          commit-message: '[bot] Update GraphQL Schema'
          committer: GitHub <noreply@github.com>
          author: ${{ github.actor }} <${{ github.actor }}@users.noreply.github.com>
          signoff: false
          branch: feature/update-graphql-schema
          branch-suffix: timestamp
          delete-branch: true
          title: '[bot] Update GraphQL Schema'
          body: GraphQL schema has been updated
          labels: automerge # required if using automerge-action
```

### 3. Automerge Pull Request (optional)

If you'd like a Pull Request to be automerged which is created in GitHub workflows, [`pascalgn/automerge-action`](https://github.com/pascalgn/automerge-action) is helpful. This GitHub Action merges a PR automatically when it has `automerge` label.

As the second workflow creates a new Pull Request and adds `automerge` label to it, the third (this) workflow should be triggered when a PR is labeled.

My `.github/workflows/automerge.yml` on client-side repo:

```yaml
name: Automerge
on:
  pull_request:
    types:
      - labeled

jobs:
  automerge:
    runs-on: ubuntu-latest
    steps:
      - name: automerge
        uses: pascalgn/automerge-action@v0.13.0
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
```

## Working example

Finally, your GraphQL project can **continuously generate TypeScript typings out of GraphQL schema across repositories**! :rocket:

If you successfully configured all of the above, workflows should look like this.

![server-side](https://dev-to-uploads.s3.amazonaws.com/i/5hyvjtgj10x7oesgce3y.png)
    
![client-side](https://dev-to-uploads.s3.amazonaws.com/i/mqkexmj1gy3gzan20cum.png)
 

### Links to my codegen & workflow configurations

- [`graphql-codegen` config](https://github.com/ayumitamai97/cont-codegen-sandbox-client/blob/05b52ee7f63e3ed6d8e9230db04bb7b57eb75cd3/codegen.yml)

- [Notify client-side repo when GraphQL schema is updated](https://github.com/ayumitamai97/cont-codegen-sandbox-api/blob/d2b8be0879e81a20309dd10094b2af1444e6f6c6/.github/workflows/dispatch_graphql_schema_update.yml)

- [Execute `graphql-codegen` and create Pull Request](https://github.com/ayumitamai97/cont-codegen-sandbox-client/blob/c6590aaeafbfb003f93e5d5d431efafef5556674/.github/workflows/graphql_schema_updated.yml)

- [Automerge Pull Request](https://github.com/ayumitamai97/cont-codegen-sandbox-client/blob/95d7606dca926c2e78ddca15c2f40d5a512fc039/.github/workflows/automerge.yml)

### Link to the example Pull Request
You can see the actual Pull Request created in the workflow here [ayumitamai97/cont-codegen-sandbox-client#36](https://github.com/ayumitamai97/cont-codegen-sandbox-client/pull/36).

Note that if you use `GITHUB_TOKEN` for `Create Pull Request` step, the committer will be "github-actions (bot)" instead of your account.

If this post is helpful, or you know better ways to do these things, please don't hesitate to leave a comment. Thank you for reading! :space_invader:

Japanese version: [GraphQL スキーマの変更を検知し、TypeScript の型定義を自動的に生成する仕組み | Medium](https://ayumitamai97.medium.com/continuously-generate-typescript-typings-from-graphql-schema-across-multiple-repositories-ja-fdb2c99c7f1c)