JSON响应与文本响应

要点

  • Hono 通过 Context 对象(c)提供多种响应方法,覆盖 JSON、纯文本、HTML、重定向等场景
  • c.json() 是 API 开发中最常用的响应方式,自动处理序列化和 Content-Type
  • c.text() 适合健康检查、简单消息等轻量场景
  • c.stream() / c.streamText() 支持流式响应,是 AI 场景的核心能力
  • c.header()c.status() 可以精细控制响应头和状态码
  • 所有响应方法底层都返回标准 Response 对象,没有框架魔法

1. c.json() — 最常用的响应方式

API 开发中 90% 的响应都是 JSON。c.json() 自动处理序列化和 Content-Type:

// src/index.ts
import { Hono } from 'hono'
 
const app = new Hono()
 
app.get('/api/user', (c) => {
  return c.json({ id: 1, name: '白川', role: 'admin' })
})
 
// 第二个参数是状态码,默认 200
app.post('/api/users', async (c) => {
  const body = await c.req.json()
  return c.json({ message: '创建成功', data: body }, 201)
})
 
export default app

响应头自动带上 Content-Type: application/json; charset=UTF-8,不需要手动设置。第三个参数可以传额外响应头:c.json(data, 200, { 'X-Request-Id': 'abc-123' })

常见状态码:200 默认成功,201 资源创建,400 参数错误,404 不存在,500 服务端错误。

AI 场景:返回模型输出

// src/index.ts
app.post('/api/chat', async (c) => {
  const { prompt } = await c.req.json()
  const result = await callModel(prompt)
 
  return c.json({
    content: result.text,
    usage: { promptTokens: result.promptTokens, completionTokens: result.completionTokens, totalTokens: result.totalTokens },
    model: 'gpt-4o'
  })
})

2. c.text() — 纯文本响应

返回纯文本,Content-Type 是 text/plain; charset=UTF-8。适合健康检查、Webhook 验证、调试快速返回等轻量场景:

// src/index.ts
app.get('/health', (c) => {
  return c.text('OK')
})
 
app.get('/ping', (c) => {
  return c.text('pong')
})
 
// 同样支持状态码
app.post('/api/maintenance', (c) => {
  return c.text('服务维护中,请稍后再试', 503)
})

3. c.html() — HTML 响应

返回 HTML 内容,Content-Type 是 text/html; charset=UTF-8

// src/index.ts
app.get('/page', (c) => {
  return c.html(`
    <!DOCTYPE html>
    <html>
    <head><title>Demo</title></head>
    <body>
      <h1>Hello from Hono</h1>
      <p>这是一个服务端渲染的页面</p>
    </body>
    </html>
  `)
})

适用场景:服务端渲染页面、管理后台界面、邮件模板预览、OAuth 回调中间页。

如果项目用了 hono/jsx,可以直接在 c.html() 里写 JSX,编译后自动生成 HTML 字符串。

4. c.redirect() — 重定向

c.redirect() 默认返回 302 临时重定向,第二个参数可以指定状态码:

// src/index.ts
// 默认 302
app.get('/old-page', (c) => {
  return c.redirect('/new-page')
})
 
// 301 永久重定向 — 浏览器会缓存,搜索引擎更新索引
app.get('/legacy', (c) => {
  return c.redirect('/modern', 301)
})
 
// 307 临时重定向 — 严格保留原始请求方法(POST 仍然是 POST)
// 302 下 POST 可能被浏览器转成 GET,307 不会
app.get('/temp', (c) => {
  return c.redirect('/other', 307)
})
 
// OAuth 回调跳转
app.get('/login', (c) => {
  return c.redirect('https://github.com/login/oauth/authorize?...')
})

5. c.body() — 自定义响应

c.body() 可以返回任意数据,不设 Content-Type(除非你手动用 c.header() 设)。适合 XML、CSV、二进制等非标准格式:

// src/index.ts
// 返回 CSV
app.get('/api/export', (c) => {
  const csv = 'name,email\nAlice,[email protected]\nBob,[email protected]'
  c.header('Content-Type', 'text/csv')
  c.header('Content-Disposition', 'attachment; filename="users.csv"')
  return c.body(csv)
})
 
// 返回二进制数据
app.get('/api/image', (c) => {
  const imageData = await generateImage()
  c.header('Content-Type', 'image/png')
  return c.body(imageData)
})

c.body() 本质上是帮你构造标准 Response 对象。你完全可以自己 new Response() 然后 return,但配合 c.header() 用起来更方便。

