created: 2025-09-03 12:00:00+09:00
TypeScriptとHono型システム
HonoはTypeScriptファーストのフレームワークとして設計されており、強力な型システムを提供しています。これにより、開発時にエラーを早期発見でき、IDEでの補完機能も充実します。この章では、HonoとTypeScriptの組み合わせを最大限活用する方法について学んでいきましょう。
Honoの型安全性の基礎
Context型の活用
import { Hono, Context } from 'hono'
const app = new Hono()
// Context型を明示的に指定
app.get('/users/:id', (c: Context) => {
  const id = c.req.param('id') // string型として推論される
  const page = c.req.query('page') // string | undefined型として推論される
  
  return c.json({
    userId: id,
    page: page ? parseInt(page) : 1
  })
})
「Context型を指定することで、c.req.param()やc.req.query()の戻り値が適切に型推論されます。」
ジェネリック型を使った型安全なAPI
interface User {
  id: string
  name: string
  email: string
}
interface CreateUserRequest {
  name: string
  email: string
}
app.post('/users', async (c) => {
  // 型安全なJSONパース
  const body = await c.req.json<CreateUserRequest>()
  
  const user: User = {
    id: crypto.randomUUID(),
    name: body.name, // TypeScriptが型をチェック
    email: body.email
  }
  
  // 型安全なJSONレスポンス
  return c.json<User>(user, 201)
})
Honoの高度な型機能
型付きルートパラメータ
// ルートパラメータの型を定義
type UserParams = {
  id: string
}
type PostParams = {
  userId: string
  postId: string
}
app.get('/users/:id', (c) => {
  const { id } = c.req.param() // 自動的に型推論される
  return c.json({ userId: id })
})
app.get('/users/:userId/posts/:postId', (c) => {
  const { userId, postId } = c.req.param()
  return c.json({ userId, postId })
})
型付きクエリパラメータ
interface SearchQuery {
  q?: string
  page?: string
  limit?: string
  sort?: 'name' | 'date'
}
app.get('/search', (c) => {
  const query = c.req.query() // Record<string, string>型
  
  // より型安全な方法
  const q = c.req.query('q')
  const page = parseInt(c.req.query('page') || '1')
  const limit = parseInt(c.req.query('limit') || '10')
  const sort = c.req.query('sort') as 'name' | 'date' | undefined
  
  return c.json({ q, page, limit, sort })
})
Zodとの連携による高度なバリデーション
import { z } from 'zod'
import { zValidator } from '@hono/zod-validator'
// スキーマ定義
const CreateUserSchema = z.object({
  name: z.string().min(1).max(100),
  email: z.string().email(),
  age: z.number().min(0).max(150),
  tags: z.array(z.string()).optional()
})
const UpdateUserSchema = CreateUserSchema.partial()
type CreateUserRequest = z.infer<typeof CreateUserSchema>
type UpdateUserRequest = z.infer<typeof UpdateUserSchema>
// バリデーションミドルウェアの使用
app.post('/users', zValidator('json', CreateUserSchema), async (c) => {
  const user = c.req.valid('json') // CreateUserRequest型で型推論される
  
  // userの各プロパティが型安全にアクセス可能
  console.log(user.name, user.email, user.age)
  
  return c.json({ message: 'User created', user })
})
「zValidatorを使うことで、バリデーションと型推論が同時に行われます。非常に便利ですね。」
カスタム型定義
レスポンス型の統一
interface ApiResponse<T> {
  data: T
  message?: string
  timestamp: string
}
interface ErrorResponse {
  error: string
  message?: string
  details?: any[]
  timestamp: string
  path: string
}
interface PaginatedResponse<T> {
  data: T[]
  pagination: {
    page: number
    limit: number
    total: number
    totalPages: number
  }
  timestamp: string
}
// 型安全なレスポンスヘルパー
const createApiResponse = <T>(data: T, message?: string): ApiResponse<T> => ({
  data,
  message,
  timestamp: new Date().toISOString()
})
const createErrorResponse = (
  error: string,
  message?: string,
  details?: any[],
  path?: string
): ErrorResponse => ({
  error,
  message,
  details,
  timestamp: new Date().toISOString(),
  path: path || ''
})
app.get('/users/:id', async (c) => {
  const id = c.req.param('id')
  
  try {
    const user = await getUserById(id)
    
    if (!user) {
      const errorResponse = createErrorResponse(
        'USER_NOT_FOUND',
        `User with ID ${id} not found`,
        undefined,
        c.req.url
      )
      return c.json(errorResponse, 404)
    }
    
    const response = createApiResponse(user, 'User retrieved successfully')
    return c.json(response)
    
  } catch (error) {
    const errorResponse = createErrorResponse(
      'INTERNAL_ERROR',
      'An unexpected error occurred',
      [error],
      c.req.url
    )
    return c.json(errorResponse, 500)
  }
})
環境変数の型定義
interface Environment {
  PORT: number
  DATABASE_URL: string
  JWT_SECRET: string
  JWT_EXPIRES_IN: string
  NODE_ENV: 'development' | 'production' | 'test'
  CORS_ORIGINS: string[]
  LOG_LEVEL: 'debug' | 'info' | 'warn' | 'error'
}
const parseEnvironment = (): Environment => {
  const requiredEnvVars = {
    PORT: parseInt(process.env.PORT || '3000'),
    DATABASE_URL: process.env.DATABASE_URL || 'sqlite://app.db',
    JWT_SECRET: process.env.JWT_SECRET || (() => {
      throw new Error('JWT_SECRET is required')
    })(),
    JWT_EXPIRES_IN: process.env.JWT_EXPIRES_IN || '24h',
    NODE_ENV: (process.env.NODE_ENV as Environment['NODE_ENV']) || 'development',
    CORS_ORIGINS: process.env.CORS_ORIGINS?.split(',') || ['http://localhost:3000'],
    LOG_LEVEL: (process.env.LOG_LEVEL as Environment['LOG_LEVEL']) || 'info'
  }
  
  return requiredEnvVars
}
export const env = parseEnvironment()
型安全なミドルウェア
ジェネリックミドルウェア
interface AuthenticatedUser {
  id: string
  email: string
  role: 'user' | 'admin'
}
// 型安全な認証ミドルウェア
const authenticateUser = async (c: Context, next: Next) => {
  const token = c.req.header('Authorization')?.replace('Bearer ', '')
  
  if (!token) {
    return c.json({ error: 'Authentication required' }, 401)
  }
  
  try {
    const user: AuthenticatedUser = await verifyToken(token)
    c.set('user', user) // ユーザー情報を Context に設定
    await next()
  } catch (error) {
    return c.json({ error: 'Invalid token' }, 401)
  }
}
// 認証されたユーザー情報を型安全に取得
app.get('/profile', authenticateUser, (c) => {
  const user = c.get('user') as AuthenticatedUser // 型アサーションが必要
  return c.json({
    id: user.id,
    email: user.email,
    role: user.role
  })
})
より型安全なアプローチ
// カスタム Context 型を定義
interface AuthenticatedContext extends Context {
  get(key: 'user'): AuthenticatedUser
}
// 型ガード関数
const isAuthenticated = (c: Context): c is AuthenticatedContext => {
  return c.get('user') !== undefined
}
app.get('/profile', authenticateUser, (c) => {
  if (!isAuthenticated(c)) {
    return c.json({ error: 'Authentication required' }, 401)
  }
  
  const user = c.get('user') // AuthenticatedUser型で推論される
  return c.json({
    id: user.id,
    email: user.email,
    role: user.role
  })
})
データベースとの型連携
Prismaとの組み合わせ
// schema.prisma
/*
model User {
  id        String   @id @default(cuid())
  email     String   @unique
  name      String
  posts     Post[]
  createdAt DateTime @default(now())
  updatedAt DateTime @updatedAt
}
model Post {
  id       String @id @default(cuid())
  title    String
  content  String
  authorId String
  author   User   @relation(fields: [authorId], references: [id])
}
*/
import { PrismaClient } from '@prisma/client'
const prisma = new PrismaClient()
// Prismaの型を直接使用
app.get('/users', async (c) => {
  const users = await prisma.user.findMany({
    include: {
      posts: true
    }
  })
  
  // users は User & { posts: Post[] } 型で推論される
  return c.json(users)
})
app.post('/users', zValidator('json', CreateUserSchema), async (c) => {
  const userData = c.req.valid('json')
  
  const user = await prisma.user.create({
    data: userData
  })
  
  return c.json(user, 201)
})
DrizzleORMとの組み合わせ
import { drizzle } from 'drizzle-orm/better-sqlite3'
import { text, integer, sqliteTable } from 'drizzle-orm/sqlite-core'
// スキーマ定義
export const users = sqliteTable('users', {
  id: text('id').primaryKey(),
  name: text('name').notNull(),
  email: text('email').notNull().unique(),
  age: integer('age'),
  createdAt: text('created_at').notNull()
})
// 型推論
export type User = typeof users.$inferSelect
export type CreateUser = typeof users.$inferInsert
const db = drizzle(sqlite)
app.get('/users', async (c) => {
  const allUsers = await db.select().from(users)
  // allUsers は User[] 型で推論される
  return c.json(allUsers)
})
型安全なテスト
import { describe, it, expect } from 'vitest'
import { testClient } from 'hono/testing'
describe('User API', () => {
  const client = testClient(app)
  
  it('should create user', async () => {
    const userData: CreateUserRequest = {
      name: 'John Doe',
      email: 'john@example.com'
    }
    
    const res = await client.users.$post({
      json: userData
    })
    
    expect(res.status).toBe(201)
    
    const user = await res.json()
    expect(user).toMatchObject({
      name: userData.name,
      email: userData.email
    })
  })
  
  it('should validate input', async () => {
    const invalidData = {
      name: '', // 空文字列は無効
      email: 'invalid-email'
    }
    
    const res = await client.users.$post({
      json: invalidData
    })
    
    expect(res.status).toBe(400)
    
    const error = await res.json()
    expect(error).toHaveProperty('error', 'Validation failed')
  })
})
TypeScriptの設定最適化
厳密な設定
tsconfig.json:
{
  "compilerOptions": {
    "target": "ES2022",
    "module": "ES2022",
    "moduleResolution": "bundler",
    "strict": true,
    "noUnusedLocals": true,
    "noUnusedParameters": true,
    "exactOptionalPropertyTypes": true,
    "noImplicitReturns": true,
    "noFallthroughCasesInSwitch": true,
    "noUncheckedIndexedAccess": true,
    "allowSyntheticDefaultImports": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true,
    "outDir": "./dist",
    "rootDir": "./src",
    "baseUrl": "./src",
    "paths": {
      "@/*": ["*"],
      "@/types/*": ["types/*"],
      "@/utils/*": ["utils/*"]
    }
  },
  "include": ["src/**/*"],
  "exclude": ["node_modules", "dist"]
}
型チェックスクリプト
package.json:
{
  "scripts": {
    "type-check": "tsc --noEmit",
    "type-check:watch": "tsc --noEmit --watch",
    "lint": "eslint src/**/*.ts",
    "lint:fix": "eslint src/**/*.ts --fix"
  }
}
実践的な型設計パターン
状態管理の型安全性
type LoadingState<T> = 
  | { status: 'idle' }
  | { status: 'loading' }
  | { status: 'success'; data: T }
  | { status: 'error'; error: string }
