GraphQL IDE の “GraphiQL” をカスタマイズして、開発ツールとして活用する

こんにちは.マンガチームの id:mangano-ito です.最近は GraphQL API の開発を担当しており,GraphQL に関することを勉強したり実践したりしています.今回は開発ツールについてのお話です.

GraphiQL とは

graphql/graphiql: GraphiQL & the GraphQL LSP Reference Ecosystem for building browser & IDE tools.

GraphQL API の使いやすい GUI クライアントです.GUI クライアントなので GraphQL ではなく Graph i QL となっているのがポイントですね.

余談ですが「グラフィキューエル」と呼んでいたら「グラフィクル」だという声をいただき,「グラフィカル」にかけていると気づきました(ちなみにREADMEでは読み方が/ˈɡrafək(ə)l/と示されています).

ライブデモがあるので,どのようなものかはブラウザ上で確認できます.また Electron で実装されたアプリもあり,デスクトップ上でも使うことができます.

GitHub API での使用例

まず GitHub API の Explorer を見てみてください.これは GitHub の GraphQL API をブラウザ上でテストできるツールです.

GraphiQL の使用例: Github GraphQL Explorer

GitHub にサインインしていれば,自分のアカウントを使って GraphQL API をテストすることができます.実際のデータで確認できて便利です.

お気づきかと思いますが,ここに埋め込まれているクライアントは GraphiQL です.つまり GraphiQL を自分のウェブページにマウントすることができるということが,この例からわかります.

GraphiQL を導入してみよう

こうなるとこの例と同じように,開発しているサービスに特化した GraphiQL を便利に使えるようにしてみたいですね.どのようにすれば使うことができるでしょうか……

より詳しい情報はREADMEをあたっていきましょう.GraphiQL をモジュールとして使うための方法が書かれています.

graphiql/graphiql - GraphiQL (README.md)

ここにお試しで掲載されているケースでは,ただ CDN で配布されたスクリプトを読み込み,ReactDOM.renderで GraphiQL のコンポーネントを任意の要素にマウントすればそれで完了です!

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <title>Simple GraphiQL Example</title>
    <link href="https://unpkg.com/graphiql/graphiql.min.css" rel="stylesheet" />
  </head>

  <body style="margin: 0;">
    <script crossorigin src="https://unpkg.com/react/umd/react.production.min.js"></script>
    <script crossorigin src="https://unpkg.com/react-dom/umd/react-dom.production.min.js"></script>
    <script crossorigin src="https://unpkg.com/graphiql/graphiql.min.js"></script>
    
    <div id="graphiql" style="height: 100vh;"></div>
    <script>
      const fetcher = GraphiQL.createFetcher({ url: '<GraphQL API エンドポイントの URL>' });
     
      ReactDOM.render(
        React.createElement(GraphiQL, { fetcher: fetcher }),
        document.getElementById('graphiql'),
      );
    </script>
  </body>
</html>

Usage: UMD Bundle over CDN より

React のコンポーネントをマウントする形になっているのはユニークですね.これだけで GraphiQL の機能がひと通りそろっていて,クエリもできるし,ドキュメンテーションもしっかりと表示できる状態になっています.

もちろんパッケージをnpm installして使うこともできます,本格的に組み込んで使える場合はこちらが都合よさそうですね.

import React from 'react';
import ReactDOM from 'react-dom';

import GraphiQL from 'graphiql';
import { createGraphiQLFetcher } from '@graphiql/toolkit';

const fetcher = createGraphiQLFetcher({
  url: '<GraphQL API エンドポイントの URL>',
});

ReactDOM.render(
  <GraphiQL fetcher={fetcher} />,
  document.body,
);

Usage: NPM module より

fetcherは,対象となる GraphQL API のエンドポイントを指定しリクエストするために必要なものです.

const fetcher = GraphiQL.createFetcher({
  url: '<GraphQL API エンドポイントの URL>'
});

以降のコードではこの基本形を基にして拡張してみます.

ツールバーをカスタマイズしてみよう

React のコンポーネントになっていることで,さらに面白い点としては,自分が作って任意のコンポーネントを GraphiQL 上に追加することができることです.

GraphiQLコンポーネントの属性により注目してみましょう.

<GraphiQL
  fetcher={fetcher}
  toolbar={toolbar} />

toolbarという属性があります.これはその名の通り,ツールバーの設定を記述するための属性です.ここにadditionalContentという属性を持ったオブジェクトを与えることで,任意の React コンポーネントを追加することができます.

const toolbar = {
  additionalContent: (
    <div>
      <GraphiQL.Button
        label="ボタンA"
        onClick={() => window.alert('こんにちは!')} />
      <GraphiQL.Button
        label="ボタンB"
        onClick={() => window.alert('さようなら!')} />
    </div>
  ),
};

const Editor = () => (
  <GraphiQL
    fetcher={fetcher}
    toolbar={toolbar} />
);

ReactDOM.render(
  <Editor />,
  document.getElementById('graphiql'),
);

例えばこのようなコードを書いてみます.GraphiQL.Buttonは GraphiQL のツールバーボタンのコンポーネントです.ツールバーに2つのボタンが追加されていることが確認できます.

ツールバーにボタンが追加された

コードから予想できるように,ボタンをクリックするとアラートが表示されます.

アラートが表示された

他に,ボタン以外のコンポーネントもあります.メニューは便利そうですね.

メニューの例
const toolbar = {
  additionalContent: (
    <GraphiQL.Menu label="メニュー">
      <GraphiQL.MenuItem
        label="おはよう"
        onSelect={() => alert('おはよう')} />
      <GraphiQL.MenuItem
        label="おやすみ"
        onSelect={() => alert('おやすみ')} />
    </GraphiQL.Menu>
  ),
};

