关于 vtex-io-http-routes
为 VTEX IO 服务开发者提供 HTTP 路由设计指导,涵盖路由声明、处理器边界与中间件组织。重点是明确请求响应契约,并将耗时工作留给异步流程。
主要功能
- 01配置 service.json 路由并连接处理器
- 02在处理器边界验证请求并规范响应
- 03通过中间件复用校验与认证逻辑
- 04区分 HTTP 请求与异步批处理工作
适用场景
- 01构建回调或 Webhook 端点
- 02为合作伙伴或后台流程提供集成接口
- 03整理路由处理器与中间件链
- 04检查路由的参数、响应及状态码约定
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ vtex/ skills --skill vtex-io-http-routes安装后在 Claude Code 中输入/vtex-io-http-routes确认已加载。
详细介绍
HTTP 路由与处理器边界
适用场景
当 VTEX IO 服务需要通过 service.json 路由公开明确的 HTTP 端点,并在 node/ 下实现相应的处理器时,请使用此技能。
- 构建回调或 webhook 端点
- 为合作伙伴或后台流程公开集成端点
- 组织路由处理器和中间件链
- 验证参数、查询字符串、标头或请求正文
- 统一响应格式和状态码行为
以下场景请勿使用此技能:
- 调整或优化服务运行时
- 在
manifest.json中决定应用策略 - 设计 GraphQL API
- 建模异步事件或工作进程流程
决策规则
- 当集成需要明确的 URL 合约、webhook 或回调式请求-响应行为时,请使用 HTTP 路由。
- 在 VTEX IO 中,
service.json声明路由 ID、路径和public等公开范围,而 Node 入口点会将这些路由 ID 连接到从node/routes导出的处理器。中间件在代码中组合,而非直接在service.json中声明。 - 路由处理器应保持精简且明确。路由代码应验证输入、调用领域或集成服务,并构造响应。
- 将验证、请求规范化或共享身份验证检查等横切关注点放入中间件,而不是在各个处理器中重复实现。
- 尽可能在处理器边界附近定义路由参数、查询预期和正文结构。
- 对相似的路由系列使用一致的状态码和响应结构。
- 对于 webhook 或回调端点,应遵循调用方文档中对状态码和错误正文的要求,并保持响应简洁且确定,以避免重试情况不明确。
- 为关键路由记录结构化日志或指标,以便诊断故障、延迟和集成健康状况,同时不改变处理器合约。
- 优先使用按有界领域分组的明确路由文件,例如
routes/orders.ts或routes/catalog.ts。 - 将公开路由视为明确的外部合约。未经验证、身份验证预期和响应安全性审查,不要将路由扩展为公开用途。
严格约束
约束:路由处理器必须明确 HTTP 合约
每个路由处理器都必须让请求和响应合约在处理器边界处清晰易懂。不要将必需参数、正文字段或状态码决策隐藏在无关服务的深处。
原因
HTTP 集成依赖可预测的合约。当验证和响应构造隐含存在或分散在各处时,合作伙伴集成会变得脆弱,错误也更难诊断。
检测
如果处理器立即委托其他逻辑,却未验证必需参数、查询值、标头或请求正文结构,请停止操作,并先明确合约。
正确
export async function getOrder(ctx: Context, next: () => Promise<void>) {
const { id } = ctx.vtex.route.params
if (!id) {
ctx.status = 400
ctx.body = { message: 'Missing route param: id' }
return
}
const order = await ctx.clients.partnerApi.getOrder(id)
ctx.status = 200
ctx.body = order
await next()
}
错误
export async function getOrder(ctx: Context) {
ctx.body = await handleOrder(ctx)
}
约束:共享路由关注点必须放在中间件中,而非在每个处理器中重复实现
验证、请求规范化或通用身份验证检查等重复关注点应由中间件实现,并通过路由链组合。
原因
在多个处理器中重复相同检查会造成行为偏差,导致路由行为不一致。中间件让 HTTP 接口更易审查和演进。
检测
如果多个处理器重复执行相同的正文验证、标头检查或上下文准备,请停止操作,并先提取中间件,再添加更多重复逻辑。
正确
export async function validateSignature(ctx: Context, next: () => Promise<void>) {
const signature = ctx.request.header['x-signature']
if (!signature) {
ctx.status = 401
ctx.body = { message: 'Missing signature' }
return
}
await next()
}
错误
export async function routeA(ctx: Context) {
if (!ctx.request.header['x-signature']) {
ctx.status = 401
return
}
}
export async function routeB(ctx: Context) {
if (!ctx.request.header['x-signature']) {
ctx.status = 401
return
}
}
约束:HTTP 路由不应承载应由事件或工作进程处理的异步或批量工作
路由 MUST 保持请求-响应延迟可控。如果路由会触发耗时、容易重试或面向批量的工作,请将这些工作移至异步流程,并让路由仅作为轻量触发器或确认边界。
重要原因
长时间运行的 HTTP 处理程序会导致集成行为不佳、增加超时风险,并造成运行不稳定。VTEX IO 服务应将即时路由契约与后台处理分开。
检测
如果路由执行大量循环、批量导入、繁重的重试工作,或执行无需在响应前完成的工作,请 STOP,并围绕异步处理重新设计流程。
正确
export async function triggerImport(ctx: Context) {
await ctx.clients.importApi.enqueueImport(ctx.request.body)
ctx.status = 202
ctx.body = { accepted: true }
}
错误
export async function triggerImport(ctx: Context) {
for (const item of ctx.request.body.items) {
await ctx.clients.importApi.importItem(item)
}
ctx.status = 200
}
推荐模式
推荐的文件布局:
node/
├── routes/
│ ├── index.ts
│ ├── orders.ts
│ └── webhooks.ts
└── middlewares/
├── validateBody.ts
└── validateSignature.ts
在 VTEX IO 服务中连接路由:
在 VTEX IO 中,service.json 声明路由 ID 和路径,Node 入口点在 new Service(...) 中注册 routes 对象,而 node/routes/index.ts 则将每个路由 ID 映射到最终处理程序。中间件通过代码组合,而不是直接在 service.json 中声明。
{
"routes": {
"orders-get": {
"path": "/_v/orders/:id",
"public": false
},
"reviews-create": {
"path": "/_v/reviews",
"public": false
}
}
}
// node/index.ts
import type { ClientsConfig, RecorderState, ServiceContext } from '@vtex/api'
import { Service } from '@vtex/api'
import { Clients } from './clients'
import routes from './routes'
const clients: ClientsConfig<Clients> = {
implementation: Clients,
options: {
default: {
retries: 2,
timeout: 800,
},
},
}
declare global {
type Context = ServiceContext<Clients, RecorderState>
}
export default new Service<Clients, RecorderState>({
clients,
routes,
})
精简路由模式:
// node/routes/index.ts
import type { RouteHandler } from '@vtex/api'
import { createReview } from './reviews'
import { getOrder } from './orders'
const routes: Record<string, RouteHandler> = {
'orders-get': getOrder,
'reviews-create': createReview,
}
export default routes
// node/routes/orders.ts
import { compose } from 'koa-compose'
import { validateSignature } from '../middlewares/validateSignature'
async function rawGetOrder(ctx: Context, next: () => Promise<void>) {
const { id } = ctx.vtex.route.params
if (!id) {
ctx.status = 400
ctx.body = { message: 'Missing route param: id' }
return
}
const order = await ctx.clients.partnerApi.getOrder(id)
ctx.status = 200
ctx.body = order
await next()
}
export const getOrder = compose([validateSignature, rawGetOrder])
export async function createReview(ctx: Context, next: () => Promise<void>) {
const body = ctx.request.body
if (!body?.productId) {
ctx.status = 400
ctx.body = { message: 'Missing productId' }
return
}
const review = await ctx.clients.reviewApi.createReview(body)
ctx.status = 201
ctx.body = review
await next()
}
将领域逻辑放在服务或集成中,并让路由处理程序负责 HTTP 相关事项,例如验证、状态码、标头和响应格式。
常见故障模式
- 将请求验证隐藏在无关服务中,而不是明确说明路由的预期要求。
- 在多个处理程序中重复相同的身份验证或规范化逻辑,而不是使用中间件。
- 让 HTTP 处理程序执行长时间运行的异步或批量工作。
- 为相似端点返回不一致的状态码或响应格式。
- 未审查信任边界,就扩大路由的公开范围。
审查清单
- HTTP 是此契约的合适暴露方式吗?
- 必需的参数、标头、查询值和请求正文字段是否已在路由边界处验证?
- 是否已将重复的关注点提取到中间件中?
- 处理程序是否精简,并专注于 HTTP 相关事项?
- 是否应将部分工作移至异步事件或工作进程?
相关技能
vtex-io-events-and-workers- 当耗时或容易重试的工作应从 HTTP 处理程序移至异步流程时使用vtex-io-auth-and-policies- 在决定哪些策略或访问规则应保护 HTTP 路由时使用
参考资料
- 服务 - 路由声明和服务暴露
- Node Builder - 后端文件布局和路由实现背景
- 使用 Node 客户端 - 在路由处理程序中使用客户端