🧻

GraphQLクライアントがリクエストするURLにoperationNameを付けると便利です

2023/01/25に公開

GraphQLのクエリやミューテーションはJSONエンコードされた本文をHTTP POSTでサーバに送ることで実行されます。
すべてのクエリやミューテーションは同じエンドポイント(/graphql等)に対してPOSTされるため、特別な対応をしない限り、ログには「クエリ or ミューテーションどちらなのか」「どんな内容か」などの情報が記録されません。

簡単な設定だけでログを見てざっくりリクエストの傾向がわかるようにできると便利ですよね。

エンドポイントにoperationNameを付与する

例: query FooBarQury を実行した場合 -> /graphql?operationName=FooBarQuery

上記のようにしてクエリやミューテーションの名前を operationName としてリクエストURLに付与します。

具体的な情報はもちろん本文に含まれるため、細かな分析はできませんが、ログに記載された operatioName が分かれば、コードベースを grep するだけでどんなクエリを送っているのか調べることができます。便利ですね! [1]

設定方法

GraphQLクライアントでは初期化時の設定やリクエスト送信時のオプションで fetch をカスタマイズすることが可能ですので、各ライブラリに合わせて設定を行います。

urqlの場合

いきなり fetch をカスタマイズする必要はありません。 urql は exchange という仕組みで、通信やキャッシュの処理に介入することができます。

あまり詳しくはないのですが、自前で exchange を書かなくても @urql/exchange-context でクエリ実行前に context に介入することができるようです。 [2]

https://www.npmjs.com/package/@urql/exchange-context

getOperationName というユーティリティー関数を使用して DocumentNode から名前を取得します。( urql 4.x からは使用できなくなっていますので、コアを参考に自前で実装してください)

import {
  createClient,
  dedupExchange,
  cacheExchange,
  fetchExchange,
  getOperationName, // urql 3.x
} from "urql";
import { contextExchange } from "@urql/exchange-context";

// urql 4.x
// https://github.com/urql-graphql/urql/blob/af5b90b7bb9aec77638a44671e9cddff531c16e1/packages/core/src/utils/request.ts#L170C1-L176C3
const getOperationName = (query: DocumentNode): string | undefined => {
  for (const node of query.definitions) {
    if (node.kind === Kind.OPERATION_DEFINITION) {
      return node.name ? node.name.value : undefined;
    }
  }
};

/** リクエストのURLにoperationNameを付与する */
const annotatedURL = (operation: Operation) => {
  // contextは使い回されるため、パラメーターが既に存在する場合を考慮してURLクラスを使用してます
  // 相対パスを処理する良い方法がわからなかったためダミーのbase URLを指定しています。
  const url = new URL(operation.context.url, "http://example.com");
  url.searchParams.set(
    "operationName",
    getOperationName(operation.query) ?? ""
  );
  return url.host === "example.com"
    ? `${url.pathname}${url.search}`
    : url.toString();
};

const client = createClient({
  url: "/graphql",
  exchanges: [
    // この辺はデフォルトに含まれているexchange
    dedupExchange,
    cacheExchange,
    contextExchange({
      getContext: async (operation) => ({
        ...operation.context,
        url: annotatedURL(operation),
      }),
    }),
    // fetchより前に入れる
    fetchExchange,
  ],
});

ApolloClientの場合

実装例がドキュメントにあります。
https://www.apollographql.com/docs/react/api/link/apollo-link-http/#dynamic-uri

HttpLink を作成して fetch に介入することでURLを変更します。

import { ApolloClient, from, HttpLink } from '@apollo/client'

const customFetch = (uri, options) => {
  const { operationName } = JSON.parse(options.body);
  return fetch(`${uri}?opname=${operationName}`, options);
};

const link = new HttpLink({ fetch: customFetch });

const client = new ApolloClient({
   link: from([link])
})

まとめ

GraphQLクライアントの設定やプラグイン機構を利用することでHTTP通信をカスタマイズできます。
自分のプロジェクトではこの仕組みを使い、APIトークンを付与したり、ローカル環境で臨場感が出るようにディレイを入れたりしています。

脚注
  1. このテクニック自体は同僚の @nkzn に教えてもらいました。Thx! ↩︎

  2. urqlでも下記のApolloClientのようにfetchをカスタマイズすることができるので、その方法でもよいのですが、そのままだと https://github.com/jaydenseric/graphql-multipart-request-spec を使用したい場合に対応できていないと思うのでcontext部分に介入する方法を選択しました。 ↩︎

株式会社モニクル

Discussion