MeWrite Docs

Hono: Context type mismatch or undefined

HonoでContextの型や値に問題がある場合のエラー

概要

Honoフレームワークでコンテキスト(c)の型が合わない、または値がundefinedの場合のエラーと解決策です。

エラーメッセージ

TypeError: Cannot read properties of undefined (reading 'user')
Property 'user' does not exist on type 'Context<Env, "/", {}>'
Type 'string | undefined' is not assignable to type 'string'

解決策

1. 基本的な型定義

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import { Hono } from 'hono'

// 環境変数の型
type Bindings = {
  DATABASE_URL: string
  JWT_SECRET: string
}

// コンテキストに追加する変数の型
type Variables = {
  user: { id: string; email: string }
}

const app = new Hono<{ Bindings: Bindings; Variables: Variables }>()

app.use('*', async (c, next) => {
  // c.set() と c.get() が型安全に
  c.set('user', { id: '1', email: 'user@example.com' })
  await next()
})

app.get('/me', (c) => {
  const user = c.get('user')  // 型推論される
  return c.json(user)
})

2. ミドルウェアでの型伝播

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import { createMiddleware } from 'hono/factory'

// 型付きミドルウェアを作成
const authMiddleware = createMiddleware<{
  Variables: { user: { id: string; role: string } }
}>(async (c, next) => {
  const token = c.req.header('Authorization')?.split(' ')[1]

  if (!token) {
    return c.json({ error: 'Unauthorized' }, 401)
  }

  const user = await verifyToken(token)
  c.set('user', user)
  await next()
})

// ルートで使用
app.use('/api/*', authMiddleware)

app.get('/api/profile', (c) => {
  const user = c.get('user')  // 型が効く
  return c.json(user)
})

3. リクエストボディの型

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
import { zValidator } from '@hono/zod-validator'
import { z } from 'zod'

const createUserSchema = z.object({
  email: z.string().email(),
  password: z.string().min(8),
})

app.post(
  '/users',
  zValidator('json', createUserSchema),
  (c) => {
    const body = c.req.valid('json')  // 型推論される
    // body.email, body.password
    return c.json({ id: '1', ...body })
  }
)

4. パラメータとクエリの型

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
// パスパラメータ
app.get('/users/:id', (c) => {
  const id = c.req.param('id')  // string
  return c.json({ id })
})

// 複数パラメータ
app.get('/users/:userId/posts/:postId', (c) => {
  const { userId, postId } = c.req.param()
  return c.json({ userId, postId })
})

// クエリパラメータ
app.get('/search', (c) => {
  const query = c.req.query('q')  // string | undefined
  const page = c.req.query('page') ?? '1'
  return c.json({ query, page })
})

5. 環境変数へのアクセス

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
// Cloudflare Workers
type Bindings = {
  DB: D1Database
  KV: KVNamespace
  SECRET: string
}

const app = new Hono<{ Bindings: Bindings }>()

app.get('/data', async (c) => {
  // 型安全にアクセス
  const db = c.env.DB
  const result = await db.prepare('SELECT * FROM users').all()
  return c.json(result)
})

6. レスポンスの型

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
import { Hono } from 'hono'
import type { StatusCode } from 'hono/utils/http-status'

app.get('/users/:id', async (c): Promise<Response> => {
  const user = await getUser(c.req.param('id'))

  if (!user) {
    return c.json({ error: 'Not found' }, 404)
  }

  return c.json(user, 200)
})

// または型ヘルパーを使用
import type { TypedResponse } from 'hono'

type UserResponse = { id: string; name: string }

app.get('/users/:id', async (c): Promise<TypedResponse<UserResponse>> => {
  // ...
})

7. RPC型(クライアント用)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
// server.ts
import { Hono } from 'hono'

const app = new Hono()
  .get('/users', (c) => c.json([{ id: '1', name: 'John' }]))
  .post('/users', async (c) => {
    const body = await c.req.json()
    return c.json({ id: '2', ...body }, 201)
  })

export type AppType = typeof app

// client.ts
import { hc } from 'hono/client'
import type { AppType } from './server'

const client = hc<AppType>('http://localhost:8787')

// 型安全なAPI呼び出し
const res = await client.users.$get()
const users = await res.json()  // 型推論される

8. エラーハンドリング

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
import { HTTPException } from 'hono/http-exception'

app.get('/protected', (c) => {
  const user = c.get('user')

  if (!user) {
    throw new HTTPException(401, { message: 'Unauthorized' })
  }

  return c.json(user)
})

// グローバルエラーハンドラー
app.onError((err, c) => {
  if (err instanceof HTTPException) {
    return c.json({ error: err.message }, err.status)
  }
  console.error(err)
  return c.json({ error: 'Internal Server Error' }, 500)
})

関連エラー

関連エラー

最終更新: 2025-12-22