- Claude Codeは型定義の自動生成・推論・リファクタリングを高精度で実行できる
- APIレスポンス型・Zod スキーマ・ジェネリクスの実装を自然言語で指示するだけで完成
- TypeScript案件はSES市場で需要急増中、月単価75〜95万円が目安
「JavaScriptプロジェクトをTypeScriptに移行したいが、型定義が膨大すぎて手が出ない」「APIのレスポンス型を手動で書くのが面倒」「ジェネリクスの使い方がわからず、any地獄に陥っている」——SES現場でTypeScript開発に携わるエンジニアなら、一度はこうした悩みを抱えたことがあるでしょう。
2026年現在、TypeScriptはフロントエンド・バックエンド問わずSES案件の必須スキルとなっています。React/Next.js案件の90%以上がTypeScriptを採用し、Node.jsバックエンドでもTypeScript化が標準になりました。しかし、型システムの複雑さゆえに開発効率が落ちるケースも少なくありません。
本記事では、Claude Codeを活用してTypeScript開発を劇的に効率化する方法を、型定義の自動生成からリファクタリング、型安全なAPI実装まで体系的に解説します。
TypeScript開発でClaude Codeが威力を発揮する理由
TypeScript開発には「型定義の作成コスト」「型エラーの解消」「ジェネリクスの設計」という3つのボトルネックがあります。Claude Codeは、これらすべてを自然言語の指示で解決できます。
従来のTypeScript開発の課題
| 課題 | 具体的な問題 | 工数への影響 |
|---|---|---|
| 型定義の手動作成 | APIレスポンス型を一つずつ定義 | 1エンドポイントあたり30〜60分 |
| 型エラーの解消 | 複雑な型推論エラーの原因特定 | デバッグに数時間かかることも |
| ジェネリクス設計 | 再利用可能な型の設計に高い専門知識が必要 | 設計に半日〜1日 |
| JS→TS移行 | 既存コードへの段階的な型付け | ファイルあたり1〜2時間 |
Claude Codeによる解決
# APIレスポンスから型を自動生成
claude "このJSONレスポンスからTypeScript型定義を生成して。
ネストされたオブジェクトも再帰的に型定義してほしい"
# 型エラーを一括修正
claude "tsc --noEmitで出ているエラーを全て修正して。
any型はできるだけ具体的な型に置き換えて"
# ジェネリクスを使った汎用コンポーネント作成
claude "このフォームコンポーネントをジェネリクスで
汎用化して。任意のフォームスキーマに対応できるようにして"
Claude CodeはTypeScriptの型システムを深く理解しており、Conditional Types、Mapped Types、Template Literal Typesなどの高度な型機能も適切に活用した提案をしてくれます。
実践①:型定義の自動生成
APIレスポンス型の自動生成
SES案件で最も頻繁に行う作業の一つが、APIレスポンスの型定義です。Claude Codeを使えば、JSONサンプルから正確な型定義を瞬時に生成できます。
claude "以下のAPIレスポンスJSONからTypeScript型を生成して。
- null許容のフィールドはoptionalにする
- 日付文字列はstring型でJSDocコメントを付ける
- ネストされたオブジェクトは別の型として分離する
{
\"id\": 1,
\"name\": \"田中太郎\",
\"email\": \"[email protected]\",
\"profile\": {
\"bio\": \"エンジニアです\",
\"avatar_url\": null,
\"social_links\": [
{\"platform\": \"twitter\", \"url\": \"https://twitter.com/tanaka\"}
]
},
\"created_at\": \"2026-01-15T09:00:00Z\",
\"updated_at\": \"2026-03-10T14:30:00Z\"
}"
Claude Codeが生成する型定義:
/** ソーシャルリンク情報 */
interface SocialLink {
/** プラットフォーム名 */
platform: string;
/** プロフィールURL */
url: string;
}
/** ユーザープロフィール */
interface UserProfile {
/** 自己紹介文 */
bio: string;
/** アバター画像URL(未設定の場合null) */
avatar_url?: string | null;
/** ソーシャルリンク一覧 */
social_links: SocialLink[];
}
/** ユーザー情報 */
interface User {
/** ユーザーID */
id: number;
/** 氏名 */
name: string;
/** メールアドレス */
email: string;
/** プロフィール情報 */
profile: UserProfile;
/** 作成日時(ISO 8601形式) */
created_at: string;
/** 更新日時(ISO 8601形式) */
updated_at: string;
}
Zodスキーマとの連携
2026年のTypeScript開発では、ランタイムバリデーションライブラリZodとの組み合わせが主流です。Claude Codeは型定義とZodスキーマを同時に生成できます。
claude "上のUser型に対応するZodスキーマも生成して。
z.inferでUser型が導出できるようにして。
バリデーションエラーメッセージは日本語で"
import { z } from 'zod';
const socialLinkSchema = z.object({
platform: z.string().min(1, 'プラットフォーム名は必須です'),
url: z.string().url('有効なURLを入力してください'),
});
const userProfileSchema = z.object({
bio: z.string().max(500, '自己紹介は500文字以内で入力してください'),
avatar_url: z.string().url().nullable().optional(),
social_links: z.array(socialLinkSchema),
});
const userSchema = z.object({
id: z.number().int().positive(),
name: z.string().min(1, '氏名は必須です'),
email: z.string().email('有効なメールアドレスを入力してください'),
profile: userProfileSchema,
created_at: z.string().datetime(),
updated_at: z.string().datetime(),
});
// 型をスキーマから導出
type User = z.infer<typeof userSchema>;
type UserProfile = z.infer<typeof userProfileSchema>;
type SocialLink = z.infer<typeof socialLinkSchema>;
export { userSchema, userProfileSchema, socialLinkSchema };
export type { User, UserProfile, SocialLink };
実践②:JavaScript→TypeScript移行
段階的な移行戦略
SES案件では既存のJavaScriptプロジェクトをTypeScriptに移行する作業が頻繁に発生します。Claude Codeを使えば、ファイル単位で安全に移行できます。
# プロジェクト全体の移行計画を立てる
claude "このプロジェクトをTypeScriptに段階的に移行したい。
- まずtsconfig.jsonを設定して
- allowJsをtrueにして共存できるようにして
- 依存関係の少ないユーティリティから移行を開始して
- 移行順序のロードマップを作成して"
tsconfig.jsonの最適設定
{
"compilerOptions": {
"target": "ES2022",
"module": "ESNext",
"moduleResolution": "bundler",
"strict": true,
"noUncheckedIndexedAccess": true,
"exactOptionalPropertyTypes": true,
"allowJs": true,
"checkJs": true,
"declaration": true,
"declarationMap": true,
"sourceMap": true,
"outDir": "./dist",
"rootDir": "./src",
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"isolatedModules": true,
"verbatimModuleSyntax": true
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist"]
}
具体的な移行プロンプト
# ユーティリティファイルの移行
claude "src/utils/formatDate.jsをTypeScriptに変換して。
- 引数と戻り値に適切な型を付ける
- any型は使わない
- オーバーロードが必要なら定義する
- JSDocコメントは保持する"
移行前(JavaScript):
// src/utils/formatDate.js
/**
* 日付をフォーマットする
* @param {Date|string|number} date - フォーマットする日付
* @param {string} format - フォーマット文字列
* @returns {string} フォーマットされた日付文字列
*/
export function formatDate(date, format = 'YYYY-MM-DD') {
const d = date instanceof Date ? date : new Date(date);
if (isNaN(d.getTime())) {
throw new Error('Invalid date');
}
const map = {
YYYY: d.getFullYear(),
MM: String(d.getMonth() + 1).padStart(2, '0'),
DD: String(d.getDate()).padStart(2, '0'),
HH: String(d.getHours()).padStart(2, '0'),
mm: String(d.getMinutes()).padStart(2, '0'),
ss: String(d.getSeconds()).padStart(2, '0'),
};
return format.replace(/YYYY|MM|DD|HH|mm|ss/g, (match) => String(map[match]));
}
移行後(TypeScript):
// src/utils/formatDate.ts
/** サポートするフォーマットトークン */
type FormatToken = 'YYYY' | 'MM' | 'DD' | 'HH' | 'mm' | 'ss';
/** 日付として受け入れ可能な型 */
type DateInput = Date | string | number;
/** デフォルトのフォーマット */
const DEFAULT_FORMAT = 'YYYY-MM-DD' as const;
/**
* 日付をフォーマットする
* @param date - フォーマットする日付(Date, ISO文字列, UNIXタイムスタンプ)
* @param format - フォーマット文字列(デフォルト: 'YYYY-MM-DD')
* @returns フォーマットされた日付文字列
* @throws {Error} 無効な日付が渡された場合
*
* @example
* formatDate(new Date('2026-03-14'), 'YYYY/MM/DD') // '2026/03/14'
* formatDate('2026-03-14T09:30:00Z', 'HH:mm') // '09:30'
*/
export function formatDate(
date: DateInput,
format: string = DEFAULT_FORMAT
): string {
const d = date instanceof Date ? date : new Date(date);
if (Number.isNaN(d.getTime())) {
throw new Error(`Invalid date: ${String(date)}`);
}
const tokenMap: Record<FormatToken, string> = {
YYYY: String(d.getFullYear()),
MM: String(d.getMonth() + 1).padStart(2, '0'),
DD: String(d.getDate()).padStart(2, '0'),
HH: String(d.getHours()).padStart(2, '0'),
mm: String(d.getMinutes()).padStart(2, '0'),
ss: String(d.getSeconds()).padStart(2, '0'),
};
const tokenRegex = /YYYY|MM|DD|HH|mm|ss/g;
return format.replace(tokenRegex, (match) => tokenMap[match as FormatToken]);
}
実践③:型安全なAPI実装
tRPCライクな型安全ルーター
Claude Codeは、エンドツーエンドの型安全性を実現するAPI設計を支援します。
claude "tRPCを使って型安全なAPIルーターを実装して。
- ユーザーCRUD操作
- 入力バリデーションはZod
- エラーハンドリングも型安全に
- Prismaとの連携も含めて"
// server/routers/user.ts
import { z } from 'zod';
import { router, publicProcedure, protectedProcedure } from '../trpc';
import { TRPCError } from '@trpc/server';
const createUserInput = z.object({
name: z.string().min(1).max(100),
email: z.string().email(),
role: z.enum(['admin', 'member', 'viewer']).default('member'),
});
const updateUserInput = z.object({
id: z.string().uuid(),
name: z.string().min(1).max(100).optional(),
email: z.string().email().optional(),
role: z.enum(['admin', 'member', 'viewer']).optional(),
});
export const userRouter = router({
// 一覧取得
list: publicProcedure
.input(
z.object({
page: z.number().int().positive().default(1),
limit: z.number().int().min(1).max(100).default(20),
search: z.string().optional(),
})
)
.query(async ({ input, ctx }) => {
const { page, limit, search } = input;
const where = search
? {
OR: [
{ name: { contains: search, mode: 'insensitive' as const } },
{ email: { contains: search, mode: 'insensitive' as const } },
],
}
: {};
const [users, total] = await Promise.all([
ctx.prisma.user.findMany({
where,
skip: (page - 1) * limit,
take: limit,
orderBy: { createdAt: 'desc' },
}),
ctx.prisma.user.count({ where }),
]);
return {
users,
pagination: {
page,
limit,
total,
totalPages: Math.ceil(total / limit),
},
};
}),
// 個別取得
getById: publicProcedure
.input(z.object({ id: z.string().uuid() }))
.query(async ({ input, ctx }) => {
const user = await ctx.prisma.user.findUnique({
where: { id: input.id },
include: { profile: true },
});
if (!user) {
throw new TRPCError({
code: 'NOT_FOUND',
message: `ユーザーが見つかりません: ${input.id}`,
});
}
return user;
}),
// 作成
create: protectedProcedure
.input(createUserInput)
.mutation(async ({ input, ctx }) => {
const existing = await ctx.prisma.user.findUnique({
where: { email: input.email },
});
if (existing) {
throw new TRPCError({
code: 'CONFLICT',
message: 'このメールアドレスは既に登録されています',
});
}
return ctx.prisma.user.create({ data: input });
}),
// 更新
update: protectedProcedure
.input(updateUserInput)
.mutation(async ({ input, ctx }) => {
const { id, ...data } = input;
return ctx.prisma.user.update({
where: { id },
data,
});
}),
// 削除
delete: protectedProcedure
.input(z.object({ id: z.string().uuid() }))
.mutation(async ({ input, ctx }) => {
await ctx.prisma.user.delete({ where: { id: input.id } });
return { success: true };
}),
});
フロントエンドでの型安全な呼び出し
// components/UserList.tsx
import { trpc } from '../utils/trpc';
export function UserList() {
// 戻り値の型が自動的に推論される
const { data, isLoading, error } = trpc.user.list.useQuery({
page: 1,
limit: 20,
});
// data.users の型が User[] として正確に推論
// data.pagination.totalPages も number として推論
const createMutation = trpc.user.create.useMutation({
onSuccess: () => {
// キャッシュの無効化
utils.user.list.invalidate();
},
onError: (error) => {
// error.message の型が string として推論
console.error('作成エラー:', error.message);
},
});
const handleCreate = async () => {
// 型安全:name, email, role以外のプロパティは渡せない
await createMutation.mutateAsync({
name: '新しいユーザー',
email: '[email protected]',
role: 'member', // 'admin' | 'member' | 'viewer' のみ許可
});
};
if (isLoading) return <div>読み込み中...</div>;
if (error) return <div>エラー: {error.message}</div>;
return (
<div>
{data?.users.map((user) => (
<div key={user.id}>
<h3>{user.name}</h3>
<p>{user.email}</p>
</div>
))}
</div>
);
}
実践④:高度な型テクニック
Conditional Typesの活用
Claude Codeは複雑なConditional Typesも的確に生成します。
claude "APIレスポンスの型を、成功時とエラー時で
分岐する型を作って。ディスクリミネーテッドユニオンで。
成功時はdataフィールド、エラー時はerrorフィールドを持つ"
// 型安全なAPIレスポンス
type ApiResponse<T> =
| { success: true; data: T; error?: never }
| { success: false; data?: never; error: ApiError };
interface ApiError {
code: string;
message: string;
details?: Record<string, string[]>;
}
// 使用例
async function fetchUser(id: string): Promise<ApiResponse<User>> {
try {
const response = await fetch(`/api/users/${id}`);
if (!response.ok) {
return {
success: false,
error: {
code: 'FETCH_ERROR',
message: `HTTP ${response.status}: ${response.statusText}`,
},
};
}
const data = await response.json();
return { success: true, data };
} catch (e) {
return {
success: false,
error: {
code: 'NETWORK_ERROR',
message: e instanceof Error ? e.message : '不明なエラー',
},
};
}
}
// 型ガードが自動的に効く
const result = await fetchUser('123');
if (result.success) {
// result.data は User 型として推論
console.log(result.data.name);
} else {
// result.error は ApiError 型として推論
console.error(result.error.message);
}
Mapped Typesでフォーム状態管理
claude "フォームの状態管理を型安全にしたい。
各フィールドのvalue, error, touched状態を
元の型から自動生成する Mapped Type を作って"
// フォームフィールドの状態
interface FieldState<T> {
value: T;
error: string | null;
touched: boolean;
dirty: boolean;
}
// 型から自動的にフォーム状態を生成
type FormState<T extends Record<string, unknown>> = {
[K in keyof T]: FieldState<T[K]>;
};
// フォームのアクション
type FormAction<T extends Record<string, unknown>> =
| { type: 'SET_VALUE'; field: keyof T; value: T[keyof T] }
| { type: 'SET_ERROR'; field: keyof T; error: string | null }
| { type: 'SET_TOUCHED'; field: keyof T }
| { type: 'RESET' }
| { type: 'VALIDATE' };
// 使用例
interface LoginForm {
email: string;
password: string;
rememberMe: boolean;
}
// FormState<LoginForm> は以下と同等:
// {
// email: FieldState<string>;
// password: FieldState<string>;
// rememberMe: FieldState<boolean>;
// }
function useTypedForm<T extends Record<string, unknown>>(
initialValues: T,
validate: (values: T) => Partial<Record<keyof T, string>>
) {
const initialState: FormState<T> = Object.fromEntries(
Object.entries(initialValues).map(([key, value]) => [
key,
{ value, error: null, touched: false, dirty: false },
])
) as FormState<T>;
// ... reducer implementation
return { state: initialState };
}
実践⑤:TypeScript×Claude Codeのワークフロー最適化
CLAUDE.mdでのTypeScript設定
プロジェクトルートのCLAUDE.mdにTypeScript固有のルールを記載することで、Claude Codeの出力品質を大幅に向上させます。
# TypeScriptルール
## 型定義
- any型の使用禁止(unknown + 型ガードを使う)
- as アサーションよりも型ガード関数を優先
- 戻り値の型は明示的に書く(型推論に頼りすぎない)
- interfaceよりtypeを優先(拡張が必要な場合のみinterface)
## コーディング規約
- strict: trueを前提とする
- noUncheckedIndexedAccess: trueを前提とする
- enumは使わない(const objectsまたはunion typeを使う)
- namespace は使わない(ES modules を使う)
## インポート
- type-only importを使う: import type { Foo } from './foo'
- バレルファイル(index.ts)は使わない(ビルド性能に影響)
## エラーハンドリング
- catchしたエラーはunknownとして扱う
- カスタムエラークラスを使って型安全にする
型チェックの自動実行
# tscエラーを検出して一括修正
claude "tsc --noEmitを実行して、出ているエラーを全て修正して。
修正方針:
1. any → 具体的な型に変更
2. 型の不一致 → 正しい型に修正
3. undefinedの可能性 → オプショナルチェーンまたは型ガード追加
4. 未使用変数 → アンダースコアプレフィックスを付けるか削除"
SES現場でのTypeScriptスキルの市場価値
2026年のTypeScript案件動向

TypeScriptのスキルレベルによって、SES案件の単価は大きく変わります。
| スキルレベル | できること | 月単価目安 |
|---|---|---|
| 基礎 | 型アノテーション、基本的な型定義 | 60〜70万円 |
| 中級 | ジェネリクス、Utility Types活用 | 70〜85万円 |
| 上級 | Conditional Types、型レベルプログラミング | 85〜95万円 |
| エキスパート | 型安全なフレームワーク設計、DX改善 | 95万円以上 |
需要の高いTypeScript関連スキル
- React + TypeScript: コンポーネントの型安全な設計
- Next.js App Router + TypeScript: サーバーコンポーネント型
- tRPC / GraphQL Code Generator: エンドツーエンド型安全
- Prisma: 型安全なデータベースアクセス
- Zod / Valibot: ランタイムバリデーション
実践⑥:テスト自動生成
型を活用したテスト
claude "このuserRouter.tsに対するユニットテストを生成して。
- vitestを使う
- 各プロシージャの正常系・異常系をカバー
- モック型もPrismaの型から自動生成
- テストヘルパーは型安全にする"
// __tests__/user.test.ts
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { createCaller } from '../server/routers/_app';
import type { PrismaClient } from '@prisma/client';
import { TRPCError } from '@trpc/server';
// 型安全なモック
const mockPrisma = {
user: {
findMany: vi.fn(),
findUnique: vi.fn(),
create: vi.fn(),
update: vi.fn(),
delete: vi.fn(),
count: vi.fn(),
},
} satisfies { user: Partial<PrismaClient['user']> };
const caller = createCaller({
prisma: mockPrisma as unknown as PrismaClient,
session: { userId: 'test-user-id' },
});
describe('userRouter', () => {
beforeEach(() => {
vi.clearAllMocks();
});
describe('list', () => {
it('ページネーション付きでユーザー一覧を返す', async () => {
const mockUsers = [
{ id: '1', name: 'テスト太郎', email: '[email protected]' },
];
mockPrisma.user.findMany.mockResolvedValue(mockUsers);
mockPrisma.user.count.mockResolvedValue(1);
const result = await caller.user.list({ page: 1, limit: 20 });
expect(result.users).toEqual(mockUsers);
expect(result.pagination.total).toBe(1);
expect(result.pagination.totalPages).toBe(1);
});
it('検索クエリでフィルタリングする', async () => {
mockPrisma.user.findMany.mockResolvedValue([]);
mockPrisma.user.count.mockResolvedValue(0);
await caller.user.list({ page: 1, limit: 20, search: 'テスト' });
expect(mockPrisma.user.findMany).toHaveBeenCalledWith(
expect.objectContaining({
where: expect.objectContaining({
OR: expect.arrayContaining([
expect.objectContaining({
name: expect.objectContaining({ contains: 'テスト' }),
}),
]),
}),
})
);
});
});
describe('create', () => {
it('新しいユーザーを作成する', async () => {
const input = {
name: '新規ユーザー',
email: '[email protected]',
role: 'member' as const,
};
mockPrisma.user.findUnique.mockResolvedValue(null);
mockPrisma.user.create.mockResolvedValue({ id: '2', ...input });
const result = await caller.user.create(input);
expect(result.name).toBe(input.name);
expect(mockPrisma.user.create).toHaveBeenCalledWith({
data: input,
});
});
it('重複メールアドレスでエラーを返す', async () => {
mockPrisma.user.findUnique.mockResolvedValue({
id: '1',
email: '[email protected]',
});
await expect(
caller.user.create({
name: 'テスト',
email: '[email protected]',
})
).rejects.toThrow(TRPCError);
});
});
});
まとめ:Claude Code × TypeScriptで開発速度を3倍に
Claude Codeを活用したTypeScript開発のポイントをおさらいしましょう。
| 活用シーン | 効果 | 時間短縮 |
|---|---|---|
| 型定義の自動生成 | APIレスポンス型を即座に生成 | 最大80%短縮 |
| JS→TS移行 | ファイル単位で安全に型付け | 最大70%短縮 |
| 型安全なAPI実装 | tRPC/Zodの定型パターン生成 | 最大60%短縮 |
| 高度な型テクニック | Conditional/Mapped Types生成 | 設計時間を大幅短縮 |
| テスト自動生成 | 型情報を活用した網羅的テスト | 最大75%短縮 |
TypeScriptスキル × AI活用は、2026年のSES市場で最も評価される組み合わせの一つです。Claude Codeを日々の開発に取り入れることで、型安全なコードを効率的に書けるようになり、より高単価な案件にチャレンジできるようになります。
📚 Claude Code 完全攻略シリーズの他の記事もチェック!