ウェブサイト検索

javascript - tRPC 10でのサーバーサイドキャッシュミドルウェアの実装


私はNext.jsのプロジェクトに取り組んでおり、tRPCの結果のためにインメモリキャッシュを実装しようとしています。各tRPCプロシージャがカスタムTTLでオプトインできるようにしたいと考えています。この目的にはtRPCのミドルウェアが適していると思います。残念ながら、現在のtRPCのミドルウェアのドキュメントでは、この特定のシナリオをカバーしていないようです。

tRPC 10でサーバーサイドのキャッシュミドルウェアをどのように実装できますか?

回答 #1

これを簡単に行うことを可能にする機能リクエストに関する関連する議論がGithubのイシューにあります。その間にも、カスタムロジックを実装することでこれを行うことはまだ可能です。

以下の例では、インメモリキャッシュのアプローチとしてnode-cacheを使用しています。これは tRPC 10.43.3 でテストされています。cachedProcedures に記載されている手続きは、キャッシュを使用するように設定されています。

// src/app/api/middleware/cache.ts

import { initTRPC } from "@trpc/server";
import NodeCache from "node-cache";

const cacheSingleton = new NodeCache();

// A map of cached procedure names to a callable that gives a TTL in seconds
const cachedProcedures: Map<string, (() => number) | undefined> = new Map();
cachedProcedures.set("router0.procedure0", () => 2 * 3600); // 2 hours
cachedProcedures.set("router0.procedure1", () => 1800); // 30 minutes
cachedProcedures.set("router1.procedure0", secondsUntilMidnight); // dynamic TTL
cachedProcedures.set("router1.procedure1", () => undefined); // never expires

const t = initTRPC.create();
const middlewareMarker = "middlewareMarker" as "middlewareMarker" & {
  __brand: "middlewareMarker";
};


const cacheMiddleware = t.middleware(
  async ({ ctx, next, path, type, rawInput }) => {
    if (type !== "query" || !cachedProcedures.has(path)) {
      return next();
    }
    let key = path;
    if (rawInput) {
      key += JSON.stringify(rawInput).replace(/\"/g, "'");
    }
    const cachedData = cacheSingleton.get(key);
    if (cachedData) {
      return {
        ok: true,
        data: cachedData,
        ctx,
        marker: middlewareMarker,
      };
    }
    const result = await next();

    //@ts-ignore
    // data is not defined in the type MiddlewareResult
    const dataCopy = structuredClone(result.data);

    const ttlSecondsCallable = cachedProcedures.get(path);
    if (ttlSecondsCallable) {
      cacheSingleton.set(key, dataCopy, ttlSecondsCallable());
    } else {
      cacheSingleton.set(key, dataCopy);
    }
    return result;
  }
);
export default cacheMiddleware;
// src/server/api, or wherever you define tRPC procedures

import cacheMiddleware from "@/app/api/middleware/cache";
...
export const publicProcedure = t.procedure.use(cacheMiddleware);

いくつかの注意点:

  • この例は Next.js のアプリルーターを前提としていますが、pages ルーターや他のフレームワークでも機能するはずです
  • キャッシュは query タイプのみに有効であり、mutation や subscription には有効ではありません
  • この手順に提供された入力に基づいて、異なるキャッシュキーを使用します
  • middlewareMarker はややハッキーな修正です。なぜなら、すべてのミドルウェアの戻り値には marker を含める必要があるからです。
  • structuredClone は Node 17 以降でのみ利用可能です。これが利用できない場合は、別のディープクローンの方法が必要です。
  • ログを追加するのが賢明でしょう