JSON响应与文本响应
要点
- Hono 通过 Context 对象(
c)提供多种响应方法,覆盖 JSON、纯文本、HTML、重定向等场景 c.json()是 API 开发中最常用的响应方式,自动处理序列化和 Content-Typec.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/json | API 响应、结构化数据 | 否 |
c.text() | text/plain | 健康检查、简单消息 | 否 |
c.html() | text/html | 服务端渲染页面 | 否 |
c.redirect() | — | 重定向到另一个 URL | 否 |
c.body() | 不设置(手动指定) | XML、CSV、二进制数据 | 否 |
c.stream() | application/octet-stream | SSE、通用流式数据 | 是 |
c.streamText() | text/plain | AI 流式文本输出 | 是 |
c.notFound() | text/plain | 404 响应 | 否 |
选哪种响应方式,取决于客户端需要什么格式的数据。API 接口用 c.json(),纯文本用 c.text(),AI 流式输出用 c.streamText(),基本覆盖了日常开发的大部分场景。
延伸阅读
总结
这一篇梳理了 Hono 的所有响应方式。核心就三句话:
- API 接口用
c.json(),它帮你处理序列化和 Content-Type - AI 流式输出用
c.streamText(),逐 token 写入 - 特殊场景(XML、CSV、二进制)用
c.body()配合c.header()手动控制
所有方法底层都返回标准的 Response 对象。掌握这些响应方法之后,接下来看中间件系统——Hono 的中间件机制让这些响应处理可以灵活组合和复用。