EasyStarter logoEasyStarter

API 開発

oRPC で型安全な API を構築

概要

EasyStarter は oRPC を使用してエンドツーエンドの型安全な API 開発を行い、tRPC と OpenAPI の良いところを組み合わせています。

アーキテクチャ

apps/server/src/
├── routers/           # API ルーター
│   ├── index.ts       # メインルーター
│   └── user.ts        # ユーザー関連プロシージャ
├── handlers/          # ビジネスロジック
└── middlewares/       # 認証、ログなど

ルーターの作成

プロシージャの定義

// apps/server/src/routers/post.ts
import { publicProcedure, protectedProcedure, router } from "../lib/orpc";
import { z } from "zod";

export const postRouter = router({
	// パブリックプロシージャ
	list: publicProcedure
		.input(
			z.object({
				page: z.number().default(1),
				limit: z.number().default(10),
			}),
		)
		.query(async ({ input, ctx }) => {
			return ctx.db.select().from(posts).limit(input.limit);
		}),

	// 保護されたプロシージャ(認証が必要)
	create: protectedProcedure
		.input(
			z.object({
				title: z.string().min(1),
				content: z.string(),
			}),
		)
		.mutation(async ({ input, ctx }) => {
			return ctx.db.insert(posts).values({
				...input,
				authorId: ctx.user.id,
			});
		}),
});

ルーターの登録

// apps/server/src/routers/index.ts
import { postRouter } from "./post";

export const appRouter = router({
	post: postRouter,
	// ... 他のルーター
});

export type AppRouter = typeof appRouter;

フロントエンドでの使用

クライアントの設定

// apps/web/src/lib/api.ts
import { createORPCClient } from "@orpc/client";
import type { AppRouter } from "@server/routers";

export const api = createORPCClient<AppRouter>({
	baseURL: import.meta.env.VITE_SERVER_URL,
});

データのクエリ

// React Query を使用
const { data, isLoading } = api.post.list.useQuery({
	page: 1,
	limit: 10,
});

データの変更

const createPost = api.post.create.useMutation({
	onSuccess: () => {
		queryClient.invalidateQueries(["post", "list"]);
	},
});

await createPost.mutateAsync({
	title: "Hello World",
	content: "私の最初の投稿",
});

ミドルウェア

認証ミドルウェア

const authMiddleware = middleware(async ({ ctx, next }) => {
	const session = await getSession(ctx.request);
	if (!session) {
		throw new ORPCError("UNAUTHORIZED");
	}
	return next({ ctx: { ...ctx, user: session.user } });
});

export const protectedProcedure = publicProcedure.use(authMiddleware);

エラーハンドリング

import { ORPCError } from "@orpc/server";

throw new ORPCError("NOT_FOUND", { message: "投稿が見つかりません" });
throw new ORPCError("FORBIDDEN", { message: "アクセスが拒否されました" });
throw new ORPCError("BAD_REQUEST", { message: "入力が無効です" });

On this page