メインコンテンツにスキップ

tRPCのご紹介

·読了時間:5分
Alex / KATT 🐱

私はAlex、GitHubでは"KATT"です。tRPCというライブラリについてお話ししたいと思います。まだ記事は公開していませんが、勢いを付けるためにこの紹介記事を書いています(しかし、GitHubではすでに530個以上のスターを獲得しています)。今後、記事や動画による紹介も予定しています! 最新情報を入手したり、質問をしたい場合は、Twitterで@alexdotjsをフォローしてください。

簡単に言うと、tRPCは(Node.js)サーバーからクライアントまで、型を宣言することなくエンドツーエンドの型安全性を提供します。バックエンドでは関数でデータを返すだけで、フロントエンドではエンドポイント名に基づいてそのデータを使用します。

tRPCエンドポイントとクライアント呼び出しを行う場合、以下のように表示されます。 代替テキスト

優れたreact-queryの上に構築されたReact用ライブラリ(@trpc/react)を作成しましたが、クライアントライブラリ(@trpc/client)はReactなしでも動作します(特定のSvelte/Vue/Angular/[..]ライブラリを構築したい場合は、ご連絡ください!)

コード生成は不要で、既存のNext.js/CRA/Expressプロジェクトに簡単に追加できます。

string型の引数を取る、helloというtRPCプロシージャ(別名エンドポイント)の例を次に示します。

tsx
const appRouter = trpc.router().query('hello', {
input: z.string().optional(),
resolve: ({ input }) => {
return {
text: `hello ${input ?? 'world'}`,
};
},
});
export type AppRouter = typeof appRouter;
tsx
const appRouter = trpc.router().query('hello', {
input: z.string().optional(),
resolve: ({ input }) => {
return {
text: `hello ${input ?? 'world'}`,
};
},
});
export type AppRouter = typeof appRouter;

そして、そのデータを使用する型安全なクライアントは次のとおりです。

tsx
import type { AppRouter } from './server';
async function main() {
const client = createTRPCClient<AppRouter>({
url: `http://localhost:2022`,
});
const result = await client.query('hello', '@alexdotjs');
console.log(result); // --> { text: "hello @alexdotjs" }
}
main();
tsx
import type { AppRouter } from './server';
async function main() {
const client = createTRPCClient<AppRouter>({
url: `http://localhost:2022`,
});
const result = await client.query('hello', '@alexdotjs');
console.log(result); // --> { text: "hello @alexdotjs" }
}
main();

型安全性を確保するために必要なのはこれだけです! resultは、バックエンドの関数が返す値から型推論されます。入力データもバリデータの戻り値から推論されるため、データはそのまま安全に使用できます。実際、入力データをバリデータに渡す必要があります(tRPCはzod/yup/カスタムバリデータとそのまま連携します)。

上記の例を試せるCodeSandboxのリンクはこちらです:https://githubbox.com/trpc/trpc/tree/next/examples/standalone-server(プレビューではなく、ターミナルの出力を確認してください!)

え?バックエンドからクライアントにコードをインポートしているのですか? - いいえ、実際にはインポートしていません。

そう見えるかもしれませんが、サーバーからクライアントにコードは共有されません。TypeScriptのimport typeは "[..]型注釈と宣言に使用される宣言のみをインポートします。常に完全に削除されるため、実行時には痕跡が残っていません。" - TypeScript 3.8で追加された機能 - TypeScriptのドキュメントを参照してください

コード生成は不要です。サーバーからクライアントに型を共有する方法があれば(すでにモノレポを使用していることが望ましい)、今日からアプリにこれを追加できます。

しかし、これはまだ始まりに過ぎません!

前述したように、Reactライブラリがあります。Reactで上記のデータを使用するには、次のようにします。

tsx
const { data } = trpc.useQuery(['hello', '@alexdotjs']);
tsx
const { data } = trpc.useQuery(['hello', '@alexdotjs']);

...すると、クライアントで型安全なデータが得られます。

既存のブラウンフィールドプロジェクト(Express/Next.js用アダプターがあります)で今日からtRPCを追加できます。CRAでも正常に動作し、React Nativeでも動作するはずです。Reactに限定されていないため、SvelteやVueのライブラリを作成したい場合は、ご連絡ください。

データの変更はどうですか?

ミューテーションはクエリと同じくらい簡単です。実際には内部的には同じですが、構文上の都合で異なって公開されており、GETリクエストではなくHTTP POSTリクエストを生成します。

データベースを使用した、もう少し複雑な例を次に示します。これは、todomvc.trpc.io / https://github.com/trpc/trpc/tree/next/examples/next-prisma-todomvcにあるTodoMVCの例から引用したものです。

tsx
const todoRouter = createRouter().mutation('add', {
input: z.object({
id: z.string().uuid(),
data: z.object({
completed: z.boolean().optional(),
text: z.string().min(1).optional(),
}),
}),
async resolve({ ctx, input }) {
const { id, data } = input;
const todo = await ctx.task.update({
where: { id },
data,
});
return todo;
},
});
tsx
const todoRouter = createRouter().mutation('add', {
input: z.object({
id: z.string().uuid(),
data: z.object({
completed: z.boolean().optional(),
text: z.string().min(1).optional(),
}),
}),
async resolve({ ctx, input }) {
const { id, data } = input;
const todo = await ctx.task.update({
where: { id },
data,
});
return todo;
},
});

そして、Reactでの使用例は次のようになります。

tsx
const addTask = trpc.useMutation('todos.add');
return (
<>
<input
placeholder="What needs to be done?"
onKeyDown={(e) => {
const text = e.currentTarget.value.trim();
if (e.key === 'Enter' && text) {
addTask.mutate({ text });
e.currentTarget.value = '';
}
}}
/>
</>
)
tsx
const addTask = trpc.useMutation('todos.add');
return (
<>
<input
placeholder="What needs to be done?"
onKeyDown={(e) => {
const text = e.currentTarget.value.trim();
if (e.key === 'Enter' && text) {
addTask.mutate({ text });
e.currentTarget.value = '';
}
}}
/>
</>
)

とりあえず、終わりです。

とにかく、言ったように、私はただ勢いをつけたかっただけです。他にもたくさんのことがあります。

  • リゾルバーに依存性注入されるユーザー固有データの着信リクエストのコンテキストを作成する - リンク
  • ルーターのミドルウェアサポート - リンク
  • ルーターのマージ(すべてのバックエンドデータを1つのファイルにまとめたくないでしょう) - リンク
  • @trpc/nextアダプターを使用した、Reactでこれまでで最も簡単なサーバーサイドレンダリング - リンク
  • 型安全なエラーフォーマット - リンク
  • データトランスフォーマー(ネットワークを介してDate/Map/Setオブジェクトを使用する) - リンク
  • React Queryのヘルパー

始めるには、Next.jsの入門ガイドにいくつかの例があります。

最新情報については、Twitterで私をフォローしてください!