まくろぐ

Node.js で GitHub GraphQL API を使用する (@octkit/graphql)

更新:
作成:

概要

GitHub 上の情報を扱う API として、GitHub は GraphQL API を提供しています。

Node.js で GraphQL を扱う方法としては、Apollo ライブラリを使った方法 などがありますが、GitHub の GraphQL API を呼び出したいのであれば、GitHub が提供している GraphQL パッケージ @octokit/graphql を使うのが手っ取り早いかもしれません(エンドポイントの URL などを省略できます)。

ここでは、TypeScript からこの @octokit/graphql パッケージを使用する方法を紹介します。

☝️ REST API と GraphQL API GitHub API バージョン 3 は REST API でしたが、柔軟性などの観点 から、GitHub API バージョン 4 の GraphQL 版の API を使用することが推奨されています。 GraphQL API を使用することで、REST API で複数のリクエストが必要だったものを 1 度のリクエストで取得できたりします。 また、GraphQL API でしか取得できない情報もあったりします。

セットアップ

まず、TypeScript のプロジェクトを作成します。

@octokit/graphql パッケージは次のようにインストールします。 このパッケージには TypeScript の型情報も含まれています。 あと、環境変数設定用のライブラリとして、dotenv もインストールしておきます。

$ npm install @octokit/graphql --save
$ npm install dotenv --save

GitHub のアクセストークンが必要になるので、GitHub の設定画面からパーソナルアクセストークンを取得しておいてください。

アクセストークンは、環境変数 MYAPP_GITHUB_TOKEN か、プロジェクトルートの .env ファイルに次のように保存しておきます。

.env
MYAPP_GITHUB_TOKEN=ccdccab3363b3554ebadc033fa8fe43403705301

サンプルコード

ここでは、次のような方針で、@octkit/graphql モジュールを使ったコードを作成してみます。

  • パーソナルアクセストークンは環境変数 MYAPP_GITHUB_TOKEN(あるいは .env ファイル)で設定する
  • 上記トークンを使って GraphQL リクエストを送る graphql モジュールを作成し、それをメインプログラムから使用する

次の graphql モジュールは、アクセストークン付きの GraphQL リクエストを送るためのセットアップを行います。 この処理をメインプログラムから切り出しておくことで、メインプログラム側のコードをスッキリさせることができます。

graphql.ts
import { graphql } from '@octokit/graphql';
import * as dotenv from 'dotenv';

// .env ファイルの内容を環境変数に反映
dotenv.config();

// graphql をアクセストークン付きで呼び出せるようにする
const graphqlWithAuth = graphql.defaults({
  headers: {
    authorization: `token ${process.env.MYAPP_GITHUB_TOKEN}`,
  },
});

export { graphqlWithAuth as graphql };

メインプログラムからは次のように GraphQL リクエストを実行します。 ここでは、GitHub の octokit/graphql.js リポジトリの最新の Issue 情報を取得しています。

main.ts
import { graphql } from './graphql';

const QUERY = `
  {
    repository(owner: "octokit", name: "graphql.js") {
      issues(last: 3, states: [OPEN]) {
        edges {
          node {
            number
            title
          }
        }
      }
    }
  }
`;

async function main() {
  try {
    const {repository} = await graphql(QUERY);
    for (const {node: issue} of repository.issues.edges) {
      console.log(`* ${issue.number}: ${issue.title}`);
    }
  } catch (err) {
    console.error(err.message);
  }
}

main();

メインプログラムは GraphQL の呼び出しだけなので、とてもシンプルですね! これで、GitHub GraphQL API を使った簡単なコマンドラインツールはサクッと作れると思います。

実行結果
* 78: Can't load client in Cloudflare workers
* 176: Can't use as Octokit with TypeScript
* 185: Setting baseUrl for GHES results in 406 when using with @octokit/auth-app

(応用)GraphQL の変数を使う

変数を使った GraphQL クエリも簡単に扱うことができます。 変数の値は、graphql 関数の第 2 パラメータで指定します。

main.ts
import { graphql } from './graphql';

const QUERY = `
  query getUser($login: String!) {
    user(login: $login) {
      login
      name
      url
      websiteUrl
      avatarUrl
    }
  }
`;

async function main() {
  try {
    const {user} = await graphql(QUERY, {login: 'maku77'});
    console.log(user);
  } catch (err) {
    console.error(err.message);
  }
}

main();
実行結果
{
  login: 'maku77',
  name: 'Masatoshi Ohta',
  url: 'https://github.com/maku77',
  websiteUrl: 'https://maku77.github.io/',
  avatarUrl: 'https://avatars2.githubusercontent.com/u/5519503?v=4'
}

(応用)ページネーションで 100 件を超えるデータを取得する

GitHub GraphQL API で何らかのリスト情報を取得する場合(Issue リストなど)、一度に取得できるデータ数は 100 件までに制限されています。 それより多くのデータを取得したい場合は、ページネーションによって繰り返しリクエストを送る必要があります。 次のページが存在するかどうかは pageInfo.hasNextPage、次のページの開始位置は pageInfo.endCursor で取得することができます。

main.ts
import { graphql } from './graphql';

const QUERY = `
  query getIssues($after: String) {
    repository(owner: "octokit", name: "graphql.js") {
      issues(first: 100, after: $after) {
        edges {
          node {
            number
            title
          }
        }
        pageInfo {
          endCursor
          hasNextPage
        }
      }
    }
  }
`;

async function main() {
  try {
    // ページネーション情報
    let hasNextPage = true;
    let endCursor: String | null = null;

    // 次のページがあれば GraphQL 呼び出し
    while (hasNextPage) {
      const {repository: {issues}} = await graphql(QUERY, {after: endCursor});

      // 現在のページの Issue 情報を出力
      for (const {node} of issues.edges) {
        console.log(`* ${node.number}: ${node.title}`);
      }

      // ページネーション情報を更新
      hasNextPage = issues.pageInfo.hasNextPage;
      endCursor = issues.pageInfo.endCursor;
    }
  } catch (err) {
    console.error(err.message);
  }
}

main();

関連記事

まくろぐ
サイトマップまくへのメッセージ