はじめに
TypeScript界隈でtRPCが採用されるケースが増えている感覚がありますが、今回はSvelteKitでtRPCを使う方法について解説しようと思います。SvelteKitとtRPCを組み合わせることで、フルスタックのTypeScriptアプリケーションを簡単に作成できます。tRPCは、クライアントとサーバー間の通信を型安全に行うことができる素晴らしいツールです。そして、trpc-sveltekitを使うことで、SvelteKitとtRPCの統合がとても簡単になります。
tRPCとは
tRPCは、TypeScriptベースのRPC(Remote Procedure Call)フレームワークです。tRPCを使うと、APIの定義とクライアントとの通信を型安全に行うことができます。これにより、エンドポイントごとに異なる型を定義しなくても、TypeScriptの型推論を最大限に活用できるようになります。tRPCの主な特徴は以下の通りです。
- 型安全: サーバーとクライアント間の通信が完全に型安全。
- シンプル: 簡単にセットアップして使用できる。
- 柔軟: RESTやGraphQLのような特定のプロトコルに縛られない。
trpc-sveltekitとは
trpc-sveltekitは、SvelteKitプロジェクトでtRPCを簡単に統合できるパッケージです。このパッケージを使うことで、SvelteKitの強力な機能とtRPCの型安全な通信を組み合わせることができます。trpc-sveltekitは、SvelteKitのエンドポイントとtRPCのルーターをシームレスに統合し、開発者の負担を軽減します。
導入方法
SvelteKitのプロジェクトがすでにセットアップ済みである前提で、必要なパッケージのインストールをしてゆきます。
npm install @trpc/server @trpc/client trpc-sveltekit
tRPCルーターの設定
src/lib/trpc/router.tsを作成し、以下のコードを追加します。
import type { Context } from '$lib/trpc/context';
import { initTRPC } from '@trpc/server';
import delay from 'delay';
export const t = initTRPC.context<Context>().create();
export const router = t.router({
greeting: t.procedure.query(async () => {
await delay(500); // 👈 500msかかる処理をシミュレーション
return `Hello tRPC @ ${new Date().toLocaleTimeString()}`;
})
});
export type Router = typeof router;
tRPC Contextを作成
src/lib/trpc/context.tsを作成し、以下のコードを追加します。
import type { RequestEvent } from '@sveltejs/kit';
import type { inferAsyncReturnType } from '@trpc/server';
export async function createContext(event: RequestEvent) {
return {
// eventからlocalsやurlなどのオブジェクトが取得できます
};
}
export type Context = inferAsyncReturnType<typeof createContext>;
handleにtRPCHandleを追加
src/hooks.server.tsのhandleに以下のコードを追加します。
import { createContext } from '$lib/trpc/context';
import { router } from '$lib/trpc/router';
import type { Handle } from '@sveltejs/kit';
import { createTRPCHandle } from 'trpc-sveltekit';
// sequenceを使って複数のhandleを追加する例
export const handle: Handle = sequence(
somthingHanfle,
createTRPCHandle({ router, createContext })
);
ヘルパー関数の作成
PageでtRPCを簡単に使用できるように、lib/trpc/client.tsにヘルパー関数を作成します。
import type { Router } from '$lib/trpc/router';
import { createTRPCClient, type TRPCClientInit } from 'trpc-sveltekit';
let browserClient: ReturnType<typeof createTRPCClient<Router>>;
export function trpc(init?: TRPCClientInit) {
const isBrowser = typeof window !== 'undefined';
if (isBrowser && browserClient) return browserClient;
const client = createTRPCClient<Router>({ init });
if (isBrowser) browserClient = client;
return client;
}
関数を実行してみる
src/routes/+page.svelteに、以下のようなpageを作成します。
<script lang="ts">
import { page } from '$app/stores';
import { trpc } from '$lib/trpc/client';
let greeting = 'ボタンをクリックしてデータを取得';
let loading = false;
const loadData = async () => {
loading = true;
// tRPC Routerに作成した関数を実行する
greeting = await trpc($page).greeting.query();
loading = false;
};
</script>
<h6>Loading data in<br /><code>+page.svelte</code></h6>
<a
href="#load"
role="button"
class="secondary"
aria-busy={loading}
on:click|preventDefault={loadData}>Load</a
>
<p>{greeting}</p>
ボタンをクリックすると500msのディレイの後に「Hello tRPC」というメッセージが表示されます。
引数を渡してみる
Zodを使用して引数のスキーマを定義することができます。
Router内の処理が実行される前に引数のバリデーションチェックも実施されます。
例として src/lib/trpc/router.ts を以下のように改変します。
// Zodスキーマ
const schema = z.object({
message: .string().min(1)
});
export const router = t.router({
// input関数を使ってスキーマを設定します
greeting: t.procedure.input(schema).query(async ({ input }) => {
...
// inputオブジェクトからスキーマの値を取得できます
return `Hello tRPC @ ${input.message}}`;
})
});
引数を指定して関数を実行
src/routes/+page.svelteも併せて改変します。
<script lang="ts">
...
const loadData = async () => {
...
// tRPC Routerに作成した関数を実行する
greeting = await trpc($page).greeting.query({ message: 'Japan' });
...
};
</script>
...
ボタンをクリックすると「Hello tRPC @ Japan」というメッセージが表示されるようになりました。
情報の更新を伴う関数
query()はGETメソッドでリクエストされますが、POSTメソッドはmutation()という関数を使用します。
mutationは、データの作成、更新、削除などを行うために使用されます。
mutationを使用するためにsrc/lib/trpc/router.tsにmutation用のルーティングを追加してゆきます。
// Zodスキーマ
const schema = z.object({
message: .string().min(1)
});
export const router = t.router({
greeting: ...,
postMessage: t.procedure.input(schema).mutation(async ({ ctx, input }) => {
// 更新を伴う処理など
// オブジェクトを返却することもできます
return {
id: ...
}
}
});
Pageから実行する
実行の仕方はquery()と同じです。
const result = await trpc().createUser.mutate({
message: 'Earth'
});
// 返り値も型安全です
// { id: xxxxxx }
result
まとめ
以上で、SvelteKitとtRPCを使用する方法の説明となります。tRPCは、型安全な通信を提供し、開発者の生産性を向上させる素晴らしいツールです。trpc-sveltekitを使えば、SvelteKitとの統合も簡単に行えます。ぜひ、この組み合わせを試してみてください。
参考