vtex-io-http-routes

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

HTTP Routes & Handler Boundaries

HTTP路由与处理函数边界

When this skill applies

本技能适用场景

Use this skill when a VTEX IO service needs to expose explicit HTTP endpoints through
service.json
routes and implement the corresponding handlers under
node/
.
  • Building callback or webhook endpoints
  • Exposing integration endpoints for partners or backoffice flows
  • Structuring route handlers and middleware chains
  • Validating params, query strings, headers, or request bodies
  • Standardizing response shape and status code behavior
Do not use this skill for:
  • sizing or tuning the service runtime
  • deciding app policies in
    manifest.json
  • designing GraphQL APIs
  • modeling async event or worker flows
当VTEX IO服务需要通过
service.json
路由暴露明确的HTTP端点,并在
node/
目录下实现对应处理函数时,使用本技能:
  • 构建回调或webhook端点
  • 为合作伙伴或后台流程暴露集成端点
  • 结构化路由处理函数与中间件链
  • 校验参数、查询字符串、请求头或请求体
  • 统一响应结构与状态码行为
本技能不适用于:
  • 服务运行时的容量规划或调优
  • manifest.json
    中定义应用权限策略
  • 设计GraphQL API
  • 建模异步事件或工作流流程

Decision rules

决策规则

  • Use HTTP routes when the integration needs explicit URL contracts, webhooks, or callback-style request-response behavior.
  • In VTEX IO,
    service.json
    declares route IDs, paths, and exposure such as
    public
    , while the Node entrypoint wires those route IDs to handlers exported from
    node/routes
    . Middlewares are composed in code, not declared directly in
    service.json
    .
  • Keep route handlers small and explicit. Route code should validate input, call domain or integration services, and shape the response.
  • Put cross-cutting concerns such as validation, request normalization, or shared auth checks into middlewares instead of duplicating them across handlers.
  • Define route params, query expectations, and body shape as close as possible to the handler boundary.
  • Use consistent status codes and response structures for similar route families.
  • For webhook or callback endpoints, follow the caller's documented expectations for status codes and error bodies, and keep responses small and deterministic to avoid ambiguous retries.
  • Emit structured logs or metrics for critical routes so failures, latency, and integration health can be diagnosed without changing the handler contract.
  • Prefer explicit route files grouped by bounded domain such as
    routes/orders.ts
    or
    routes/catalog.ts
    .
  • Treat public routes as explicit external contracts. Do not expand a route to public use without reviewing validation, auth expectations, and response safety.
  • 当集成需要明确的URL契约、webhook或回调式请求-响应行为时,使用HTTP路由。
  • 在VTEX IO中,
    service.json
    声明路由ID、路径以及
    public
    等暴露属性,Node入口文件将这些路由ID与
    node/routes
    导出的处理函数关联。中间件在代码中组合,而非直接在
    service.json
    中声明。
  • 保持路由处理函数精简明确。路由代码应负责校验输入、调用业务域或集成服务、格式化响应。
  • 将校验、请求标准化、公共鉴权校验等横切关注点放到中间件中,避免在多个处理函数中重复实现。
  • 尽可能在靠近处理函数边界的位置定义路由参数、查询预期和请求体结构。
  • 同类路由族使用统一的状态码和响应结构。
  • 针对webhook或回调端点,遵循调用方文档中对状态码和错误体的要求,保持响应精简且确定,避免出现歧义导致重试。
  • 为关键路由输出结构化日志或指标,无需修改处理函数契约即可诊断故障、延迟和集成健康度。
  • 优先按限界域分组维护显式路由文件,例如
    routes/orders.ts
    routes/catalog.ts
  • 将公共路由视为明确的外部契约。在未完成校验、鉴权预期和响应安全性审查前,不要将路由开放为公共使用。

Hard constraints

硬约束

Constraint: Route handlers must keep the HTTP contract explicit

约束:路由处理函数必须保证HTTP契约明确

Each route handler MUST make the request and response contract understandable at the handler boundary. Do not hide required params, body fields, or status code decisions deep inside unrelated services.
Why this matters
HTTP integrations depend on predictable contracts. When validation and response shaping are implicit or scattered, partner integrations become fragile and errors become harder to diagnose.
Detection
If the handler delegates immediately without validating required params, query values, headers, or request body shape, STOP and make the contract explicit before proceeding.
Correct
typescript
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()
}
Wrong
typescript
export async function getOrder(ctx: Context) {
  ctx.body = await handleOrder(ctx)
}
每个路由处理函数必须在其边界处清晰呈现请求和响应契约。不要将必填参数、请求体字段或状态码决策隐藏在不相关的服务深处。
重要性
HTTP集成依赖可预测的契约。如果校验和响应格式化逻辑是隐式的或分散的,合作伙伴集成会变得脆弱,错误也更难排查。
检测方式
如果处理函数没有校验必填参数、查询值、请求头或请求体结构就直接委托逻辑,立即停止开发,先明确契约再继续。
正确示例
typescript
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()
}
错误示例
typescript
export async function getOrder(ctx: Context) {
  ctx.body = await handleOrder(ctx)
}

Constraint: Shared route concerns must live in middlewares, not repeated in every handler

约束:路由公共逻辑必须放在中间件中,不得在每个处理函数中重复实现

Repeated concerns such as validation, request normalization, or common auth checks SHOULD be implemented as middlewares and composed through the route chain.
Why this matters
Duplicating the same checks in many handlers creates drift and inconsistent route behavior. Middleware keeps the HTTP surface easier to review and evolve.
Detection
If multiple handlers repeat the same body validation, header checks, or context preparation, STOP and extract a middleware before adding more duplication.
Correct
typescript
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()
}
Wrong
typescript
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暴露层更易审查和迭代。
检测方式
如果多个处理函数重复实现相同的请求体验证、请求头校验或上下文预处理逻辑,立即停止开发,先提取为中间件再继续,避免产生更多重复代码。
正确示例
typescript
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()
}
错误示例
typescript
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
  }
}

