Skip to content

Typed Context:如何用泛型定义 c.var.user: User

const app = new Hono<{ Variables: { user: User } }>()

在 Hono 中,类型安全是构建可维护、可扩展应用的关键。虽然 c.var 提供了中间件间传递数据的能力,但默认情况下它是 any 类型,容易引发运行时错误。

通过 Hono 强大的 泛型上下文(Typed Context) 机制,你可以为 c.varc.envc.req.param 等添加精确的 TypeScript 类型,实现 编译时类型检查IDE 智能提示

一、问题:c.var.user 默认是 any

ts
// 没有类型安全
app.use('/api/*', async (c, next) => {
  const user = { id: '123', name: 'John' }
  c.set('user', user)
  await next()
})

app.get('/profile', (c) => {
  const user = c.var.user // → any
  return c.json({ name: user.name.toUpperCase() })
})
  • 如果 user 结构变化,编译器无法发现错误;
  • IDE 无法提供 .name.id 等属性提示。

二、解决方案:使用泛型定义 Variables

Hono 允许你在创建应用时,通过泛型参数定义上下文的类型结构:

ts
type User = {
  id: string
  name: string
  email: string
}

// 定义 Typed Context
const app = new Hono<{
  Variables: {
    user: User
  }
}>()
现在 c.var.user 有了正确类型:
ts
app.use('/api/*', async (c, next) => {
  const user: User = { id: '123', name: 'John', email: 'john@example.com' }
  c.set('user', user) // ✅ 类型检查:必须是 User
  await next()
})

app.get('/profile', (c) => {
  const user = c.var.user // ✅ 类型为 User
  // user. → IDE 自动提示 id, name, email
  return c.json({ profile: user })
})

如果误用:

ts
c.set('user', { wrong: 'data' }) // ❌ TS Error: 缺少 id, name, email

三、扩展:同时定义 envbindings

Hono 的泛型支持多个键,用于定义完整的上下文类型:

ts
type User = { id: string; name: string }

type Env = {
  JWT_SECRET: string
  DB: D1Database
  ASSETS: Fetcher
}

const app = new Hono<{
  Variables: {
    user: User
    role: 'admin' | 'user'
  }
  Bindings: Env  // 对应 c.env
}>()
现在所有 c.* 属性都有类型:
ts
app.use('/admin/*', async (c, next) => {
  const token = c.req.header('Authorization')?.split(' ')[1]
  const secret = c.env.JWT_SECRET // ✅ string
  const db = c.env.DB // ✅ D1Database

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

  try {
    const payload = await verify(token, secret)
    c.set('user', { id: payload.sub, name: payload.name })
    c.set('role', 'admin')
    await next()
  } catch {
    return c.json({ error: 'Invalid token' }, 401)
  }
})

app.get('/admin/dashboard', (c) => {
  const user = c.var.user // ✅ User
  const role = c.var.role // ✅ 'admin' | 'user'
  const db = c.env.DB // ✅ D1Database

  return c.json({ admin: user, dbStatus: 'ok' })
})

四、高级用法:路径参数类型化

你还可以为 c.req.param() 定义类型:

ts
// 定义路由参数类型
app.get('/users/:id/:role', (c) => {
  const id = c.req.param('id')     // string
  const role = c.req.param('role') // string

  // 但如果我们知道 role 只能是 'admin' | 'user'?
})

结合泛型,可以更精确:

ts
const app = new Hono<{
  Variables: { user: User }
  Bindings: Env
  'customParam': {
    role: 'admin' | 'user'
  }
}>()

app.get('/users/:id/:role', (c) => {
  const id = c.req.param('id')     // string
  const role = c.req.param('role') // 'admin' | 'user' ✅
})

注意:目前 Hono 对 param 的泛型支持有限,更推荐在函数内做类型断言或验证。

五、最佳实践:集中定义类型

创建 types.ts 统一管理:

ts
// types.ts
export type User = {
  id: string
  name: string
  email: string
  avatar?: string
}

export type Env = {
  JWT_SECRET: string
  DATABASE_URL: string
  REDIS: Redis
  BUCKET: R2Bucket
}

export type AppVariables = {
  user: User
  ipLocation?: { city: string; country: string }
}

在应用中使用:

ts
// app.ts
import { Hono } from 'hono'
import type { AppVariables, Env } from './types'

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

六、为什么这很重要?

优势说明
类型安全避免 c.var.user.nam 拼写错误导致 undefined
IDE 提示.user. 后自动弹出可用属性
重构安全修改 User 类型后,编译器提示所有使用点
文档化类型即文档,新人快速理解上下文数据结构
减少 Bug编译时发现类型错误,而非运行时崩溃

七、总结

通过:

ts
const app = new Hono<{ Variables: { user: User } }>()

你实现了:

  • c.var.user 的类型为 User
  • 中间件注入和处理器读取都受类型检查;
  • 配合 c.env 泛型,构建全栈类型安全的 API。

Typed Context 是 Hono 类型系统的核心,它让你在享受轻量框架便利的同时,不失 TypeScript 的严谨性。在生产项目中,强烈建议从第一天就开始使用。