trpc-router-creating
BoxLogのtRPC v11ルーターを作成。サービス層分離、Zodバリデーション、エラーハンドリングを適用。
$ Instalar
git clone https://github.com/majiayu000/claude-skill-registry /tmp/claude-skill-registry && cp -r /tmp/claude-skill-registry/skills/development/trpc-router-creating ~/.claude/skills/claude-skill-registry// tip: Run this command in your terminal to install the skill
SKILL.md
name: trpc-router-creating description: BoxLogのtRPC v11ルーターを作成。サービス層分離、Zodバリデーション、エラーハンドリングを適用。
tRPC Router Creating Skill
BoxLogプロジェクトのtRPC v11ルーターを規約に沿って作成するスキルです。
このスキルを使用するタイミング
以下のキーワードが含まれる場合に自動的に起動:
- 「APIを作成」「エンドポイント追加」
- 「tRPCルーター」「router作成」
- 「バックエンド実装」
- 「CRUD API」
ルーター構造
src/server/api/routers/{entity}/
├── index.ts # ルーターのマージ・エクスポート
├── crud.ts # 基本CRUD操作
├── bulk.ts # バルク操作(optional)
├── statistics.ts # 統計(optional)
└── __tests__/
└── crud.test.ts
作成手順
1. スキーマ定義(Zod)
// src/schemas/{entity}.ts
import { z } from 'zod'
export const {entity}IdSchema = z.object({
id: z.string().uuid(),
})
export const create{Entity}Schema = z.object({
name: z.string().min(1).max(100),
description: z.string().optional(),
// other fields
})
export const update{Entity}Schema = create{Entity}Schema.partial()
export const {entity}FilterSchema = z.object({
search: z.string().optional(),
limit: z.number().min(1).max(100).default(50),
offset: z.number().min(0).default(0),
})
2. サービス層(ビジネスロジック)
// src/server/services/{entity}/index.ts
import { SupabaseClient } from '@supabase/supabase-js'
export class {Entity}ServiceError extends Error {
constructor(
public code: string,
message: string
) {
super(message)
this.name = '{Entity}ServiceError'
}
}
export function create{Entity}Service(supabase: SupabaseClient) {
return {
async list(params: { userId: string; limit?: number; offset?: number }) {
const { data, error } = await supabase
.from('{entities}')
.select('*')
.eq('user_id', params.userId)
.range(params.offset ?? 0, (params.offset ?? 0) + (params.limit ?? 50) - 1)
if (error) {
throw new {Entity}ServiceError('FETCH_FAILED', error.message)
}
return data
},
async getById(params: { userId: string; {entity}Id: string }) {
const { data, error } = await supabase
.from('{entities}')
.select('*')
.eq('id', params.{entity}Id)
.eq('user_id', params.userId)
.single()
if (error) {
throw new {Entity}ServiceError('NOT_FOUND', error.message)
}
return data
},
async create(params: { userId: string; input: Create{Entity}Input }) {
const { data, error } = await supabase
.from('{entities}')
.insert({ ...params.input, user_id: params.userId })
.select()
.single()
if (error) {
throw new {Entity}ServiceError('CREATE_FAILED', error.message)
}
return data
},
async update(params: { userId: string; {entity}Id: string; input: Update{Entity}Input }) {
const { data, error } = await supabase
.from('{entities}')
.update(params.input)
.eq('id', params.{entity}Id)
.eq('user_id', params.userId)
.select()
.single()
if (error) {
throw new {Entity}ServiceError('UPDATE_FAILED', error.message)
}
return data
},
async delete(params: { userId: string; {entity}Id: string }) {
const { error } = await supabase
.from('{entities}')
.delete()
.eq('id', params.{entity}Id)
.eq('user_id', params.userId)
if (error) {
throw new {Entity}ServiceError('DELETE_FAILED', error.message)
}
return { success: true }
},
}
}
3. CRUDルーター
// src/server/api/routers/{entity}/crud.ts
import { TRPCError } from '@trpc/server'
import { z } from 'zod'
import { create{Entity}Schema, {entity}IdSchema, update{Entity}Schema } from '@/schemas/{entity}'
import { createTRPCRouter, protectedProcedure } from '@/server/api/trpc'
import { create{Entity}Service, {Entity}ServiceError } from '@/server/services/{entity}'
function handleServiceError(error: unknown): never {
if (error instanceof {Entity}ServiceError) {
const codeMap: Record<string, 'INTERNAL_SERVER_ERROR' | 'NOT_FOUND' | 'BAD_REQUEST'> = {
FETCH_FAILED: 'INTERNAL_SERVER_ERROR',
NOT_FOUND: 'NOT_FOUND',
CREATE_FAILED: 'INTERNAL_SERVER_ERROR',
UPDATE_FAILED: 'INTERNAL_SERVER_ERROR',
DELETE_FAILED: 'INTERNAL_SERVER_ERROR',
}
throw new TRPCError({
code: codeMap[error.code] ?? 'INTERNAL_SERVER_ERROR',
message: error.message,
})
}
throw new TRPCError({
code: 'INTERNAL_SERVER_ERROR',
message: error instanceof Error ? error.message : 'Unknown error',
})
}
export const {entity}CrudRouter = createTRPCRouter({
list: protectedProcedure.query(async ({ ctx }) => {
const service = create{Entity}Service(ctx.supabase)
try {
return await service.list({ userId: ctx.userId })
} catch (error) {
handleServiceError(error)
}
}),
getById: protectedProcedure.input({entity}IdSchema).query(async ({ ctx, input }) => {
const service = create{Entity}Service(ctx.supabase)
try {
return await service.getById({ userId: ctx.userId, {entity}Id: input.id })
} catch (error) {
handleServiceError(error)
}
}),
create: protectedProcedure.input(create{Entity}Schema).mutation(async ({ ctx, input }) => {
const service = create{Entity}Service(ctx.supabase)
try {
return await service.create({ userId: ctx.userId, input })
} catch (error) {
handleServiceError(error)
}
}),
update: protectedProcedure
.input(z.object({ id: z.string().uuid(), data: update{Entity}Schema }))
.mutation(async ({ ctx, input }) => {
const service = create{Entity}Service(ctx.supabase)
try {
return await service.update({
userId: ctx.userId,
{entity}Id: input.id,
input: input.data,
})
} catch (error) {
handleServiceError(error)
}
}),
delete: protectedProcedure.input({entity}IdSchema).mutation(async ({ ctx, input }) => {
const service = create{Entity}Service(ctx.supabase)
try {
return await service.delete({ userId: ctx.userId, {entity}Id: input.id })
} catch (error) {
handleServiceError(error)
}
}),
})
4. ルーターのマージ
// src/server/api/routers/{entity}/index.ts
import { mergeRouters } from '@/server/api/trpc'
import { {entity}CrudRouter } from './crud'
// import { {entity}BulkRouter } from './bulk' // optional
export const {entity}Router = mergeRouters(
{entity}CrudRouter,
// {entity}BulkRouter,
)
5. メインルーターに追加
// src/server/api/root.ts
import { {entity}Router } from './routers/{entity}'
export const appRouter = createTRPCRouter({
// existing routers...
{entity}: {entity}Router,
})
アーキテクチャ
┌─────────────────────┐
│ tRPC Router │ ← 入力バリデーション + エラーハンドリング
├─────────────────────┤
│ Service Layer │ ← ビジネスロジック
├─────────────────────┤
│ Supabase Client │ ← データアクセス
└─────────────────────┘
チェックリスト
- Zodスキーマを
src/schemas/に作成 - サービス層を
src/server/services/に作成 -
protectedProcedureを使用(認証必須) - エラーハンドリングで
TRPCErrorを使用 -
user_idでフィルタリング(マルチテナント) - テストファイル作成
- メインルーターに登録
既存ルーター参考
src/server/api/routers/
├── plans/ # 最も完成度が高い例
│ ├── index.ts
│ ├── crud.ts
│ ├── bulk.ts
│ ├── statistics.ts
│ └── __tests__/
├── profile.ts # シンプルな例
└── notifications.ts
Repository

majiayu000
Author
majiayu000/claude-skill-registry/skills/development/trpc-router-creating
0
Stars
0
Forks
Updated1d ago
Added1w ago