🧊

Cloudflare WorkersでもtRPCを使う

検索してみてもシュッと解決策にたどり着けなかったので書いておく。

TL;DR

  • 特別なモジュールは不要で、公式サポートされてる
  • R2などBindingsを通すところだけ、少し工夫が必要

コード

それ用のハンドラが用意されてるので、fetch()の中で使う。

import { fetchRequestHandler } from "@trpc/server/adapters/fetch";
import { createContext, createRouter } from "./rpc/router";
import type { FetchCreateContextFnOptions } from "@trpc/server/adapters/fetch";
import type { Env } from "./types/private";

export default {
  async fetch(req: Request, env: Env, ctx: ExecutionContext) {
    const url = new URL(req.url);

    if (url.pathname.startsWith("/rpc/"))
      return fetchRequestHandler({
        endpoint: "/rpc",
        req,
        router: createRouter(),
        createContext: (options: FetchCreateContextFnOptions) =>
          createContext(options, [env, ctx]), // 👈
      });

    return new Response("Not Found", { status: 404 });
  },
};

細かいimportはそれぞれよしなにするとして、createContext()を関数にして、そこで渡せばよい。

import { initTRPC } from "@trpc/server";
import type { FetchCreateContextFnOptions } from "@trpc/server/adapters/fetch";
import type { Env } from "../types/private";

export const createContext = async (
  { req }: FetchCreateContextFnOptions,
  [env]: [Env, ExecutionContext],
) => {
  // 🎉
  return { DB: env.DB };
};

こうしておけば、それぞれのハンドラで使える。

export const createRouter = () => t.router({
  listAllTodos: t.procedure
    .query(({ input, ctx }) => {
      const rows = await ctx.DB.prepare("SELECT * FROM todos").all();
      return { rows };
    }),
});

簡単。

おまけ

Cloudflare WorkersでtRPCするなら、バリデーションは軽量なsuperstructvalibotがいいかなと思ってるところ。

ただしvalibotは、最新の0.13系ではドキュメント通りには動かない。

// NG
.input(string())

// OK
.input((raw) => parseAsync(string(), raw))

なるほど。

feat: support valibot > 0.12.0 · Issue #4737 · trpc/trpc server: add support for next valibot version by fabian-hiller · Pull Request #4715 · trpc/trpc