DEV Community

Ayumi Tamai
Ayumi Tamai

Posted on

Continuously generate TypeScript typings from GraphQL schema across multiple repositories ๐Ÿš€

GraphQL Code Generator (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:

schema: schema.graphql
generates:
  ./src/graphql-schema.ts:
    plugins:
      - typescript
Enter fullscreen mode Exit fullscreen mode

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 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 (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

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 โœจ). codegen.yml should be following syntax.

schema:
  - user/repo#branchName:path/to/schema.graphql:
      token: ${PERSONAL_ACCESS_TOKEN}
Enter fullscreen mode Exit fullscreen mode

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:

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

generates:
  ./types/graphql-schema.ts:
    plugins:
      - typescript
    config:
      apolloClientVersion: 2
Enter fullscreen mode Exit fullscreen mode

Now, we can generate type declation for TypeScript by hitting yarn graphql-codegen -r dotenv/config ๐Ÿ”ฅ

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.

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

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
Enter fullscreen mode Exit fullscreen mode

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:

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
Enter fullscreen mode Exit fullscreen mode

3. Automerge Pull Request (optional)

If you'd like a Pull Request to be automerged which is created in GitHub workflows, 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:

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 }}
Enter fullscreen mode Exit fullscreen mode

Working example

Finally, your GraphQL project can continuously generate TypeScript typings out of GraphQL schema across repositories! ๐Ÿš€

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

server-side

client-side

Links to my codegen & workflow configurations

Link to the example Pull Request

You can see the actual Pull Request created in the workflow here ayumitamai97/cont-codegen-sandbox-client#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! ๐Ÿ‘พ

Japanese version: GraphQL ใ‚นใ‚ญใƒผใƒžใฎๅค‰ๆ›ดใ‚’ๆคœ็Ÿฅใ—ใ€TypeScript ใฎๅž‹ๅฎš็พฉใ‚’่‡ชๅ‹•็š„ใซ็”Ÿๆˆใ™ใ‚‹ไป•็ต„ใฟ | Medium

Top comments (1)

Collapse
 
tehpsalmist profile image
Ben Steward

LOVE IT. Thank you!