interface ApiState<T> {
  data: LoadingState<T>
  refetch: () => Promise<void>
}
// 使用例
app.get('/users/:id', async (c) => {
  const id = c.req.param('id')
  let state: LoadingState<User> = { status: 'loading' }
  
  try {
    const user = await getUserById(id)
    if (user) {
      state = { status: 'success', data: user }
    } else {
      state = { status: 'error', error: 'User not found' }
    }
  } catch (error) {
    state = { status: 'error', error: error.message }
  }
  
  return c.json(state)
})
やってみよう!
型安全性を活用したAPIを作成してみましょう:
- 
強い型付きのCRUD API
- Zodバリデーション付き
 - Prisma/DrizzleORMとの連携
 - エラーハンドリングの型安全性
 
 - 
認証システムの型定義
- JWT ペイロードの型定義
 - ロールベースアクセス制御
 - ミドルウェアの型安全性
 
 - 
テストコードの型安全性
- テストケースでの型推論
 - モックデータの型定義
 
 
ポイント
- 型推論の活用:TypeScriptの強力な型推論を最大限活用
 - Zodとの連携:バリデーションと型定義の統一
 - カスタム型定義:アプリケーション固有の型システム構築
 - 厳密な設定:TypeScriptコンパイラの厳密な設定活用
 - 型安全なテスト:テストコードでも型安全性を確保