6. c.stream() / c.streamText() — 流式响应

流式响应是 AI 场景的核心能力。模型生成内容不是一次性返回的,而是一个 token 一个 token 往外吐。Hono 提供两个方法:

  • c.stream() — Content-Type 是 application/octet-stream,适合 SSE(Server-Sent Events)
  • c.streamText() — Content-Type 是 text/plain,适合纯文本流式输出
// src/index.ts - c.stream() SSE 示例
app.get('/api/stream', (c) => {
  return c.stream(async (stream) => {
    for (let i = 0; i < 5; i++) {
      await stream.write(`data: ${JSON.stringify({ index: i })}\n\n`)
      await stream.sleep(200)
    }
    await stream.write('data: [DONE]\n\n')
  })
})
// src/index.ts - 真实 AI 流式输出
import OpenAI from 'openai'
const openai = new OpenAI({ apiKey: process.env.OPENAI_API_KEY })
 
app.post('/api/chat/stream', async (c) => {
  const { messages } = await c.req.json()
 
  const response = await openai.chat.completions.create({
    model: 'gpt-4o', messages, stream: true
  })
 
  return c.streamText(async (stream) => {
    for await (const chunk of response) {
      const content = chunk.choices[0]?.delta?.content || ''
      if (content) await stream.write(content)
    }
  })
})

客户端用 fetch + response.body.getReader()EventSource 接收流数据。

7. c.notFound() — 404 响应

c.notFound() 返回一个 404 响应。默认行为是返回纯文本「404 Not Found」:

// src/index.ts
app.get('/api/users/:id', async (c) => {
  const user = await findUser(c.req.param('id'))
 
  if (!user) {
    return c.notFound()
  }
 
  return c.json(user)
})

可以通过设置 app.notFound() 来自定义全局 404 响应:

// src/index.ts
const app = new Hono()
 
// 自定义 404 处理
app.notFound((c) => {
  return c.json({
    error: 'Not Found',
    message: `路径 ${c.req.path} 不存在`,
    statusCode: 404
  }, 404)
})

注意 app.notFound()c.notFound() 的区别:

  • app.notFound(handler) — 注册全局 404 处理器,路由匹配不到时调用
  • c.notFound() — 在路由内部手动返回 404,适合资源查找失败的场景

8. 响应 Headers 和 Status Code

除了在各响应方法中直接传参,还可以用 c.header()c.status() 单独设置:

// src/index.ts
app.get('/api/data', (c) => {
  c.header('X-Custom-Header', 'hello')
  c.header('Cache-Control', 'max-age=3600')
  return c.json({ data: 'value' })
})
 
app.delete('/api/users/:id', async (c) => {
  await deleteUser(c.req.param('id'))
  c.status(204) // No Content
  return c.text('')
})

c.header()c.status() 都返回 Context 本身,支持链式调用:

// src/index.ts
app.get('/api/file/:id', async (c) => {
  const file = await getFile(c.req.param('id'))
  return c
    .header('Content-Type', file.mimeType)
    .header('Content-Disposition', `attachment; filename="${file.name}"`)
    .body(file.data)
})

注意链式调用中 c.header()c.status() 只是记录值,真正生效在最终的 c.json() / c.text() / c.body() 调用时。

9. 各种响应方式对比

方法Content-Type适用场景支持流式
c.json()application/jsonAPI 响应、结构化数据
c.text()text/plain健康检查、简单消息
c.html()text/html服务端渲染页面
c.redirect()重定向到另一个 URL
c.body()不设置(手动指定)XML、CSV、二进制数据
c.stream()application/octet-streamSSE、通用流式数据
c.streamText()text/plainAI 流式文本输出
c.notFound()text/plain404 响应

选哪种响应方式,取决于客户端需要什么格式的数据。API 接口用 c.json(),纯文本用 c.text(),AI 流式输出用 c.streamText(),基本覆盖了日常开发的大部分场景。

延伸阅读

总结

这一篇梳理了 Hono 的所有响应方式。核心就三句话:

  1. API 接口用 c.json(),它帮你处理序列化和 Content-Type
  2. AI 流式输出用 c.streamText(),逐 token 写入
  3. 特殊场景(XML、CSV、二进制)用 c.body() 配合 c.header() 手动控制

所有方法底层都返回标准的 Response 对象。掌握这些响应方法之后,接下来看中间件系统——Hono 的中间件机制让这些响应处理可以灵活组合和复用。