响应
本指南介绍 AdonisJS 的 Response 类,以及用于构建 HTTP 响应的可用方法。你将了解:
- 以不同格式发送响应体
- 处理请求头和 Cookie
- 处理重定向和文件下载
- 理解响应序列化如何工作
- 用自定义方法扩展 Response 类
概述
Response 类为构建 AdonisJS 应用中的 HTTP 响应提供了辅助函数。与其直接使用 Node.js 的原始响应对象,Response 类为发送 JSON、设置请求头、处理重定向和流式传输文件下载等常见任务提供了流畅、富有表现力的 API。
Response 类可通过 ctx.response 属性访问。你可以在应用的路由处理器、中间件和异常处理器中访问它。对于许多简单的响应,你可以直接从路由处理器返回值,AdonisJS 会自动使用 Response 类来发送它们。
发送响应体
Response 类提供了多种发送响应体的方式。你可以直接从路由处理器返回值,也可以使用显式的响应方法。
从路由处理器返回值
最简单的方式是直接从路由处理器返回值。AdonisJS 会自动序列化该值并设置适当的内容类型请求头。
import router from '@adonisjs/core/services/router'
router.get('/', async () => {
/**
* 返回纯文本,content-type: text/plain
*/
return 'This is the homepage.'
})
router.get('/welcome', async () => {
/**
* 返回 HTML 片段,content-type: text/html
*/
return '<p>This is the homepage</p>'
})
router.get('/api/page', async () => {
/**
* 返回 JSON,content-type: application/json
*/
return { page: 'home' }
})
router.get('/timestamp', async () => {
/**
* Date 实例会被转换为 ISO 字符串
*/
return new Date()
})
使用 response.send()
你也可以显式使用 response.send() 方法,它提供相同的自动内容类型检测。
import router from '@adonisjs/core/services/router'
router.get('/', async ({ response }) => {
/**
* send() 方法与返回值的工作方式完全相同。
* 当你需要在发送前设置请求头或状态时很有用。
*/
response.send('This is the homepage')
})
router.get('/data', async ({ response }) => {
/**
* 对象和数组会自动被字符串化
*/
response.send({ page: 'home' })
})
强制发送 JSON 响应
当你需要确保响应以 JSON 形式发送(即使它可能被检测为 HTML)时,使用 response.json() 方法。
另请参阅: 响应体序列化
import Post from '#models/post'
import type { HttpContext } from '@adonisjs/core/http'
export default class PostsController {
async index({ response }: HttpContext) {
const posts = await Post.all()
/**
* 显式将 content-type 设置为 application/json
* 并序列化 posts 数组
*/
response.json(posts)
}
}
处理请求头
Response 类提供了设置、追加和移除 HTTP 请求头的方法。请求头必须在响应体发送之前设置。
设置请求头
使用 response.header() 方法设置响应头。如果该请求头已存在,它将被覆盖。
import type { HttpContext } from '@adonisjs/core/http'
export default class ApiController {
async index({ response }: HttpContext) {
/**
* 设置用于 API 版本控制的自定义请求头
*/
response.header('X-API-Version', 'v1')
/**
* 设置缓存控制请求头
*/
response.header('Cache-Control', 'public, max-age=3600')
return { status: 'ok' }
}
}
安全地设置请求头
response.safeHeader() 方法仅在请求头尚不存在时才设置它。当你想提供默认值而不覆盖现有请求头时,这很有用。
import type { HttpContext } from '@adonisjs/core/http'
import type { NextFn } from '@adonisjs/core/types/http'
export default class CorsMiddleware {
async handle({ response }: HttpContext, next: NextFn) {
/**
* 仅当其他中间件尚未设置时才设置 CORS 请求头
*/
response.safeHeader('Access-Control-Allow-Origin', '*')
await next()
}
}
追加到请求头
某些请求头可以有多个值。使用 response.append() 添加额外的值,而不移除现有的值。
import type { HttpContext } from '@adonisjs/core/http'
export default class DownloadsController {
async show({ response }: HttpContext) {
/**
* 为不同的 Cookie 追加多个 Set-Cookie 请求头
*/
response.append('Set-Cookie', 'session=abc123; HttpOnly')
response.append('Set-Cookie', 'preferences=dark-mode; Path=/')
return { download: 'ready' }
}
}
移除请求头
使用 response.removeHeader() 移除之前设置的请求头。
import type { HttpContext } from '@adonisjs/core/http'
import type { NextFn } from '@adonisjs/core/types/http'
export default class SecurityMiddleware {
async handle({ response }: HttpContext, next: NextFn) {
await next()
/**
* 移除 server 请求头以隐藏服务器实现细节
*/
response.removeHeader('X-Powered-By')
}
}
处理重定向
response.redirect() 方法返回 Redirect 类的实例,该类提供流畅的 API 用于创建带有不同目标和选项的重定向响应。
重定向到路径
使用 response.redirect().toPath() 重定向到特定的 URI 或外部 URL。
import type { HttpContext } from '@adonisjs/core/http'
export default class AuthController {
async logout({ response, auth }: HttpContext) {
await auth.logout()
/**
* 退出登录后重定向到主页
*/
response.redirect().toPath('/')
}
async external({ response }: HttpContext) {
/**
* 重定向到外部网站
*/
response.redirect().toPath('https://adonisjs.com')
}
}
重定向到命名路由
response.redirect().toRoute() 方法接受路由标识符及其参数,使得在 URL 变更时重定向仍然易于维护。
import Post from '#models/post'
import { createPostValidator } from '#validators/post'
import type { HttpContext } from '@adonisjs/core/http'
export default class PostsController {
async store({ request, response }: HttpContext) {
/**
* 验证传入的请求数据
*/
const payload = await request.validateUsing(createPostValidator)
/**
* 创建文章
*/
const post = await Post.create(payload)
/**
* 重定向到新创建文章的展示页面
*/
response.redirect().toRoute('posts.show', [post.id])
}
}
重定向返回
使用 response.redirect().back() 重定向到上一页。该方法读取 Referer 请求头,并验证引用来源的主机是否与当前请求的 Host 请求头匹配。如果引用来源缺失、无效,或来自未知主机,重定向将回退到 /。
你可以向 back() 提供一个自定义的回退 URL 作为参数。
import type { HttpContext } from '@adonisjs/core/http'
export default class CommentsController {
async destroy({ response, params }: HttpContext) {
/**
* 重定向回用户来源的页面。
* 如果找不到有效的引用来源,则回退到 /posts。
*/
response.redirect().back('/posts')
}
}
允许外部引用来源主机
默认情况下,back() 只接受与当前请求主机匹配的引用来源。这可以防止开放重定向攻击,即恶意的引用来源可能将用户发送到外部站点。
如果你的应用跨越多个域名(例如 app.example.com 和 admin.example.com),你可以将受信任的主机添加到 redirect.allowedHosts 配置中,这样 back() 也会接受来自这些域名的引用来源。
import { defineConfig } from '@adonisjs/core/http'
export const http = defineConfig({
redirect: {
allowedHosts: [
'app.example.com',
'admin.example.com',
],
},
})
自定义上一页 URL 的解析
当使用 @adonisjs/session 包时,你可以通过设置 redirect.previousUrl 属性将会话中的上一页 URL 存储起来。设置后,back() 将使用会话中的值,而不是 Referer 请求头。
当你的应用将用户重定向到第三方网站(如 OAuth 提供商)时,这很有用。当用户返回时,浏览器的 Referer 请求头将指向外部站点,因此 back() 会回退到 / 而不是用户原本所在的页面。通过在重定向前将预期的返回 URL 存储在会话中,你可以确保 back() 将他们带到正确的位置。
import type { HttpContext } from '@adonisjs/core/http'
export default class AuthController {
async redirectToProvider({ session, response }: HttpContext) {
/**
* 存储当前 URL,以便在 OAuth 流程完成之后
* back() 返回到这里
*/
session.put('redirect.previousUrl', '/dashboard')
response.redirect().toPath('https://accounts.google.com/o/oauth2/auth')
}
async handleCallback({ response }: HttpContext) {
/**
* 这将重定向到 /dashboard(来自会话)
* 而不是使用 Referer 请求头
*/
response.redirect().back()
}
}
基于会话的上一页 URL 解析需要 @adonisjs/session 包。没有它,back() 将始终使用 Referer 请求头。
设置重定向状态码
默认情况下,重定向使用 302 状态码。使用 status() 方法设置不同的重定向状态。
import type { HttpContext } from '@adonisjs/core/http'
export default class PagesController {
async oldPage({ response }: HttpContext) {
/**
* 为已移动的页面使用永久重定向(301)
*/
response.redirect().status(301).toPath('/new-page')
}
}
转发查询字符串
查询字符串转发在 AdonisJS 起步套件中默认通过服务器配置中的 redirect.forwardQueryString 选项启用。你可以在 config/app.ts 文件中验证这一点。
import { defineConfig } from '@adonisjs/core/http'
export const http = defineConfig({
redirect: {
forwardQueryString: true,
},
})
启用后,所有重定向都会自动将当前 URL 的查询字符串携带到目标。例如,如果当前 URL 是 /search?q=adonis&sort=date,而你重定向到 /results,用户将被发送到 /results?q=adonis&sort=date。
你可以通过向 withQs() 传递 false 来禁用特定重定向的转发。
import type { HttpContext } from '@adonisjs/core/http'
export default class AuthController {
async logout({ response }: HttpContext) {
/**
* 禁用此重定向的查询字符串转发。
* 用户将被发送到 /,不带任何查询参数。
*/
response.redirect().withQs(false).toPath('/')
}
}
如果默认未启用查询字符串转发,你可以通过不带参数调用 withQs() 为特定重定向启用它。
import type { HttpContext } from '@adonisjs/core/http'
export default class SearchController {
async filter({ response }: HttpContext) {
/**
* 将现有的查询参数转发到新 URL。
* 如果当前 URL 是 /search?q=adonis&sort=date,
* 这将重定向到 /results?q=adonis&sort=date
*/
response.redirect().withQs().toPath('/results')
}
}
设置自定义查询字符串
向 withQs() 传递一个对象来设置自定义查询参数。多次链式调用该方法来追加额外的参数。
import type { HttpContext } from '@adonisjs/core/http'
export default class ProductsController {
async search({ response }: HttpContext) {
/**
* 使用自定义查询参数重定向。
* 结果为 /products?category=electronics&sort=price
*/
response
.redirect()
.withQs({ category: 'electronics' })
.withQs({ sort: 'price' })
.toPath('/products')
}
}
重定向到预期 URL
toIntended() 方法重定向到之前通过 session.setIntendedUrl() 存储在会话中的 URL。如果没有存储预期的 URL,它会重定向到提供的回退地址。存储的 URL 在使用后会被消费(从会话中移除)。
withIntendedUrl() 方法将当前请求 URL 作为预期目标存储在会话中。它只对匹配了路由的 GET、导航类请求进行存储。
这些方法在安装了 @adonisjs/session 包时可用。请参阅
登录后重定向到预期 URL
获取完整示例。
import type { HttpContext } from '@adonisjs/core/http'
export default class SessionController {
async store({ auth, request, response }: HttpContext) {
await auth.use('web').login(
await User.verifyCredentials(
request.input('email'),
request.input('password')
)
)
/**
* 重定向到预期 URL 或 /dashboard
*/
return response.redirect().toIntended('/dashboard')
}
}
流式传输与下载
Response 类提供了流式传输数据和服务文件下载的方法。这些方法会自动处理适当的请求头、缓存和清理。
流式传输响应
使用 response.stream() 将可读流作为响应发送。AdonisJS 会等待路由处理器和上游中间件完成,然后才开始从流中读取。
import { createReadStream } from 'node:fs'
import type { HttpContext } from '@adonisjs/core/http'
export default class ExportsController {
async generate({ response }: HttpContext) {
/**
* 从文件创建一个可读流
*/
const stream = createReadStream('./exports/data.csv')
/**
* 将文件流式传输给客户端。
* AdonisJS 自动处理背压和清理。
*/
response.stream(stream)
}
}
下载文件
response.download() 方法流式传输一个文件,并为下载设置适当的请求头。它接受一个文件的绝对路径。
import app from '@adonisjs/core/services/app'
import type { HttpContext } from '@adonisjs/core/http'
export default class InvoicesController {
async download({ response, params }: HttpContext) {
/**
* 构建指向发票文件的绝对路径
*/
const filePath = app.makePath(`storage/invoices/${params.id}.pdf`)
/**
* 流式传输文件以供下载,并支持 ETag。
* 只要文件内容保持不变,浏览器就会缓存该响应。
*/
response.download(filePath)
}
}
强制下载并自定义文件名
使用 response.attachment() 强制下载并指定自定义文件名。浏览器将提示用户以给定名称保存文件。
import app from '@adonisjs/core/services/app'
import type { HttpContext } from '@adonisjs/core/http'
export default class ReportsController {
async export({ response, params }: HttpContext) {
/**
* 指向报表文件的绝对路径
*/
const filePath = app.makePath(`storage/reports/${params.id}.xlsx`)
/**
* 以用户友好的文件名强制下载。
* 第二个参数在 Content-Disposition 请求头中设置文件名。
*/
response.attachment(filePath, `monthly-report-${params.month}.xlsx`)
}
}
设置响应状态
Response 类提供了设置 HTTP 状态码的方法。状态码向客户端传达请求的结果。
设置状态码
使用 response.status() 设置 HTTP 状态码。该方法会覆盖任何之前设置的状态码。
import Post from '#models/post'
import type { HttpContext } from '@adonisjs/core/http'
export default class PostsController {
async store({ request, response }: HttpContext) {
const post = await Post.create(request.all())
/**
* 成功创建资源时返回 201 Created
*/
response.status(201)
return post
}
}
安全地设置状态
response.safeStatus() 方法仅在尚未设置状态码时才设置它。这在中间件中很有用,当你想提供默认值而不覆盖显式设置的状态码时。
import type { HttpContext } from '@adonisjs/core/http'
import type { NextFn } from '@adonisjs/core/types/http'
export default class JsonApiMiddleware {
async handle({ response }: HttpContext, next: NextFn) {
await next()
/**
* 仅在尚未设置时才设置默认的成功状态。
* 控制器特定的状态码优先。
*/
response.safeStatus(200)
}
}
状态简写方法
AdonisJS 提供了在一次调用中同时设置状态码和响应体的简写方法。
import User from '#models/user'
import type { HttpContext } from '@adonisjs/core/http'
export default class UsersController {
async show({ response, params }: HttpContext) {
const user = await User.find(params.id)
if (!user) {
/**
* 一次调用中设置状态 404 并发送响应体
*/
return response.notFound({ error: 'User not found' })
}
return user
}
async destroy({ response, params, auth }: HttpContext) {
const user = await User.findOrFail(params.id)
if (user.id !== auth.user.id) {
/**
* 设置状态 403 并发送错误消息
*/
return response.forbidden({ error: 'Cannot delete other users' })
}
await user.delete()
return response.noContent()
}
}
响应简写方法
AdonisJS 为常见的 HTTP 状态码提供了简写方法。每个方法都会设置状态码并可选择发送响应体。
| 方法 | 状态码 |
|---|---|
response.continue() | 100 |
response.switchingProtocols() | 101 |
response.ok(body?, generateEtag?) | 200 |
response.created(body?, generateEtag?) | 201 |
response.accepted(body?, generateEtag?) | 202 |
response.nonAuthoritativeInformation(body?, generateEtag?) | 203 |
response.noContent() | 204 |
response.resetContent() | 205 |
response.partialContent(body?, generateEtag?) | 206 |
response.multipleChoices(body?, generateEtag?) | 300 |
response.movedPermanently(body?, generateEtag?) | 301 |
response.movedTemporarily(body?, generateEtag?) | 302 |
response.seeOther(body?, generateEtag?) | 303 |
response.notModified(body?, generateEtag?) | 304 |
response.useProxy(body?, generateEtag?) | 305 |
response.temporaryRedirect(body?, generateEtag?) | 307 |
response.badRequest(body?, generateEtag?) | 400 |
response.unauthorized(body?, generateEtag?) | 401 |
response.paymentRequired(body?, generateEtag?) | 402 |
response.forbidden(body?, generateEtag?) | 403 |
response.notFound(body?, generateEtag?) | 404 |
response.methodNotAllowed(body?, generateEtag?) | 405 |
response.notAcceptable(body?, generateEtag?) | 406 |
response.proxyAuthenticationRequired(body?, generateEtag?) | 407 |
response.requestTimeout(body?, generateEtag?) | 408 |
response.conflict(body?, generateEtag?) | 409 |
response.gone(body?, generateEtag?) | 410 |
response.lengthRequired(body?, generateEtag?) | 411 |
response.preconditionFailed(body?, generateEtag?) | 412 |
response.requestEntityTooLarge(body?, generateEtag?) | 413 |
response.requestUriTooLong(body?, generateEtag?) | 414 |
response.unsupportedMediaType(body?, generateEtag?) | 415 |
response.requestedRangeNotSatisfiable(body?, generateEtag?) | 416 |
response.expectationFailed(body?, generateEtag?) | 417 |
response.unprocessableEntity(body?, generateEtag?) | 422 |
response.tooManyRequests(body?, generateEtag?) | 429 |
response.internalServerError(body?, generateEtag?) | 500 |
response.notImplemented(body?, generateEtag?) | 501 |
response.badGateway(body?, generateEtag?) | 502 |
response.serviceUnavailable(body?, generateEtag?) | 503 |
response.gatewayTimeout(body?, generateEtag?) | 504 |
response.httpVersionNotSupported(body?, generateEtag?) | 505 |
处理 Cookie
Response 类提供了设置不同安全级别 Cookie 的方法。AdonisJS 支持签名 Cookie、加密 Cookie 和普通 Cookie。
设置签名 Cookie
response.cookie() 方法创建一个签名 Cookie。签名 Cookie 不能被篡改,但其内容对客户端可见。
import type { HttpContext } from '@adonisjs/core/http'
export default class PreferencesController {
async update({ response, request }: HttpContext) {
const theme = request.input('theme')
/**
* 设置一个 2 小时后过期的签名 Cookie。
* 签名可防止客户端篡改。
*/
response.cookie('theme', theme, {
maxAge: '2h'
})
return { success: true }
}
}
设置加密 Cookie
使用 response.encryptedCookie() 对 Cookie 值进行加密。app.appKey 会对值进行加密,使其对客户端不可读。如果 app key 发生变化,该 Cookie 将无法解密。
import type { HttpContext } from '@adonisjs/core/http'
export default class SessionsController {
async create({ response, auth }: HttpContext) {
/**
* 将敏感的会话数据存储在加密 Cookie 中。
* 该值使用 app.appKey 加密。
*/
response.encryptedCookie('session_data', {
userId: auth.user.id,
loginAt: new Date()
})
return { success: true }
}
}
设置普通 Cookie
response.plainCookie() 方法创建一个 base64 编码的 Cookie,不进行签名或加密。
import type { HttpContext } from '@adonisjs/core/http'
export default class TrackingController {
async view({ response }: HttpContext) {
/**
* 为非敏感的追踪数据设置普通 Cookie
*/
response.plainCookie('last_visit', new Date().toISOString())
return { success: true }
}
}
支持的 Cookie 值类型
Cookie 值可以是以下任何数据类型:string、number、bigInt、boolean、null、object 和 array。
import type { HttpContext } from '@adonisjs/core/http'
export default class CartController {
async add({ response, request }: HttpContext) {
const cartItems = [
{ id: 1, name: 'Product A', quantity: 2 },
{ id: 2, name: 'Product B', quantity: 1 }
]
/**
* 数组和对象会被自动序列化
*/
response.encryptedCookie('cart', cartItems)
return { success: true }
}
}
Cookie 选项
所有 Cookie 方法都接受一个选项对象来控制 Cookie 的行为。这些选项与 config/app.ts 中 http.cookie 块下的配置相匹配。
import type { HttpContext } from '@adonisjs/core/http'
export default class AuthController {
async login({ response, auth }: HttpContext) {
/**
* 设置带有安全选项的认证 Cookie
*/
response.encryptedCookie('auth_token', auth.token, {
domain: '',
path: '/',
maxAge: '2h',
httpOnly: true, // 阻止 JavaScript 访问
secure: true, // 仅通过 HTTPS 发送
sameSite: 'lax', // CSRF 防护
partitioned: false, // 实验性:CHIPS
priority: 'medium' // 实验性:Cookie 优先级
})
return { success: true }
}
}
domain
Cookie 域名。空字符串表示当前域名。
path
Cookie 路径。默认是 /。
maxAge
Cookie 过期时间。可以是 '2h' 这样的持续时间字符串或毫秒数。
httpOnly
阻止 JavaScript 访问 Cookie。
secure
仅通过 HTTPS 连接发送 Cookie。
sameSite
CSRF 防护。默认是 'lax'。
partitioned
实验性:启用 Cookie 分区(CHIPS)。
priority
实验性:给浏览器的 Cookie 优先级提示。
在响应发送后运行操作
response.onFinish() 方法允许你注册在响应已发送给客户端之后执行的回调。这对于清理任务、日志记录,或不应该阻塞响应的操作很有用。
文件下载后清理
一个常见的用例是在文件流式传输给客户端后删除临时文件。
import { unlink } from 'node:fs/promises'
import app from '@adonisjs/core/services/app'
import type { HttpContext } from '@adonisjs/core/http'
export default class FilesController {
async download({ response, params }: HttpContext) {
const filePath = app.makePath(`tmp/exports/${params.id}.zip`)
/**
* 在流式传输文件之前注册清理回调
*/
response.onFinish(async () => {
await unlink(filePath)
})
/**
* 流式传输文件。下载完成后,
* 清理回调将自动运行。
*/
response.download(filePath)
}
}
记录响应指标
你可以使用 onFinish() 来记录分析数据或指标,而无需延迟响应。
import type { HttpContext } from '@adonisjs/core/http'
import type { NextFn } from '@adonisjs/core/types/http'
export default class AnalyticsMiddleware {
async handle({ response, request }: HttpContext, next: NextFn) {
const startTime = Date.now()
/**
* 在响应发送后注册分析日志记录。
* 这不会延迟对客户端响应。
*/
response.onFinish(() => {
const duration = Date.now() - startTime
console.log(`${request.method()} ${request.url()} - ${duration}ms`)
})
await next()
}
}
响应体序列化
Response 类会自动序列化响应体,并根据你发送的数据类型设置适当的内容类型请求头。
当你发送响应时,AdonisJS 执行两个操作:它将值序列化为适合 HTTP 传输的字符串格式,并设置适当的 content-type 和 content-length 请求头。
数组和对象
- Content-Type:
application/json - 序列化行为: 使用安全字符串化(类似
JSON.stringify(),但会移除循环引用并序列化 BigInt)
HTML 片段
- Content-Type:
text/html - 序列化行为: 原样发送(以
<开头的字符串)
数字和布尔值
- Content-Type:
text/plain - 序列化行为: 转换为字符串
Date 实例
- Content-Type:
text/plain - 序列化行为: 使用
toISOString()转换为 ISO 字符串
正则表达式
- Content-Type:
text/plain - 序列化行为: 使用
toString()转换为字符串
Error 对象
- Content-Type:
text/plain - 序列化行为: 使用
toString()转换为字符串
JSONP 响应
- Content-Type:
text/javascript - 序列化行为: 适当地字符串化
其他普通字符串
- Content-Type:
text/plain - 序列化行为: 原样发送
其他数据类型
- Content-Type:
- - 序列化行为: 导致异常
这种自动处理意味着你几乎不需要手动设置内容类型请求头。
import router from '@adonisjs/core/services/router'
router.get('/api/data', async () => {
/**
* 返回 content-type: application/json 的 JSON
* BigInt 值会被安全地序列化为字符串
*/
return {
id: BigInt(9007199254740991),
timestamp: new Date(),
active: true
}
})
router.get('/page', async () => {
/**
* 返回 content-type: text/html 的 HTML
*/
return '<h1>Welcome</h1>'
})
router.get('/text', async () => {
/**
* 返回 content-type: text/plain 的纯文本
*/
return 'Hello, world!'
})
扩展 HttpResponse 类
你可以使用宏(macro)和 getter 向 HttpResponse 类添加自定义方法和属性。这让你能够创建符合应用需求的、可复用的响应辅助函数。
如果你是宏和 getter 概念的新手,请阅读 扩展 AdonisJS 指南。
添加自定义方法
使用 HttpResponse.macro() 为你的应用中所有响应实例添加方法。
import { HttpResponse } from '@adonisjs/core/http'
export default class AppProvider {
async boot() {
/**
* 添加一个自定义方法,以一致的结构发送 API 响应
*/
HttpResponse.macro('api', function (data: any, meta?: Record<string, any>) {
return this.json({
success: true,
data: data,
meta: meta || {}
})
})
/**
* 添加一个用于分页响应的自定义方法
*/
HttpResponse.macro('paginated', function (items: any[], pagination: any) {
return this.json({
data: items,
pagination: {
page: pagination.page,
perPage: pagination.perPage,
total: pagination.total
}
})
})
}
}
扩展 HttpResponse 类类型
添加自定义方法后,你必须通过扩展 HttpResponse 接口来告知 TypeScript 这些方法的存在。否则,在尝试使用自定义方法时会得到类型错误。
import { HttpResponse } from '@adonisjs/core/http'
declare module '@adonisjs/core/http' {
export interface HttpResponse {
/**
* 发送标准化的 API 响应
*/
api(data: any, meta?: Record<string, any>): void
/**
* 发送分页响应
*/
paginated(items: any[], pagination: {
page: number
perPage: number
total: number
}): void
}
}
使用自定义方法
定义后,你的自定义方法在所有响应实例上都可用。
import Post from '#models/post'
import type { HttpContext } from '@adonisjs/core/http'
export default class PostsController {
async index({ response }: HttpContext) {
const posts = await Post.all()
/**
* 使用自定义 api() 方法获得一致的响应
*/
return response.api(posts, {
version: 'v1',
timestamp: new Date()
})
}
async paginated({ response, request }: HttpContext) {
const page = request.input('page', 1)
const posts = await Post.query().paginate(page, 20)
/**
* 使用自定义 paginated() 方法
*/
return response.paginated(posts.all(), posts.getMeta())
}
}