orpc-fullstack について
oRPCでTypeScript APIを構築する開発者向けの実践ガイドです。コントラクト、プロシージャ、ルーター、ミドルウェア、クライアントに加え、Hono、TanStack Query、リアルタイムイベント、エラー処理を扱います。
主な機能
- 01コントラクトファーストのAPI開発
- 02入出力バリデーションとミドルウェア
- 03型付きエラー処理
- 04Honoサーバー連携
- 05TanStack Queryによるデータ取得
- 06SSEとイベントイテレーター
活用シーン
- 01コントラクトに基づくAPIルートの実装
- 02プロシージャへの認証と認可の追加
- 03TanStack Queryによるデータ取得
- 04SSEストリーミング機能の構築
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ stephen-golban/ orpc-fullstack --skill orpc-fullstackインストール後、Claude Code で/orpc-fullstackを入力して読み込まれたか確認します。
同梱ファイルreferences/REFERENCE.md
詳細
oRPC — 型安全な API をシンプルに
oRPC は RPC と OpenAPI を組み合わせて、エンドツーエンドで型安全な API を実現します(v1.12+)。Zod、Valibot、Arktype、または任意の Standard Schema ライブラリに対応しています。
適用する場面
次のような場合に、これらのガイドラインを参照してください。
- API プロシージャ(コントラクト、ルーター、ハンドラー)を作成または変更する
- ミドルウェア(認証、認可ガード、ロギング)を設定する
- oRPC を Hono やその他のサーバーフレームワークと統合する
- TanStack Query でデータ取得フックを構築する
- イベントイテレーター/SSE でリアルタイム機能を実装する
- クライアントリンク(fetch、WebSocket、バッチ、リトライ)を設定する
- エラー(サーバー側の ORPCError、クライアント側の型付きエラー)を処理する
コアコンセプト
| コンセプト | インポート | 目的 |
|---|---|---|
| コントラクト | oc from @orpc/contract | ハンドラーなしで API の形を定義する |
| プロシージャ | os from @orpc/server | バリデーション、ミドルウェア、DI を備えた関数 |
| ルーター | Plain object | プロシージャをツリー状に組み合わせる |
| ミドルウェア | os.middleware() | リクエストをインターセプトし、コンテキストを注入してアクセスを制御する |
| ハンドラー | OpenAPIHandler / RPCHandler | HTTP/WS 経由でプロシージャを提供する |
| クライアント | createORPCClient + link | コントラクト/ルーターから型安全なクライアントを作成する |
| TanStack Query | createTanstackQueryUtils | クエリ/ミューテーション用の React フック |
プロジェクト構成
コントラクトファイルとルーターファイルを対にして、ドメインモジュールごとに構成します。
src/
index.ts # Hono app + handler setup
middlewares/
auth-middleware.ts # Session validation -> injects user
modules/
contract.ts # Root barrel: all contracts
router.ts # Root barrel: all routers
health/
health.contract.ts
health.router.ts
user/
user.contract.ts
user.router.ts
ルートのバレルでモジュールを組み合わせます:export default { health, user }。
コントラクトファースト開発
コントラクトで API の形を定義します。ルーターでは TypeScript によるチェックを適用しながら実装します。
// Contract — define shape
import { oc } from "@orpc/contract";
import { z } from "zod/v4";
const userContract = oc
.route({ tags: ["user"] })
.errors({ UNAUTHORIZED: {} });
const searchUser = userContract
.route({ method: "POST", path: "/user/search" })
.input(z.object({ query: z.string() }))
.output(z.array(userSchema));
export default { searchUser };
// Router — implement contract
import { implement } from "@orpc/server";
import contract from "./user.contract";
const router = implement(contract).$context<{ headers: Headers }>();
const searchUser = router.searchUser
.use(authMiddleware)
.handler(async ({ input, context }) => { /* ... */ });
export default { searchUser };
プロシージャ
import { os } from "@orpc/server";
const example = os
.use(aMiddleware) // Middleware
.input(z.object({ name: z.string() })) // Validate input
.output(z.object({ id: z.number() })) // Validate output (recommended)
.handler(async ({ input, context }) => { // Handler
return { id: 1 };
});
- 必須の手順は
.handlerのみです .outputを指定すると、TypeScript の型推論が速くなります- 再利用可能なベースを作成します:
const protectedProcedure = os.$context<Ctx>().use(authMiddleware)
ミドルウェア
認証ミドルウェアはユーザーをコンテキストに注入します。
export const authMiddleware = os
.$context<{ headers: Headers }>()
.middleware(async ({ context, next }) => {
const session = await auth.api.getSession({ headers: context.headers });
if (!session) throw new ORPCError("UNAUTHORIZED");
return next({ context: { ...context, user: session.user } });
});
認可ガードには、入力を認識するミドルウェアを使用します。
export const membershipGuard = os
.$context<{ user: User }>()
.middleware(async ({ context, next }, input: { uuid: string }) => {
// Check membership using input.uuid + context.user.id
if (!member) throw new ORPCError("FORBIDDEN");
return next();
});
ミドルウェアは左から右に積み重ねます:.use(auth).use(guard).handler(...)。
組み込みのミドルウェア:onStart、onSuccess、onError、onFinish、dedupeMiddleware。
エラー処理
// Server — throw errors
throw new ORPCError("NOT_FOUND");
throw new ORPCError("BAD_REQUEST", { message: "Invalid input" });
// Contract-defined typed errors
const contract = oc.errors({
RATE_LIMITED: { data: z.object({ retryAfter: z.number() }) },
});
// Handler uses typed factory
const proc = implement(contract).handler(async ({ errors }) => {
throw errors.RATE_LIMITED({ data: { retryAfter: 60 } });
});
// Client — handle errors
const [error, data] = await safe(client.doSomething({ id: "123" }));
if (isDefinedError(error)) { /* typed from contract */ }
Hono との統合
import { OpenAPIHandler } from "@orpc/openapi/fetch";
import { Hono } from "hono";
const handler = new OpenAPIHandler(router, { /* plugins, interceptors */ });
const app = new Hono()
.basePath("/api")
.use("/rpc/*", async (c, next) => {
const { matched, response } = await handler.handle(c.req.raw, {
prefix: "/api/rpc",
context: { headers: c.req.raw.headers },
});
if (matched) return c.newResponse(response.body, response);
await next();
});
TanStack Query
import { createTanstackQueryUtils } from "@orpc/tanstack-query";
const orpc = createTanstackQueryUtils(client);
// Queries
useQuery(orpc.user.search.queryOptions({ input: { query } }));
// Mutations
useMutation(orpc.vehicle.add.mutationOptions());
// Infinite queries
useInfiniteQuery(orpc.feed.list.infiniteOptions({
input: (pageParam) => ({ cursor: pageParam, limit: 20 }),
initialPageParam: undefined,
getNextPageParam: (lastPage) => lastPage.nextCursor,
}));
// Keys for invalidation
orpc.vehicle.key() // All vehicle queries
queryClient.invalidateQueries({ queryKey: orpc.vehicle.key() });
イベントイテレーター(SSE/ストリーミング)
// Server — async generator
const live = os
.output(eventIterator(z.object({ message: z.string() })))
.handler(async function* ({ signal }) {
for await (const payload of publisher.subscribe("topic", { signal })) {
yield payload;
}
});
// Client — consume
for await (const event of await client.live()) {
console.log(event.message);
}
ハンドラー間の型付き pub/sub には EventPublisher を使用します。
クライアントの設定
import { RPCLink } from "@orpc/client/fetch";
import { createORPCClient } from "@orpc/client";
const link = new RPCLink({
url: "http://localhost:3000/api/rpc",
headers: () => ({ Authorization: `Bearer ${getToken()}` }),
});
export const client = createORPCClient(link);
WebSocket:import { RPCLink } from "@orpc/client/websocket"。
詳細なリファレンス
包括的な例、高度なパターン、API 全体の詳細については、次を参照してください。
- 完全なリファレンス — コントラクト、ルーター、ミドルウェア、サーバーハンドラー、クライアントの設定、TanStack Query、イベントイテレーター、プラグイン、ファイルアップロード、WebSocket、AI SDK との統合、メタデータを網羅した、詳細なコード例付きの完全なドキュメントです。