🧻

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

ka2n2023/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

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

/** リクエストのURLにoperationNameを付与する */
const annotatedURL = (operation: Operation) => {
  // contextは使い回されるため、パラメーターが既に存在する場合を考慮してURLクラスを使用してます
  // 相対パスを処理する良い方法がわからなかったためダミーのbase URLを指定しています。
  const url = new URL(operation.contxt.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

ログインするとコメントできます