はじめに
正直なところ、型安全なAPI開発って悩ましいんですよね。
tRPCを使えばクライアント・サーバー間で型を共有できるけど、外部公開用のREST APIとしては使いにくい。かといってOpenAPIから型を生成するのは手間がかかるし、スキーマとコードの二重管理になりがち。
「型安全なRPC的開発体験」と「OpenAPI標準準拠のREST API」、この両方が欲しい。
そんな贅沢な願いを叶えてくれるのがoRPCというフレームワークです。
oRPCはGitHubで4,200スター以上を獲得している、エンドツーエンドの型安全性とOpenAPI対応を両立した次世代APIフレームワーク。2024年後半から急速に注目を集めていて、tRPCの代替として選ばれるケースが増えています。
oRPCとは
oRPCは、RPCスタイルの開発体験とOpenAPI標準準拠を両立させたAPIフレームワークです。
最大の特徴は「コントラクト・ファースト」な開発ができること。APIの仕様を先に定義して、そこから実装を進められます。しかも、その定義からOpenAPIスキーマを自動生成できるので、Swagger UIでドキュメントを公開したり、外部向けのREST APIとして提供したりできます。
tRPCの良いところを取り入れつつ、「外部公開」という実務でよくある要件にも対応している。このバランス感覚が素晴らしいんですよね。
公式サイト: https://orpc.dev GitHub: https://github.com/unnoq/orpc
特徴・メリット
1. エンドツーエンドの型安全性
oRPCの一番の強みは、クライアントからサーバーまで完全に型が通ることです。
// サーバー側で定義
const getPlanet = os
.input(z.object({ id: z.number() }))
.output(z.object({ id: z.number(), name: z.string() }))
.handler(async ({ input }) => {
return { id: input.id, name: 'Earth' }
})
// クライアント側で使用(型が自動で推論される)
const planet = await orpc.planet.get({ id: 1 })
// planet の型は { id: number, name: string } と推論される
入力も出力もエラーも、全部型安全。IDEの補完がバッチリ効くので、開発効率が格段に上がります。
2. ファーストクラスのOpenAPI対応
これ、意外と他のRPCフレームワークにはない強みなんですよ。
oRPCで定義したAPIから、そのままOpenAPI 3.x準拠のスキーマを生成できます。つまり:
- Swagger UIでAPIドキュメントを公開できる
- 外部パートナー向けにREST APIとして提供できる
- OpenAPIベースのコードジェネレーターが使える
社内ではoRPCクライアントで型安全に開発しつつ、外部向けにはOpenAPI経由でREST APIとして公開する、みたいな使い分けができます。
3. 豊富なスキーマライブラリ対応
バリデーションライブラリは好みのものを選べます。
- Zod - 一番人気
- Valibot - 軽量志向
- ArkType - パフォーマンス重視
個人的にはZodとの組み合わせが一番情報も多くて使いやすいですね。
4. フレームワーク統合
TanStack Query、SWR、React Server Actionsなど、フロントエンドでよく使われるライブラリとの統合がサポートされています。
// TanStack Query との統合例
const { data, isLoading } = useQuery({
queryKey: ['planet', id],
queryFn: () => orpc.planet.get({ id })
})
5. マルチランタイム対応
Node.js、Deno、Bun、Cloudflare Workersなど、さまざまな環境で動作します。Honoと同じくWeb標準APIベースで設計されているので、デプロイ先を選びません。
6. SSE・ストリーミング対応
型安全なServer-Sent Eventsやストリーミングにも対応。AIチャットのようなリアルタイム応答が必要なケースでも使えます。
インストール方法
前提条件
- Node.js 18以上(20以上推奨)
- TypeScriptプロジェクト
パッケージのインストール
# npm
npm install @orpc/server@latest @orpc/client@latest
# pnpm
pnpm add @orpc/server@latest @orpc/client@latest
# bun
bun add @orpc/server@latest @orpc/client@latest
Zodを使う場合は追加でインストール:
npm install zod
基本的な使い方
サーバー側の実装
まず、APIのプロシージャ(手続き)を定義します。
// server/procedures.ts
import { os, ORPCError } from '@orpc/server'
import { z } from 'zod'
// スキーマ定義
const PlanetSchema = z.object({
id: z.number().int().min(1),
name: z.string(),
population: z.number().optional()
})
// 惑星一覧を取得
export const listPlanets = os
.input(
z.object({
limit: z.number().int().min(1).max(100).optional().default(10)
})
)
.output(z.array(PlanetSchema))
.handler(async ({ input }) => {
// 実際にはDBから取得
return [
{ id: 1, name: 'Earth', population: 8000000000 },
{ id: 2, name: 'Mars', population: 0 }
].slice(0, input.limit)
})
// 惑星を取得
export const getPlanet = os
.input(z.object({ id: z.number().int() }))
.output(PlanetSchema)
.handler(async ({ input }) => {
const planet = { id: input.id, name: 'Earth' }
if (!planet) {
throw new ORPCError('NOT_FOUND', 'Planet not found')
}
return planet
})
// 惑星を作成
export const createPlanet = os
.input(
z.object({
name: z.string().min(1),
population: z.number().optional()
})
)
.output(PlanetSchema)
.handler(async ({ input }) => {
// 実際にはDBに保存
return { id: 3, name: input.name, population: input.population }
})
ルーターの作成
プロシージャをまとめてルーターを作成します。
// server/router.ts
import { listPlanets, getPlanet, createPlanet } from './procedures'
export const router = {
planet: {
list: listPlanets,
get: getPlanet,
create: createPlanet
}
}
// クライアント用に型をエクスポート
export type Router = typeof router
HTTPサーバーの起動
// server/index.ts
import { createServer } from 'node:http'
import { RPCHandler } from '@orpc/server/node'
import { CORSPlugin } from '@orpc/server/plugins'
import { router } from './router'
const handler = new RPCHandler(router, {
plugins: [new CORSPlugin()]
})
const server = createServer(async (req, res) => {
const result = await handler.handle(req, res)
if (!result.matched) {
res.statusCode = 404
res.end('Not Found')
}
})
server.listen(3000, () => {
console.log('Server running at http://localhost:3000')
})
クライアント側の実装
// client/index.ts
import { createORPCClient } from '@orpc/client'
import { RPCLink } from '@orpc/client/fetch'
import type { Router } from '../server/router'
// クライアントの作成
const link = new RPCLink({
url: 'http://localhost:3000'
})
export const orpc = createORPCClient<Router>(link)
// 使用例
async function main() {
// 一覧取得(型安全)
const planets = await orpc.planet.list({ limit: 5 })
console.log(planets) // { id: number, name: string, population?: number }[]
// 単一取得
const earth = await orpc.planet.get({ id: 1 })
console.log(earth.name) // string として推論
// 作成
const newPlanet = await orpc.planet.create({
name: 'Venus',
population: 0
})
console.log(newPlanet.id) // number として推論
}
実践的なユースケース
1. 認証付きAPIの実装
ミドルウェアを使って認証を追加できます。
import { os, ORPCError } from '@orpc/server'
import { z } from 'zod'
// 認証ミドルウェア
const authMiddleware = os.middleware(async ({ context, next }) => {
const token = context.headers?.authorization?.replace('Bearer ', '')
if (!token) {
throw new ORPCError('UNAUTHORIZED', 'Authentication required')
}
// トークンを検証(実際にはJWT検証など)
const user = await verifyToken(token)
return next({
context: { ...context, user }
})
})
// 認証が必要なプロシージャ
const protectedProcedure = os.use(authMiddleware)
export const getProfile = protectedProcedure
.output(z.object({
id: z.string(),
email: z.string(),
name: z.string()
}))
.handler(async ({ context }) => {
return context.user
})
2. TanStack Queryとの統合
React アプリケーションでの使用例です。
// hooks/usePlanets.ts
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'
import { orpc } from '../client'
export function usePlanets(limit?: number) {
return useQuery({
queryKey: ['planets', limit],
queryFn: () => orpc.planet.list({ limit })
})
}
export function useCreatePlanet() {
const queryClient = useQueryClient()
return useMutation({
mutationFn: (data: { name: string; population?: number }) =>
orpc.planet.create(data),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['planets'] })
}
})
}
// components/PlanetList.tsx
function PlanetList() {
const { data: planets, isLoading } = usePlanets(10)
const createPlanet = useCreatePlanet()
if (isLoading) return <div>Loading...</div>
return (
<div>
{planets?.map(planet => (
<div key={planet.id}>{planet.name}</div>
))}
<button onClick={() => createPlanet.mutate({ name: 'New Planet' })}>
Add Planet
</button>
</div>
)
}
3. OpenAPIスキーマの生成
oRPCで定義したAPIからOpenAPIスキーマを生成できます。
import { generateOpenAPI } from '@orpc/openapi'
import { router } from './router'
const openAPISchema = generateOpenAPI(router, {
info: {
title: 'Planet API',
version: '1.0.0'
}
})
// スキーマをJSONファイルとして出力
import fs from 'fs'
fs.writeFileSync('openapi.json', JSON.stringify(openAPISchema, null, 2))
これでSwagger UIやRedocでドキュメントを公開できます。
4. エラーハンドリング
型安全なエラーハンドリングも可能です。
import { os, ORPCError } from '@orpc/server'
import { z } from 'zod'
export const deletePlanet = os
.input(z.object({ id: z.number() }))
.output(z.object({ success: z.boolean() }))
.handler(async ({ input }) => {
const planet = await findPlanet(input.id)
if (!planet) {
throw new ORPCError('NOT_FOUND', 'Planet not found')
}
if (planet.name === 'Earth') {
throw new ORPCError('FORBIDDEN', 'Cannot delete Earth')
}
await deletePlanetFromDB(input.id)
return { success: true }
})
クライアント側でもエラーの型が分かるので、適切なエラーハンドリングができます。
tRPCとの違い
よく比較されるtRPCとの違いを整理しておきます。
| 観点 | oRPC | tRPC |
|---|---|---|
| OpenAPI対応 | ファーストクラスサポート | プラグイン経由 |
| コントラクト・ファースト | 対応 | 非対応 |
| 学習コスト | やや低い | 中程度 |
| エコシステム | 成長中 | 成熟 |
| 採用実績 | 新興 | 豊富 |
個人的には、外部向けAPIを公開する予定があるならoRPC、完全に内部利用に閉じるならtRPCでもいい、という判断をしています。
注意点
いいことばかり書いてきましたが、注意点もあります。
1. まだ新しいプロジェクト
oRPCは比較的新しいフレームワークです。tRPCほどの採用実績や情報の蓄積はまだありません。本番導入には慎重な検討が必要です。
2. 日本語の情報が少ない
公式ドキュメントは英語のみ。日本語での情報発信はまだ少ないのが現状です。
3. 破壊的変更の可能性
バージョン1.0に向けて開発が進んでいますが、APIが変わる可能性があります。導入時はバージョンを固定しておくのが無難です。
まとめ
oRPCは「型安全なRPC」と「OpenAPI対応」という、これまで両立が難しかった要件を一つのフレームワークで実現しています。
以下のようなケースで特に力を発揮します:
- 社内向けと外部向けの両方でAPIを使いたい
- 型安全な開発体験を諦めたくない
- OpenAPIでドキュメントを自動生成したい
- tRPCのDXが好きだけど、もう少し柔軟性が欲しい
まだ成熟途上のプロジェクトではありますが、設計思想とDXの良さは注目に値します。個人プロジェクトや新規開発で試してみる価値は十分あると思います。
30代になって思うのは、「型安全」と「ドキュメント」は開発効率だけでなく、チーム開発や長期運用でこそ真価を発揮するということ。oRPCはその両方を最初から考慮して設計されているので、長く使えるフレームワークになる可能性を感じています。
公式サイト: https://orpc.dev GitHub: https://github.com/unnoq/orpc ドキュメント: https://orpc.dev/docs
