mcpField
O
orpc-fullstack

orpc-fullstack

github.com/stephen-golban開発38 回インストール
リポジトリ

orpc-fullstack について

oRPCでTypeScript APIを構築する開発者向けの実践ガイドです。コントラクト、プロシージャ、ルーター、ミドルウェア、クライアントに加え、Hono、TanStack Query、リアルタイムイベント、エラー処理を扱います。

主な機能

  1. 01コントラクトファーストのAPI開発
  2. 02入出力バリデーションとミドルウェア
  3. 03型付きエラー処理
  4. 04Honoサーバー連携
  5. 05TanStack Queryによるデータ取得
  6. 06SSEとイベントイテレーター

活用シーン

  1. 01コントラクトに基づくAPIルートの実装
  2. 02プロシージャへの認証と認可の追加
  3. 03TanStack Queryによるデータ取得
  4. 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 / RPCHandlerHTTP/WS 経由でプロシージャを提供する
クライアントcreateORPCClient + linkコントラクト/ルーターから型安全なクライアントを作成する
TanStack QuerycreateTanstackQueryUtilsクエリ/ミューテーション用の 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 との統合、メタデータを網羅した、詳細なコード例付きの完全なドキュメントです。

コメント