Constraint: HTTP routes should not absorb async or batch work that belongs in events or workers

约束:HTTP路由不应承担本该由事件或工作流处理的异步或批量任务

Routes MUST keep request-response latency bounded. If a route triggers expensive, retry-prone, or batch-oriented work, move that work to an async flow and keep the route as a thin trigger or acknowledgment boundary.
Why this matters
Long-running HTTP handlers create poor integration behavior, timeout risk, and operational instability. VTEX IO services should separate immediate route contracts from background processing.
Detection
If a route performs large loops, batch imports, heavy retries, or work that is not required to complete before responding, STOP and redesign the flow around async processing.
Correct
typescript
export async function triggerImport(ctx: Context) {
  await ctx.clients.importApi.enqueueImport(ctx.request.body)
  ctx.status = 202
  ctx.body = { accepted: true }
}
Wrong
typescript
export async function triggerImport(ctx: Context) {
  for (const item of ctx.request.body.items) {
    await ctx.clients.importApi.importItem(item)
  }

  ctx.status = 200
}
路由必须保证请求-响应延迟在可控范围内。如果路由会触发计算成本高、易重试或批量处理类的任务,将这部分逻辑迁移到异步流程中,路由仅作为轻量化触发或确认入口。
重要性
运行时间过长的HTTP处理函数会导致集成体验差、超时风险和运行不稳定。VTEX IO服务应当将即时路由契约和后台处理逻辑分离。
检测方式
如果路由中存在大循环、批量导入、大量重试,或是存在不需要在响应前完成的任务,立即停止开发,基于异步处理重新设计流程。
正确示例
typescript
export async function triggerImport(ctx: Context) {
  await ctx.clients.importApi.enqueueImport(ctx.request.body)
  ctx.status = 202
  ctx.body = { accepted: true }
}
错误示例
typescript
export async function triggerImport(ctx: Context) {
  for (const item of ctx.request.body.items) {
    await ctx.clients.importApi.importItem(item)
  }

  ctx.status = 200
}

Preferred pattern

推荐模式

Recommended file layout:
text
node/
├── routes/
│   ├── index.ts
│   ├── orders.ts
│   └── webhooks.ts
└── middlewares/
    ├── validateBody.ts
    └── validateSignature.ts
Wiring routes in VTEX IO services:
In VTEX IO,
service.json
declares route IDs and paths, the Node entrypoint registers a
routes
object in
new Service(...)
, and
node/routes/index.ts
maps each route ID to the final handler. Middlewares are composed in code, not declared directly in
service.json
.
json
{
  "routes": {
    "orders-get": {
      "path": "/_v/orders/:id",
      "public": false
    },
    "reviews-create": {
      "path": "/_v/reviews",
      "public": false
    }
  }
}
typescript
// 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,
})
Minimal route pattern:
typescript
// 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
typescript
// 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])
typescript
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()
}
Keep domain logic in services or integrations, and keep route handlers responsible for HTTP concerns such as validation, status codes, headers, and response shape.
推荐文件结构:
text
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
中声明。
json
{
  "routes": {
    "orders-get": {
      "path": "/_v/orders/:id",
      "public": false
    },
    "reviews-create": {
      "path": "/_v/reviews",
      "public": false
    }
  }
}
typescript
// 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,
})
最简路由模式:
typescript
// 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
typescript
// 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])
typescript
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相关逻辑。

Common failure modes

常见故障模式

  • Hiding request validation inside unrelated services instead of making route expectations explicit.
  • Repeating the same auth or normalization logic in many handlers instead of using middleware.
  • Letting HTTP handlers perform long-running async or batch work.
  • Returning inconsistent status codes or response shapes for similar endpoints.
  • Expanding a route to public exposure without reviewing its trust boundary.
  • 将请求校验逻辑隐藏在不相关的服务中,没有明确声明路由预期
  • 在多个处理函数中重复实现相同的鉴权或标准化逻辑,没有使用中间件
  • 让HTTP处理函数执行长时间运行的异步或批量任务
  • 同类接口返回不一致的状态码或响应结构
  • 未审查信任边界就将路由开放为公共访问

Review checklist

审查清单

  • Is HTTP the right exposure mechanism for this contract?
  • Are required params, headers, query values, and body fields validated at the route boundary?
  • Are repeated concerns factored into middlewares?
  • Does the handler stay small and focused on HTTP concerns?
  • Should any part of the work move to async events or workers instead?
  • HTTP是当前契约最合适的暴露机制吗?
  • 必填参数、请求头、查询值和请求体字段都在路由边界完成校验了吗?
  • 重复逻辑已经提炼到中间件中了吗?
  • 处理函数是否保持精简,仅聚焦HTTP相关逻辑?
  • 是否有部分逻辑应当迁移到异步事件或工作流中?

Related skills

相关技能

  • vtex-io-events-and-workers
    - Use when expensive or retry-prone work should move out of HTTP handlers into async flows
  • vtex-io-auth-and-policies
    - Use when deciding which policies or access rules should protect HTTP routes
  • vtex-io-events-and-workers
    - 当需要将计算成本高或易重试的逻辑从HTTP处理函数迁移到异步流程时使用
  • vtex-io-auth-and-policies
    - 当需要确定HTTP路由的权限策略或访问规则时使用

Reference

参考