vtex-io-client-integration
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseClient Integration & Service Access
客户端集成与服务访问
When this skill applies
本规范适用场景
Use this skill when the main decision is how a VTEX IO backend app should call VTEX services or external APIs through the VTEX IO client system.
- Creating custom clients under
node/clients/ - Choosing between native clients from or
@vtex/apiand a custom client@vtex/clients - Registering clients in and exposing them through
IOClientsctx.clients - Configuring such as retries, timeout, headers, or caching
InstanceOptions - Reviewing backend integrations that currently use raw HTTP libraries
Do not use this skill for:
- deciding the app contract in
manifest.json - structuring or tuning
node/index.tsservice.json - designing GraphQL schema or resolver contracts
- modeling route authorization or security permissions
- building storefront or admin frontend integrations
当你需要决策VTEX IO后端应用如何通过VTEX IO客户端系统调用VTEX服务或外部API时,请使用本规范。
- 在目录下创建自定义客户端
node/clients/ - 选择使用或
@vtex/api提供的原生客户端还是自定义客户端@vtex/clients - 在中注册客户端并通过
IOClients暴露调用能力ctx.clients - 配置参数,如重试次数、超时时间、请求头、缓存策略
InstanceOptions - 审查当前使用原生HTTP库的后端集成实现
以下场景请勿使用本规范:
- 在中定义应用契约
manifest.json - 搭建结构或调优
node/index.ts配置service.json - 设计GraphQL schema或解析器契约
- 设计路由授权或安全权限逻辑
- 构建店铺前端或管理后台前端集成
Decision rules
决策规则
- Prefer native clients from or
@vtex/apiwhen they already cover the target VTEX service. Common examples include clients for catalog, checkout, logistics, and OMS. Write a custom client only when no suitable native client or factory exists.@vtex/clients - Use primarily for non-VTEX external APIs. Avoid using it for VTEX-hosted endpoints such as
ExternalClientor*.myvtex.comwhen a native client in*.vtexcommercestable.com.br,@vtex/clients, or another documented higher-level VTEX client is available or more appropriate.JanusClient - Janus is VTEX's Core Commerce API gateway. Use only when you need to call a VTEX Core Commerce API through Janus and no suitable native client from
JanusClientalready exists.@vtex/clients - Use only for advanced integrations with VTEX IO infrastructure services under explicit documented guidance. In partner apps, prefer higher-level clients and factories such as
InfraClientormasterDatainstead of extendingvbasedirectly.InfraClient - Register every custom or native client in through a
node/clients/index.tsclass that extendsClients.IOClients - Consume integrations through , never by instantiating client classes inside middlewares, resolvers, or event handlers.
ctx.clients - Keep clients focused on transport, request options, endpoint paths, and small response shaping. Keep business rules, authorization decisions, and orchestration outside the client.
- When building custom clients, always rely on the passed by VTEX IO such as
IOContext,account, and available auth tokens instead of hardcoding account names, workspaces, or environment-specific VTEX URLs.workspace - Configure shared in the runtime client config, then use client-specific overrides only when an integration has clearly different needs.
InstanceOptions - Use the option on important client calls so integrations can be tracked and monitored at the client layer, not only at the handler layer.
metric - Keep error normalization close to the client boundary, but avoid hiding relevant HTTP status codes or transport failures that are important for observability and debugging.
- When integrating with external services, confirm that the required outbound policies are declared in the app contract, but keep the detailed policy modeling in auth or app-contract skills.
- In rare migration or legacy scenarios, may temporarily be used against VTEX-hosted endpoints, but treat this as an exception. The long-term goal should be to move toward native clients or the proper documented VTEX client abstractions so routing, authentication, and observability stay consistent.
ExternalClient
Client selection guide:
| Client type | Use when | Avoid when |
|---|---|---|
| calling non-VTEX external APIs | VTEX-hosted APIs that already have a native client or Janus-based abstraction |
| calling VTEX Core Commerce APIs not yet wrapped by | any VTEX service that already has a native client such as Catalog, Checkout, Logistics, or OMS |
| implementing advanced infra-style clients only under explicit documented guidance | general VTEX or external APIs in partner apps |
InstanceOptions heuristics:
- Start with small, explicit client defaults such as and a request
retries: 2betweentimeoutand1000milliseconds.3000 - Use small finite retry values such as to
1for idempotent operations.3 - Avoid automatic retries on non-idempotent operations unless the upstream API explicitly documents safe idempotency behavior.
- Do not use high retry counts to hide upstream instability. Surface repeated failures clearly and handle them intentionally in the business layer.
- Prefer per-client headers and metrics instead of scattering header definitions through handlers.
- Use memory or disk cache options only when repeated reads justify it and the response can be safely reused.
- Keep auth setup inside the client constructor or factory configuration, not duplicated across handlers.
- 当或
@vtex/api的原生客户端已经覆盖目标VTEX服务时优先使用原生客户端,常见的例子包括商品目录、结算、物流、订单管理系统(OMS)的客户端。仅当没有合适的原生客户端或工厂方法时再编写自定义客户端。@vtex/clients - 主要用于非VTEX的外部API调用。如果要对接的是VTEX托管的端点如
ExternalClient或*.myvtex.com,且有可用的*.vtexcommercestable.com.br原生客户端、@vtex/clients或其他官方文档说明的更高层级VTEX客户端时,避免使用JanusClient。ExternalClient - Janus是VTEX的核心电商API网关。仅当你需要通过Janus调用VTEX核心电商API,且没有提供合适的原生客户端时,再使用
@vtex/clients。JanusClient - 仅在有明确的官方文档指导下对接VTEX IO基础设施服务的高级集成场景中使用。合作伙伴开发的应用中优先使用
InfraClient、masterData等更高层级的客户端和工厂方法,不要直接继承vbase。InfraClient - 所有自定义或原生客户端都需要在中通过继承
node/clients/index.ts的IOClients类注册。Clients - 通过调用集成能力,绝对不要在中间件、解析器或事件处理函数中直接实例化客户端类。
ctx.clients - 客户端仅负责传输逻辑、请求配置、端点路径处理和简单的响应格式化,业务规则、授权决策、流程编排逻辑请放在客户端之外实现。
- 构建自定义客户端时,始终依赖VTEX IO传递的参数(如
IOContext、account、可用的鉴权token),不要硬编码账号名称、工作区或环境相关的VTEX URL。workspace - 在运行时客户端配置中设置通用的,仅当某个集成有明确的差异化需求时再使用客户端级别的覆盖配置。
InstanceOptions - 重要的客户端调用请添加配置,这样可以在客户端层面对集成进行跟踪监控,而不局限于处理函数层面。
metric - 在客户端边界附近做错误标准化处理,但不要隐藏对可观测性和调试有重要作用的HTTP状态码或传输失败信息。
- 对接外部服务时,请确认应用契约中已经声明了所需的出站策略,但详细的策略建模请放在授权或应用契约相关规范中处理。
- 在极少数迁移或遗留场景下,可以临时使用对接VTEX托管的端点,但这属于例外情况。长期目标应该是迁移到原生客户端或官方文档推荐的VTEX客户端抽象,保证路由、鉴权、可观测性逻辑的一致性。
ExternalClient
客户端选择指南:
| 客户端类型 | 适用场景 | 避免场景 |
|---|---|---|
| 调用非VTEX的外部API | 已经有原生客户端或基于Janus的抽象层的VTEX托管API |
| 调用还没有被 | 已经有原生客户端的VTEX服务,如商品目录、结算、物流、OMS |
| 仅在有明确官方文档指导下实现基础设施类的高级客户端 | 合作伙伴应用中对接普通VTEX服务或外部API的场景 |
InstanceOptions配置参考:
- 初始使用明确的小数值默认配置,如、请求超时时间设置在1000到3000毫秒之间。
retries: 2 - 幂等操作使用1到3次的有限小重试次数。
- 非幂等操作避免自动重试,除非上游API明确文档说明支持安全幂等。
- 不要用高重试次数掩盖上游服务的不稳定性,应该明确透出重复失败信息,在业务层主动处理。
- 优先在客户端层面统一配置请求头和监控指标,不要在各个处理函数中零散定义请求头。
- 仅当有频繁重复读取请求且响应可以安全复用时,再开启内存或磁盘缓存配置。
- 鉴权配置放在客户端构造函数或工厂配置中实现,不要在各个处理函数中重复编写。
Hard constraints
硬性约束
Constraint: All service-to-service HTTP calls must go through VTEX IO clients
约束:所有服务间HTTP调用必须通过VTEX IO客户端实现
HTTP communication from a VTEX IO backend app MUST go through or clients. Do not use raw libraries such as , , , or for service integrations.
@vtex/api@vtex/clientsaxiosfetchgotnode-fetchWhy this matters
VTEX IO clients provide transport behavior that raw libraries bypass, including authentication context, retries, metrics, caching options, and infrastructure-aware request execution. Raw HTTP calls make integrations harder to observe and easier to misconfigure.
Detection
If you see , , , , or direct ad hoc HTTP code in a VTEX IO backend service, STOP and replace it with an appropriate VTEX IO client pattern.
axiosfetchgotnode-fetchCorrect
typescript
import type { IOContext, InstanceOptions } from '@vtex/api'
import { ExternalClient } from '@vtex/api'
export class WeatherClient extends ExternalClient {
constructor(context: IOContext, options?: InstanceOptions) {
super('https://api.weather.com', context, {
...options,
headers: {
'X-VTEX-Account': context.account,
'X-VTEX-Workspace': context.workspace,
'X-Api-Key': process.env.WEATHER_API_KEY,
...options?.headers,
},
})
}
public getForecast(city: string) {
return this.http.get(`/v1/forecast/${city}`, {
metric: 'weather-forecast',
})
}
}Wrong
typescript
import axios from 'axios'
export async function getForecast(city: string) {
const response = await axios.get(`https://api.weather.com/v1/forecast/${city}`, {
headers: {
'X-Api-Key': process.env.WEATHER_API_KEY,
},
})
return response.data
}VTEX IO后端应用的HTTP通信必须通过或的客户端实现。不要使用、、、等原生HTTP库做服务集成。
@vtex/api@vtex/clientsaxiosfetchgotnode-fetch重要性说明
VTEX IO客户端提供了原生HTTP库没有的传输层能力,包括鉴权上下文传递、重试逻辑、监控指标、缓存配置、感知基础设施的请求执行逻辑。原生HTTP调用会降低集成的可观测性,更容易出现配置错误。
检测方式
如果在VTEX IO后端服务中发现、、、或者直接手写的HTTP代码,请立即停止开发,替换为合适的VTEX IO客户端模式。
axiosfetchgotnode-fetch正确示例
typescript
import type { IOContext, InstanceOptions } from '@vtex/api'
import { ExternalClient } from '@vtex/api'
export class WeatherClient extends ExternalClient {
constructor(context: IOContext, options?: InstanceOptions) {
super('https://api.weather.com', context, {
...options,
headers: {
'X-VTEX-Account': context.account,
'X-VTEX-Workspace': context.workspace,
'X-Api-Key': process.env.WEATHER_API_KEY,
...options?.headers,
},
})
}
public getForecast(city: string) {
return this.http.get(`/v1/forecast/${city}`, {
metric: 'weather-forecast',
})
}
}错误示例
typescript
import axios from 'axios'
export async function getForecast(city: string) {
const response = await axios.get(`https://api.weather.com/v1/forecast/${city}`, {
headers: {
'X-Api-Key': process.env.WEATHER_API_KEY,
},
})
return response.data
}Constraint: Clients must be registered in IOClients and consumed through ctx.clients
约束:客户端必须在IOClients中注册并通过ctx.clients调用
Clients MUST be registered in the class that extends , and middlewares, resolvers, or event handlers MUST access them through .
ClientsIOClientsctx.clientsWhy this matters
The VTEX IO client registry ensures the current request context, options, caching behavior, and instrumentation are applied consistently. Direct instantiation inside handlers bypasses that shared lifecycle and creates fragile integration code.
Detection
If you see inside a middleware, resolver, or event handler, STOP. Move the client into , register it in , and consume it through .
new MyClient(...)node/clients/IOClientsctx.clientsCorrect
typescript
import { IOClients } from '@vtex/api'
import { Catalog } from '@vtex/clients'
export class Clients extends IOClients {
public get catalog() {
return this.getOrSet('catalog', Catalog)
}
}typescript
export async function getSku(ctx: Context) {
const sku = await ctx.clients.catalog.getSkuById(ctx.vtex.route.params.id)
ctx.body = sku
}Wrong
typescript
import { Catalog } from '@vtex/clients'
export async function getSku(ctx: Context) {
const catalog = new Catalog(ctx.vtex, {})
const sku = await catalog.getSkuById(ctx.vtex.route.params.id)
ctx.body = sku
}客户端必须在继承的类中注册,中间件、解析器、事件处理函数必须通过访问客户端。
IOClientsClientsctx.clients重要性说明
VTEX IO客户端注册机制可以保证当前请求上下文、配置、缓存逻辑、埋点逻辑的一致性。在处理函数中直接实例化客户端会绕过共享生命周期逻辑,生成脆弱的集成代码。
检测方式
如果在中间件、解析器、事件处理函数中发现代码,请立即停止开发,将客户端迁移到目录下,在中注册,再通过调用。
new MyClient(...)node/clients/IOClientsctx.clients正确示例
typescript
import { IOClients } from '@vtex/api'
import { Catalog } from '@vtex/clients'
export class Clients extends IOClients {
public get catalog() {
return this.getOrSet('catalog', Catalog)
}
}typescript
export async function getSku(ctx: Context) {
const sku = await ctx.clients.catalog.getSkuById(ctx.vtex.route.params.id)
ctx.body = sku
}错误示例
typescript
import { Catalog } from '@vtex/clients'
export async function getSku(ctx: Context) {
const catalog = new Catalog(ctx.vtex, {})
const sku = await catalog.getSkuById(ctx.vtex.route.params.id)
ctx.body = sku
}Constraint: Choose the narrowest client type that matches the integration boundary
约束:选择最匹配集成边界的最窄范围客户端类型
Each integration MUST use the correct client abstraction for its boundary. Do not default every integration to or when a more specific client type or native package already exists.
ExternalClientJanusClientWhy this matters
The client type communicates intent and shapes how authentication, URLs, and service boundaries are handled. Using the wrong abstraction makes the integration harder to understand and more likely to drift from VTEX IO conventions.
Detection
If the target is a VTEX Core Commerce API, STOP and check whether a native client from or is more appropriate than . If the target is VTEX-hosted, STOP and confirm that there is no more specific documented VTEX client abstraction before defaulting to .
@vtex/clientsJanusClientExternalClientExternalClientCorrect
typescript
import type { IOContext, InstanceOptions } from '@vtex/api'
import { JanusClient } from '@vtex/api'
export class RatesAndBenefitsClient extends JanusClient {
constructor(context: IOContext, options?: InstanceOptions) {
super(context, options)
}
}Wrong
typescript
import type { IOContext, InstanceOptions } from '@vtex/api'
import { ExternalClient } from '@vtex/api'
export class RatesAndBenefitsClient extends ExternalClient {
constructor(context: IOContext, options?: InstanceOptions) {
super(`https://${context.account}.vtexcommercestable.com.br`, context, options)
}
}每个集成必须使用符合其边界的正确客户端抽象。当存在更具体的客户端类型或原生包时,不要所有集成都默认使用或。
ExternalClientJanusClient重要性说明
客户端类型可以传递集成意图,定义鉴权、URL、服务边界的处理方式。使用错误的抽象会降低集成的可理解性,更容易偏离VTEX IO的开发规范。
检测方式
如果对接的是VTEX核心电商API,请先检查的原生客户端或是否比更合适。如果对接的是VTEX托管的服务,请先确认没有更具体的官方VTEX客户端抽象,再默认使用。
@vtex/clientsJanusClientExternalClientExternalClient正确示例
typescript
import type { IOContext, InstanceOptions } from '@vtex/api'
import { JanusClient } from '@vtex/api'
export class RatesAndBenefitsClient extends JanusClient {
constructor(context: IOContext, options?: InstanceOptions) {
super(context, options)
}
}错误示例
typescript
import type { IOContext, InstanceOptions } from '@vtex/api'
import { ExternalClient } from '@vtex/api'
export class RatesAndBenefitsClient extends ExternalClient {
constructor(context: IOContext, options?: InstanceOptions) {
super(`https://${context.account}.vtexcommercestable.com.br`, context, options)
}
}Preferred pattern
推荐模式
Recommended file layout:
text
node/
├── clients/
│ ├── index.ts
│ ├── catalog.ts
│ └── partnerApi.ts
├── middlewares/
│ └── getData.ts
└── index.tsRegister native and custom clients in one place:
typescript
import { IOClients } from '@vtex/api'
import { Catalog } from '@vtex/clients'
import { PartnerApiClient } from './partnerApi'
export class Clients extends IOClients {
public get catalog() {
return this.getOrSet('catalog', Catalog)
}
public get partnerApi() {
return this.getOrSet('partnerApi', PartnerApiClient)
}
}Create custom clients with explicit routes and options:
typescript
import type { IOContext, InstanceOptions } from '@vtex/api'
import { ExternalClient } from '@vtex/api'
export class PartnerApiClient extends ExternalClient {
private routes = {
order: (id: string) => `/orders/${id}`,
}
constructor(context: IOContext, options?: InstanceOptions) {
super('https://partner.example.com', context, {
...options,
retries: 2,
timeout: 2000,
headers: {
'X-VTEX-Account': context.account,
'X-VTEX-Workspace': context.workspace,
...options?.headers,
},
})
}
public getOrder(id: string) {
return this.http.get(this.routes.order(id), {
metric: 'partner-get-order',
})
}
}Wire shared client options in the runtime:
typescript
import type { ClientsConfig } from '@vtex/api'
import { Clients } from './clients'
const clients: ClientsConfig<Clients> = {
implementation: Clients,
options: {
default: {
retries: 2,
timeout: 2000,
},
},
}Use clients from handlers through :
ctx.clientstypescript
export async function getOrder(ctx: Context) {
const order = await ctx.clients.partnerApi.getOrder(ctx.vtex.route.params.id)
ctx.body = order
}If a client file grows too large, split it by bounded integration domains and keep as a small registry.
node/clients/index.ts推荐的文件结构:
text
node/
├── clients/
│ ├── index.ts
│ ├── catalog.ts
│ └── partnerApi.ts
├── middlewares/
│ └── getData.ts
└── index.ts在统一位置注册原生和自定义客户端:
typescript
import { IOClients } from '@vtex/api'
import { Catalog } from '@vtex/clients'
import { PartnerApiClient } from './partnerApi'
export class Clients extends IOClients {
public get catalog() {
return this.getOrSet('catalog', Catalog)
}
public get partnerApi() {
return this.getOrSet('partnerApi', PartnerApiClient)
}
}创建自定义客户端时明确定义路由和配置:
typescript
import type { IOContext, InstanceOptions } from '@vtex/api'
import { ExternalClient } from '@vtex/api'
export class PartnerApiClient extends ExternalClient {
private routes = {
order: (id: string) => `/orders/${id}`,
}
constructor(context: IOContext, options?: InstanceOptions) {
super('https://partner.example.com', context, {
...options,
retries: 2,
timeout: 2000,
headers: {
'X-VTEX-Account': context.account,
'X-VTEX-Workspace': context.workspace,
...options?.headers,
},
})
}
public getOrder(id: string) {
return this.http.get(this.routes.order(id), {
metric: 'partner-get-order',
})
}
}在运行时配置通用客户端参数:
typescript
import type { ClientsConfig } from '@vtex/api'
import { Clients } from './clients'
const clients: ClientsConfig<Clients> = {
implementation: Clients,
options: {
default: {
retries: 2,
timeout: 2000,
},
},
}在处理函数中通过使用客户端:
ctx.clientstypescript
export async function getOrder(ctx: Context) {
const order = await ctx.clients.partnerApi.getOrder(ctx.vtex.route.params.id)
ctx.body = order
}如果单个客户端文件过大,可以按照集成领域拆分,保持作为精简的注册入口。
node/clients/index.tsCommon failure modes
常见错误模式
- Using ,
axios, or other raw HTTP libraries in backend handlers instead of VTEX IO clients.fetch - Instantiating clients directly inside handlers instead of registering them in .
IOClients - Choosing when a native VTEX client or a more specific app client already exists.
ExternalClient - Putting business rules, validation, or orchestration into clients instead of keeping them as transport wrappers.
- Scattering headers, auth setup, and retry settings across handlers instead of centralizing them in the client or shared client config.
- Forgetting the outbound-access policy required for an external integration declared in a custom client.
- 在后端处理函数中使用、
axios或其他原生HTTP库,而非VTEX IO客户端。fetch - 在处理函数中直接实例化客户端,而非在中注册。
IOClients - 当已经有原生VTEX客户端或更具体的应用客户端时,仍然选择使用。
ExternalClient - 在客户端中实现业务规则、校验、流程编排逻辑,而不是仅将客户端作为传输层封装。
- 在各个处理函数中零散配置请求头、鉴权逻辑、重试参数,而不是在客户端或共享客户端配置中统一管理。
- 忘记在自定义客户端对应的外部集成中声明所需的出站访问策略。
Review checklist
审查清单
- Does each integration use the correct VTEX IO client abstraction?
- Are native clients from or
@vtex/apipreferred when available?@vtex/clients - Are clients registered in and consumed through
IOClients?ctx.clients - Are raw HTTP libraries absent from the backend integration code?
- Are retries, timeouts, headers, and metrics configured in the client layer rather than scattered across handlers?
- Are business rules kept out of the client layer?
- 每个集成是否使用了正确的VTEX IO客户端抽象?
- 当或
@vtex/api有可用的原生客户端时是否优先使用?@vtex/clients - 客户端是否在中注册并通过
IOClients调用?ctx.clients - 后端集成代码中是否没有使用原生HTTP库?
- 重试、超时、请求头、监控指标是否在客户端层配置,而非分散在各个处理函数中?
- 业务逻辑是否没有放在客户端层实现?
Reference
参考资料
- Using Node Clients - How to consume clients through
ctx.clients - Developing Clients - How to build custom clients with
@vtex/api - Using VTEX IO clients - How to use VTEX clients for Core Commerce APIs
- Clients - VTEX IO client architecture and native client catalog
- Using Node Clients - 如何通过调用客户端
ctx.clients - Developing Clients - 如何基于构建自定义客户端
@vtex/api - Using VTEX IO clients - 如何使用VTEX客户端对接核心电商API
- Clients - VTEX IO客户端架构和原生客户端目录