DEV Community

myougaTheAxo
myougaTheAxo

Posted on

Claude CodeでGraphQL Persisted Queriesを設計する:APQ・クエリ許可リスト・帯域削減

はじめに

「GraphQLクエリが毎回全文送信されていてMobileの通信量が多い」「任意クエリを受け付けているのでセキュリティが心配」——APQとクエリ許可リストでGraphQLを最適化する設計をClaude Codeに生成させる。


CLAUDE.mdに設計ルールを書く

## GraphQL Persisted Queries設計ルール

- APQ: クライアントはクエリハッシュ(SHA256)のみを送信
- Redisにhash→query文字列をキャッシュ(TTL: 7日)
- 本番: 許可リストモード(任意クエリ禁止)
- CI/CDでビルド時にクエリを登録(ソースコードから自動抽出)
Enter fullscreen mode Exit fullscreen mode

生成される実装(抜粋)

// APQプラグイン(Apollo Server)
export function apqPlugin(options): ApolloServerPlugin {
  return {
    async requestDidStart() {
      return {
        async didResolveSource(requestContext) {
          const persistedQuery = requestContext.request.extensions?.persistedQuery;
          if (!persistedQuery) return;

          if (requestContext.request.query) {
            // ハッシュ + クエリ全文: 登録
            const hash = crypto.createHash('sha256').update(request.query).digest('hex');
            if (hash !== persistedQuery.sha256Hash) throw new Error('Hash mismatch');
            await redis.set(`apq:${hash}`, request.query, { EX: 7 * 86400 });
          } else {
            // ハッシュのみ: 解決
            const query = await redis.get(`apq:${persistedQuery.sha256Hash}`);
            if (!query) throw new PersistedQueryNotFoundError(); // クライアントにフォールバック要求
            requestContext.request.query = query;
          }
        },
      };
    },
  };
}

// 許可リストモード(本番)
export function allowlistPlugin(allowlist, enforceInProd = true): ApolloServerPlugin {
  return {
    async requestDidStart() {
      return {
        async didResolveSource(requestContext) {
          const hash = requestContext.request.extensions?.persistedQuery?.sha256Hash;
          if (!hash && enforceInProd && process.env.NODE_ENV === 'production') {
            throw new Error('Arbitrary queries are not allowed. Use persisted queries.');
          }
          if (hash && !await allowlist.isAllowed(hash)) {
            throw new Error(`Query hash ${hash} is not in the allowlist`);
          }
        },
      };
    },
  };
}

// CI/CDスクリプト: ソースからgql`...`を抽出してRedisに登録
const queries = await extractQueriesFromFiles('src/**/*.{ts,tsx}');
await allowlist.register(queries.map(q => ({
  hash: createHash('sha256').update(print(parse(q))).digest('hex'), // 正規化してハッシュ
  queryName: q.match(/(?:query|mutation)s+(w+)/)?.[1] ?? 'anonymous',
})));
Enter fullscreen mode Exit fullscreen mode

まとめ

  1. CLAUDE.md にAPQプロトコル・Redisに7日TTLキャッシュ・本番は許可リストモード・CI/CDでビルド時登録を明記
  2. PersistedQueryNotFoundError でクライアントにフォールバックを促す——初回のみ全クエリ送信、以降はハッシュのみで通信量削減
  3. クエリ正規化(printとparse) でコメントや空白の違いを吸収——同じクエリが異なるハッシュになることを防止
  4. CI/CDでbuild時にクエリ登録 ——ソースコードの変更なしにクエリが追加された場合はデプロイが失敗する防線

セキュリティ設計のレビューは **Security Pack(¥1,480)* の /security-check で確認できます。*

prompt-works.jp

みょうが (@myougatheaxo) — ウーパールーパーのVTuber。

Top comments (0)