type-safety-validation
Zod、tRPC、Prisma、TypeScript 5.7+を連携させ、データベースからUIまで一貫した型安全性を実現し、2025年以降の開発で信頼性の高いアプリケーションを構築するSkill。
📜 元の英語説明(参考)
Achieve end-to-end type safety with Zod runtime validation, tRPC type-safe APIs, Prisma ORM, and TypeScript 5.7+ features. Build fully type-safe applications from database to UI for 2025+ development.
🇯🇵 日本人クリエイター向け解説
Zod、tRPC、Prisma、TypeScript 5.7+を連携させ、データベースからUIまで一貫した型安全性を実現し、2025年以降の開発で信頼性の高いアプリケーションを構築するSkill。
※ jpskill.com 編集部が日本のビジネス現場向けに補足した解説です。Skill本体の挙動とは独立した参考情報です。
下記のコマンドをコピーしてターミナル(Mac/Linux)または PowerShell(Windows)に貼り付けてください。 ダウンロード → 解凍 → 配置まで全自動。
mkdir -p ~/.claude/skills && cd ~/.claude/skills && curl -L -o type-safety-validation.zip https://jpskill.com/download/17258.zip && unzip -o type-safety-validation.zip && rm type-safety-validation.zip
$d = "$env:USERPROFILE\.claude\skills"; ni -Force -ItemType Directory $d | Out-Null; iwr https://jpskill.com/download/17258.zip -OutFile "$d\type-safety-validation.zip"; Expand-Archive "$d\type-safety-validation.zip" -DestinationPath $d -Force; ri "$d\type-safety-validation.zip"
完了後、Claude Code を再起動 → 普通に「動画プロンプト作って」のように話しかけるだけで自動発動します。
💾 手動でダウンロードしたい(コマンドが難しい人向け)
- 1. 下の青いボタンを押して
type-safety-validation.zipをダウンロード - 2. ZIPファイルをダブルクリックで解凍 →
type-safety-validationフォルダができる - 3. そのフォルダを
C:\Users\あなたの名前\.claude\skills\(Win)または~/.claude/skills/(Mac)へ移動 - 4. Claude Code を再起動
⚠️ ダウンロード・利用は自己責任でお願いします。当サイトは内容・動作・安全性について責任を負いません。
🎯 このSkillでできること
下記の説明文を読むと、このSkillがあなたに何をしてくれるかが分かります。Claudeにこの分野の依頼をすると、自動で発動します。
📦 インストール方法 (3ステップ)
- 1. 上の「ダウンロード」ボタンを押して .skill ファイルを取得
- 2. ファイル名の拡張子を .skill から .zip に変えて展開(macは自動展開可)
- 3. 展開してできたフォルダを、ホームフォルダの
.claude/skills/に置く- · macOS / Linux:
~/.claude/skills/ - · Windows:
%USERPROFILE%\.claude\skills\
- · macOS / Linux:
Claude Code を再起動すれば完了。「このSkillを使って…」と話しかけなくても、関連する依頼で自動的に呼び出されます。
詳しい使い方ガイドを見る →- 最終更新
- 2026-05-18
- 取得日時
- 2026-05-18
- 同梱ファイル
- 1
📖 Skill本文(日本語訳)
※ 原文(英語/中国語)を Gemini で日本語化したものです。Claude 自身は原文を読みます。誤訳がある場合は原文をご確認ください。
型安全性とバリデーション
概要
エンドツーエンドの型安全性は、バグが実行時ではなくコンパイル時に捕捉されることを保証します。このスキルでは、実行時バリデーションのための Zod、型安全な API のための tRPC、型安全なデータベースアクセスのための Prisma、および最新の TypeScript の機能について説明します。
このスキルを使用する場面:
- 型安全な API (REST, RPC, GraphQL) の構築
- ユーザー入力および外部データのバリデーション
- データベースクエリの型安全性の保証
- エンドツーエンドで型付けされたフルスタックアプリケーションの作成
- JavaScript から TypeScript への移行
- 厳密なバリデーションルールの実装
コアスタック
1. Zod - 実行時バリデーション
import { z } from 'zod'
// スキーマの定義
const UserSchema = z.object({
id: z.string().uuid(),
email: z.string().email(),
age: z.number().int().positive().max(120),
role: z.enum(['admin', 'user', 'guest']),
metadata: z.record(z.string()).optional(),
createdAt: z.date().default(() => new Date())
})
// スキーマから TypeScript の型を推論
type User = z.infer<typeof UserSchema>
// データのバリデーション
const result = UserSchema.safeParse(data)
if (result.success) {
const user: User = result.data
} else {
console.error(result.error.issues)
}
// データの変換
const EmailSchema = z.string().email().transform(email => email.toLowerCase())
高度なパターン:
// Refinements
const PasswordSchema = z.string()
.min(8)
.refine((pass) => /[A-Z]/.test(pass), 'Must contain uppercase')
.refine((pass) => /[0-9]/.test(pass), 'Must contain number')
// Discriminated Unions
const EventSchema = z.discriminatedUnion('type', [
z.object({ type: z.literal('click'), x: z.number(), y: z.number() }),
z.object({ type: z.literal('scroll'), offset: z.number() })
])
// Recursive Types
const CategorySchema: z.ZodType<Category> = z.lazy(() =>
z.object({
name: z.string(),
children: z.array(CategorySchema).optional()
})
)
2. tRPC - 型安全な API
// Server: プロシージャの定義
import { initTRPC } from '@trpc/server'
import { z } from 'zod'
const t = initTRPC.create()
export const appRouter = t.router({
getUser: t.procedure
.input(z.object({ id: z.string() }))
.query(async ({ input }) => {
return await db.user.findUnique({ where: { id: input.id } })
}),
createUser: t.procedure
.input(z.object({
email: z.string().email(),
name: z.string()
}))
.mutation(async ({ input }) => {
return await db.user.create({ data: input })
})
})
export type AppRouter = typeof appRouter
// Client: 完全に型付けされている!
import { createTRPCProxyClient, httpBatchLink } from '@trpc/client'
import type { AppRouter } from './server'
const client = createTRPCProxyClient<AppRouter>({
links: [httpBatchLink({ url: 'http://localhost:3000/api/trpc' })]
})
// TypeScript は正確な形状を知っている!
const user = await client.getUser.query({ id: '123' })
// ^? User | null
3. Prisma - 型安全な ORM
// schema.prisma
model User {
id String @id @default(cuid())
email String @unique
posts Post[]
profile Profile?
createdAt DateTime @default(now())
}
model Post {
id String @id @default(cuid())
title String
content String?
published Boolean @default(false)
author User @relation(fields: [authorId], references: [id])
authorId String
}
import { PrismaClient } from '@prisma/client'
const prisma = new PrismaClient()
// 完全に型付けされたクエリ
const user = await prisma.user.findUnique({
where: { id: '123' },
include: {
posts: {
where: { published: true },
orderBy: { createdAt: 'desc' }
}
}
})
// user は User & { posts: Post[] } として型付けされる
// 型安全な作成
const newUser = await prisma.user.create({
data: {
email: 'user@example.com',
posts: {
create: [
{ title: 'First Post', content: 'Hello world' }
]
}
}
})
4. TypeScript 5.7+ の機能
// Const type parameters (TS 5.0+)
function firstElement<T extends readonly any[]>(arr: T) {
return arr[0]
}
const result = firstElement(['a', 'b'] as const)
// result は 'a' として型付けされる
// Satisfies operator (TS 4.9+)
const config = {
url: 'https://api.example.com',
timeout: 5000
} satisfies Config // config が Config に一致することを保証するが、リテラル型を保持する
// Decorators (TS 5.0+)
function logged(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
const original = descriptor.value
descriptor.value = function (...args: any[]) {
console.log(`Calling ${propertyKey}`)
return original.apply(this, args)
}
}
class API {
@logged
async fetchData() {}
}
フルスタックの例
// ===== BACKEND (Next.js API) =====
// app/api/trpc/[trpc]/route.ts
import { fetchRequestHandler } from '@trpc/server/adapters/fetch'
import { appRouter } from '@/server/routers/_app'
export async function GET(req: Request) {
return fetchRequestHandler({
endpoint: '/api/trpc',
req,
router: appRouter,
createContext: () => ({})
})
}
export const POST = GET
// server/routers/_app.ts
import { z } from 'zod'
import { prisma } from '@/lib/prisma'
import { publicProcedure, router } from '../trpc'
export const appRouter = router({
posts: {
list: publicProcedure
.input(z.object({
limit: z.number().min(1).max(100).default(10),
cursor: z.string().optional()
}))
.query(async ({ input }) => {
const posts = await prisma.post.findMany({
take: input.limit + 1,
cursor: input.cursor ? { id: input.cursor } : undefined,
orderBy: { createdAt: 'desc' },
include: { author: true }
})
return {
items: posts.slice(0, input.limit),
nextCursor: posts[input.limit]?.id
}
}),
create: publicProcedure
.input(z.object({
title: z.string().min(1).max(200),
content: z.string().optional()
}))
.mutation(async ({ input }) => {
return awa 📜 原文 SKILL.md(Claudeが読む英語/中国語)を展開
Type Safety & Validation
Overview
End-to-end type safety ensures bugs are caught at compile time, not runtime. This skill covers Zod for runtime validation, tRPC for type-safe APIs, Prisma for type-safe database access, and modern TypeScript features.
When to use this skill:
- Building type-safe APIs (REST, RPC, GraphQL)
- Validating user input and external data
- Ensuring database queries are type-safe
- Creating end-to-end typed full-stack applications
- Migrating from JavaScript to TypeScript
- Implementing strict validation rules
Core Stack
1. Zod - Runtime Validation
import { z } from 'zod'
// Define schema
const UserSchema = z.object({
id: z.string().uuid(),
email: z.string().email(),
age: z.number().int().positive().max(120),
role: z.enum(['admin', 'user', 'guest']),
metadata: z.record(z.string()).optional(),
createdAt: z.date().default(() => new Date())
})
// Infer TypeScript type from schema
type User = z.infer<typeof UserSchema>
// Validate data
const result = UserSchema.safeParse(data)
if (result.success) {
const user: User = result.data
} else {
console.error(result.error.issues)
}
// Transform data
const EmailSchema = z.string().email().transform(email => email.toLowerCase())
Advanced Patterns:
// Refinements
const PasswordSchema = z.string()
.min(8)
.refine((pass) => /[A-Z]/.test(pass), 'Must contain uppercase')
.refine((pass) => /[0-9]/.test(pass), 'Must contain number')
// Discriminated Unions
const EventSchema = z.discriminatedUnion('type', [
z.object({ type: z.literal('click'), x: z.number(), y: z.number() }),
z.object({ type: z.literal('scroll'), offset: z.number() })
])
// Recursive Types
const CategorySchema: z.ZodType<Category> = z.lazy(() =>
z.object({
name: z.string(),
children: z.array(CategorySchema).optional()
})
)
2. tRPC - Type-Safe APIs
// Server: Define procedures
import { initTRPC } from '@trpc/server'
import { z } from 'zod'
const t = initTRPC.create()
export const appRouter = t.router({
getUser: t.procedure
.input(z.object({ id: z.string() }))
.query(async ({ input }) => {
return await db.user.findUnique({ where: { id: input.id } })
}),
createUser: t.procedure
.input(z.object({
email: z.string().email(),
name: z.string()
}))
.mutation(async ({ input }) => {
return await db.user.create({ data: input })
})
})
export type AppRouter = typeof appRouter
// Client: Fully typed!
import { createTRPCProxyClient, httpBatchLink } from '@trpc/client'
import type { AppRouter } from './server'
const client = createTRPCProxyClient<AppRouter>({
links: [httpBatchLink({ url: 'http://localhost:3000/api/trpc' })]
})
// TypeScript knows the exact shape!
const user = await client.getUser.query({ id: '123' })
// ^? User | null
3. Prisma - Type-Safe ORM
// schema.prisma
model User {
id String @id @default(cuid())
email String @unique
posts Post[]
profile Profile?
createdAt DateTime @default(now())
}
model Post {
id String @id @default(cuid())
title String
content String?
published Boolean @default(false)
author User @relation(fields: [authorId], references: [id])
authorId String
}
import { PrismaClient } from '@prisma/client'
const prisma = new PrismaClient()
// Fully typed queries
const user = await prisma.user.findUnique({
where: { id: '123' },
include: {
posts: {
where: { published: true },
orderBy: { createdAt: 'desc' }
}
}
})
// user is typed as: User & { posts: Post[] }
// Type-safe creates
const newUser = await prisma.user.create({
data: {
email: 'user@example.com',
posts: {
create: [
{ title: 'First Post', content: 'Hello world' }
]
}
}
})
4. TypeScript 5.7+ Features
// Const type parameters (TS 5.0+)
function firstElement<T extends readonly any[]>(arr: T) {
return arr[0]
}
const result = firstElement(['a', 'b'] as const)
// result is typed as 'a'
// Satisfies operator (TS 4.9+)
const config = {
url: 'https://api.example.com',
timeout: 5000
} satisfies Config // Ensures config matches Config, but keeps literal types
// Decorators (TS 5.0+)
function logged(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
const original = descriptor.value
descriptor.value = function (...args: any[]) {
console.log(`Calling ${propertyKey}`)
return original.apply(this, args)
}
}
class API {
@logged
async fetchData() {}
}
Full-Stack Example
// ===== BACKEND (Next.js API) =====
// app/api/trpc/[trpc]/route.ts
import { fetchRequestHandler } from '@trpc/server/adapters/fetch'
import { appRouter } from '@/server/routers/_app'
export async function GET(req: Request) {
return fetchRequestHandler({
endpoint: '/api/trpc',
req,
router: appRouter,
createContext: () => ({})
})
}
export const POST = GET
// server/routers/_app.ts
import { z } from 'zod'
import { prisma } from '@/lib/prisma'
import { publicProcedure, router } from '../trpc'
export const appRouter = router({
posts: {
list: publicProcedure
.input(z.object({
limit: z.number().min(1).max(100).default(10),
cursor: z.string().optional()
}))
.query(async ({ input }) => {
const posts = await prisma.post.findMany({
take: input.limit + 1,
cursor: input.cursor ? { id: input.cursor } : undefined,
orderBy: { createdAt: 'desc' },
include: { author: true }
})
return {
items: posts.slice(0, input.limit),
nextCursor: posts[input.limit]?.id
}
}),
create: publicProcedure
.input(z.object({
title: z.string().min(1).max(200),
content: z.string().optional()
}))
.mutation(async ({ input }) => {
return await prisma.post.create({
data: input
})
})
}
})
// ===== FRONTEND (React) =====
// lib/trpc.ts
import { createTRPCReact } from '@trpc/react-query'
import type { AppRouter } from '@/server/routers/_app'
export const trpc = createTRPCReact<AppRouter>()
// components/PostList.tsx
'use client'
import { trpc } from '@/lib/trpc'
export function PostList() {
const { data, isLoading } = trpc.posts.list.useQuery({ limit: 10 })
const createPost = trpc.posts.create.useMutation()
if (isLoading) return <div>Loading...</div>
return (
<div>
{data?.items.map(post => (
<div key={post.id}>
<h2>{post.title}</h2>
<p>{post.content}</p>
<span>By {post.author.name}</span>
</div>
))}
<button onClick={() => createPost.mutate({ title: 'New Post' })}>
Create Post
</button>
</div>
)
}
Best Practices
Validation
- ✅ Validate at boundaries (API inputs, form submissions, external data)
- ✅ Use
.safeParse()to handle errors gracefully - ✅ Provide clear error messages for users
- ✅ Validate environment variables at startup
- ✅ Use branded types for IDs (
z.string().brand<'UserId'>())
Type Safety
- ✅ Enable
strict: trueintsconfig.json - ✅ Use
noUncheckedIndexedAccessfor safer array access - ✅ Prefer
unknownoverany - ✅ Use type guards for narrowing
- ✅ Leverage inference with
typeofandReturnType
Performance
- ✅ Reuse schemas (don't create inline)
- ✅ Use
.parse()for known-good data (faster than.safeParse()) - ✅ Enable Prisma query optimization
- ✅ Use tRPC batching for multiple queries
- ✅ Cache validation results when appropriate