const Editor = () => (
  <GraphiQL
    fetcher={fetcher}
    toolbar={toolbar} />
);

// ReactDOM.render 以下省略……

実はロゴ部分を変えることもできたりします.面白いですね.

ロゴ部分を変えられる
const Editor = () => (
  <GraphiQL
    fetcher={fetcher}
    toolbar={toolbar}>
    <GraphiQL.Logo>マイ GraphiQL</GraphiQL.Logo>
  </GraphiQL>
);

これで任意の機能をツールバーに登録することができることがわかりました.

ヘッダーやクエリをカスタマイズしてみよう

ところで,冒頭にご紹介した GitHub の例ではログイン状態を反映することができていました.

仮にログイン状態のためのAuthorizationヘッダーが API に必要だとしましょう.私たちが作る GraphiQL にその情報を追加するにはどうすればよいでしょうか?

GraphiQLコンポーネントの他の属性を見てみましょう.headersという属性があります.ここにヘッダーの内容を示す JSON 文字列を与えることで,任意の値を追加することができます.

const headers = {
  'Authorization': 'Bearer MY_TOKEN',
};

const Editor = () => (
  <GraphiQL
    fetcher={fetcher}
    headerEditorEnabled="true"
    headers={JSON.stringify(headers, null, 2)} />
);

開発者ツールでリクエストを確認すると,ヘッダーに指定したAuthorizationの値が設定されていることがわかります.

Authorization に値が設定された

またheaderEditorEnabledで,リクエストヘッダーがエディター上に表示され,編集もできるようになります.

ヘッダーエディタ表示

さらに,この値に state 変数を使うとリアクティブ変化するので……

const MyTokenButton = ({ setToken }) => (
  <GraphiQL.Button
    label='トークンをセット'
    onClick={() => setToken('NEW_TOKEN')} />
);

const Editor = () => {
  const [token, setToken] = React.useState('');
  const headers = {
    'Authorization': (!!token ?`Bearer ${token}`: undefined),
  };
  const toolbar = {
    additionalContent: <MyTokenButton setToken={setToken} />,
  };

  return (
    <GraphiQL
      fetcher={fetcher}
      toolbar={toolbar}
      headerEditorEnabled="true"
      headers={JSON.stringify(headers, null, 2)} />
  );
};

ボタンを押すとヘッダーにトークンが設定されるようになりました.

ヘッダーを動的に変更もできる

クエリも同様にquery属性を設定でき,onEditQueryで編集されたイベントを受け取ることもできます.

クエリも動的に変更できる
const FavoriteQueryButton = ({ setQuery }) => (
  <GraphiQL.Menu label="お気に入りクエリ">
    <GraphiQL.MenuItem
      label="その1"
      onSelect={() => setQuery("{ favorite1 }")} />
    <GraphiQL.MenuItem
      label="その2"
      onSelect={() => setQuery("{ favorite2 }")} />
  </GraphiQL.Menu>
);

const Editor = () => {
  const [query, setQuery] = React.useState('');
  const toolbar = {
    additionalContent: <FavoriteQueryButton setQuery={setQuery} />,
  };

  return (
    <GraphiQL
      fetcher={fetcher}
      toolbar={toolbar}
      query={query} />
  );
};

このようにして任意のヘッダーやクエリを設定することができました.よく使うアクションやサービス特有のテンプレートをツールバーボタンから設定できると便利になりそうですね.

実際に開発ではどう使っているか

最後に,自分が担当しているサービスでの実例を紹介します.

実際に開発で使っている GraphiQL の様子*1

GraphiQL に,開発用に以下の3つの機能を追加してみました.

  1. 自分のログイン情報を自動的に付加できるようにした
  2. デバッグ機能のためのプリセットや値をツールバーから使えるようにした
  3. 書いたクエリを共有できるようにして,開発者同士でシェアできるようにした

この中で3番目の「書いたクエリを共有」というのは,上図のように特定の URL でクエリが記入された GraphiQL を開くことができるものです.確認用のクエリを Pull Request で共有できて便利だったりします.

実装はごく単純で,URL の GET パラメーターにクエリを持たせてやりとりしているだけで,簡単に実現できます.

/**
 * ?query=... に今のクエリを設定する
 * @param {string} query 更新されたクエリ
 */
const onQueryUpdated = (query) => {
  const url = new URL(document.location);
  url.searchParams.set('query', query);
  window.history.replaceState(null, null, url);
};

/**
 * ?query=... のクエリを取得する
 * @returns {string|null} クエリ
 */
const getCurrentQuery = () => (
  new URL(document.location).searchParams.get('query')
);

// あとはこれを使うだけ
const Editor = () => {
  const [query, setQuery] = React.useState(getCurrentQuery());

  return (
    <GraphiQL
      fetcher={fetcher}
      query={query}
      onEditQuery={onQueryUpdated} />
  );
};

まだできていないこととして,あらかじめよく使うクエリを何らかの手段で参照できるようにして,スニペットとしてサッと使えるようにしてみたいと思っています.

GitHub API の Explorer では,次のようにExplorerボタンからクエリをお手軽に構築できる機能があり,こういう形でスニペットを挿入するイメージです.

GraphiQL 自体が React を使って柔軟に自作コンポーネントを追加できるので,生の要素や状態を管理することなく拡張できて便利です.思いがけず簡単に導入することができたのは面白い体験でした.

エンジニア採用 - 採用情報 - 株式会社はてな

*1:隠したり文言を少し変えたりしています