Documentation
Feedback
Guides
App Development

App Development
App Development

Developing Clients

Learn how to develop custom Clients for seamless communication with other services.

If you need to integrate your VTEX IO app with external services that aren't covered by VTEX IO's native Clients, creating custom Clients can be a powerful solution. Custom Clients extend the functionality of VTEX IO Client types, offering benefits like caching and versioning. This guide will walk you through the process of creating custom Clients for your VTEX IO apps.

Note that direct communication with APIs is generally discouraged in favor of implementing a dedicated Client. By creating custom Clients, you can extend the capabilities of your VTEX IO applications, enabling seamless integration with a wide range of services.

The diagram below illustrates how an app works with a Client to an external service:

As you develop your custom Client, you will define methods within it. These Client methods are functions responsible for handling the logic needed to execute specific actions. They facilitate interactions with services and APIs, managing requests and responses effectively.

Once you've created your custom Client, you can seamlessly integrate it into your VTEX IO app to execute the implemented methods. While you have the flexibility to incorporate data-handling logic within your Client's methods (e.g., field mapping or data filtering), it's essential to keep the core responsibilities of the Client well-organized.

Before you begin

Before diving into Client development, make sure you have the following prerequisites in place:

  • VTEX IO development workspace: Ensure you have a VTEX IO development workspace set up. For more information, refer to Creating a Development workspace.
  • TypeScript Familiarity: This guide assumes you have a basic understanding of TypeScript, as we'll be using the node Builder to develop with TypeScript. For more information, refer to TypeScript's official documentation.
  • Understanding of Clients: Clients play a crucial role in facilitating interactions between your application and both external and internal services. For more information, refer to Clients.

Instructions

In this guide, we will create a custom Client example for communicating with GitHub APIs. We'll implement the following methods within our Client:

  • getRepo: Fetches a GitHub repository given the owner and repository name.
  • getUser: Retrieves GitHub user data based on a specified GitHub username.
  • createTag: Creates a new tag on GitHub, taking into account the repository owner, repository, and tag data.
  • searchRepos: Searches for GitHub repositories using a specified query.

Step 1 - Setting up your VTEX IO app

  1. Start a new VTEX IO app using the node builder and open the project using your preferred code editor.

  2. Open a terminal and navigate to the node folder of your project.


    _10
    cd node

  3. Install the @vtex/api and @octokit/rest packages by running the following command:


    _10
    yarn add @vtex/api @octokit/rest

  4. Open the app's manifest.json file and add the necessary Policies. Policies are a set of permissions granted to an app, allowing or forbidding it to execute a given set of actions, such as making requests to an external resource. For this example, we will use the outbound-access policy to communicate with GitHub APIs (api.github.com).

    manifest.json

    _10
    "policies": [
    _10
    {
    _10
    "name": "outbound-access",
    _10
    "attrs": {
    _10
    "host": "api.github.com",
    _10
    "path": "*"
    _10
    }
    _10
    },
    _10
    ]

    Note that incorrect policies may result in request blocking.

Step 2 - Creating a custom Client

  1. Create a folder named clients inside the node directory.
  2. Create a TypeScript file for your Client in the node/clients directory. Choose a name that easily identifies your Client (e.g., github.ts).
  3. Implement the TypeScript class that extends the appropriate Client type from @vtex/api. In this example, we will create the GithubClient. Also, since we are communicating with GitHub's external APIs, we will use ExternalClient.
  4. Use InstanceOptions as needed, configuring options such as authentication, timeout, caching, and more based on your requirements.
node/clients/github.ts

_17
import type { IOContext, InstanceOptions } from '@vtex/api'
_17
import { ExternalClient } from '@vtex/api'
_17
_17
export default class GithubClient extends ExternalClient {
_17
constructor(context: IOContext, options?: InstanceOptions) {
_17
super('http://api.github.com', context, {
_17
...options,
_17
retries: 2,
_17
headers: {
_17
Accept: 'application/vnd.github.machine-man-preview+json',
_17
'x-vtex-use-https': 'true'
_17
}
_17
});
_17
}
_17
_17
// Add the Client-related methods here
_17
}

Ensure to export the Client from its module, either default or named export.

Step 3 - Implementing Client methods

Within your Client TypeScript file, implement the methods for your Client. You can leverage the methods provided by the HttpClient to build your own methods. For example:

node/clients/github.ts

_17
import type { RestEndpointMethodTypes } from '@octokit/rest'
_17
_17
...
_17
_17
public getRepo({ owner, repo }: GitRepo, token?: string) {
_17
return this.http.get<RestEndpointMethodTypes['repos']['get']['response']>(
_17
this.routes.repo({ owner, repo }),
_17
{
_17
metric: 'git-repo-get',
_17
headers: {
_17
...this.options?.headers,
_17
Accept: 'application/vnd.github.nebula-preview+json',
_17
...(token && { Authorization: `token ${token}` }),
_17
},
_17
}
_17
)
_17
}

Below we have the complete code example:

node/github.ts

_85
import type { IOContext, InstanceOptions } from '@vtex/api'
_85
import { ExternalClient } from '@vtex/api'
_85
import type { RestEndpointMethodTypes } from '@octokit/rest'
_85
_85
interface GitRepo {
_85
owner: string
_85
repo: string
_85
}
_85
_85
export default class GithubClient extends ExternalClient {
_85
private routes = {
_85
repo: ({ owner, repo }: GitRepo) => `/repos/${owner}/${repo}`,
_85
user: ({ username }: { username: string }) => `/users/${username}`,
_85
tags: ({ owner, repo }: GitRepo) => `/repos/${owner}/${repo}/git/tags`,
_85
searchRepos: () => '/search/repositories',
_85
}
_85
_85
constructor(context: IOContext, options?: InstanceOptions) {
_85
super('http://api.github.com', context, {
_85
...options,
_85
retries: 2,
_85
headers: {
_85
Accept: 'application/vnd.github.machine-man-preview+json',
_85
'x-vtex-use-https': 'true',
_85
},
_85
})
_85
}
_85
_85
public getRepo({ owner, repo }: GitRepo, token?: string) {
_85
return this.http.get<RestEndpointMethodTypes['repos']['get']['response']>(
_85
this.routes.repo({ owner, repo }),
_85
{
_85
metric: 'git-repo-get',
_85
headers: {
_85
...this.options?.headers,
_85
Accept: 'application/vnd.github.nebula-preview+json',
_85
...(token && { Authorization: `token ${token}` }),
_85
},
_85
}
_85
)
_85
}
_85
_85
public async getUser({ username }: { username: string }) {
_85
const response = await this.http.getRaw<
_85
RestEndpointMethodTypes['users']['getByUsername']['response']
_85
>(this.routes.user({ username }), { metric: 'git-user-get' })
_85
_85
return response.data
_85
}
_85
_85
public createTag(
_85
{
_85
owner,
_85
repo,
_85
...tagData
_85
}: {
_85
owner: string
_85
repo: string
_85
} & RestEndpointMethodTypes['git']['createTag']['parameters'],
_85
token?: string
_85
) {
_85
console.info('Tag data:', tagData)
_85
_85
return this.http.post<RestEndpointMethodTypes['git']['createTag']>(
_85
this.routes.tags({ owner, repo }),
_85
tagData,
_85
{
_85
metric: 'git-create-tag',
_85
headers: {
_85
...this.options?.headers,
_85
...(token && { Authorization: `token ${token}` })
_85
},
_85
}
_85
)
_85
}
_85
_85
public searchRepos({ query }: { query: string }) {
_85
return this.http.get<
_85
RestEndpointMethodTypes['search']['repos']['response']
_85
>(this.routes.searchRepos(), {
_85
metric: 'git-search-repos',
_85
params: { q: query },
_85
})
_85
}
_85
}

VTEX API core types:

  • IOContext: Contains the VTEX IO context data, such as account, workspace, locale, authentication token, etc.
  • InstanceOptions: The options for configuring your HttpClient instance.
  • ExternalClient: The base Client class for communicating with external APIs.

Type of the base Client that the custom Client extends from. Used to connect with external APIs.

node/github.ts

_85
import type { IOContext, InstanceOptions } from '@vtex/api'
_85
import { ExternalClient } from '@vtex/api'
_85
import type { RestEndpointMethodTypes } from '@octokit/rest'
_85
_85
interface GitRepo {
_85
owner: string
_85
repo: string
_85
}
_85
_85
export default class GithubClient extends ExternalClient {
_85
private routes = {
_85
repo: ({ owner, repo }: GitRepo) => `/repos/${owner}/${repo}`,
_85
user: ({ username }: { username: string }) => `/users/${username}`,
_85
tags: ({ owner, repo }: GitRepo) => `/repos/${owner}/${repo}/git/tags`,
_85
searchRepos: () => '/search/repositories',
_85
}
_85
_85
constructor(context: IOContext, options?: InstanceOptions) {
_85
super('http://api.github.com', context, {
_85
...options,
_85
retries: 2,
_85
headers: {
_85
Accept: 'application/vnd.github.machine-man-preview+json',
_85
'x-vtex-use-https': 'true',
_85
},
_85
})
_85
}
_85
_85
public getRepo({ owner, repo }: GitRepo, token?: string) {
_85
return this.http.get<RestEndpointMethodTypes['repos']['get']['response']>(
_85
this.routes.repo({ owner, repo }),
_85
{
_85
metric: 'git-repo-get',
_85
headers: {
_85
...this.options?.headers,
_85
Accept: 'application/vnd.github.nebula-preview+json',
_85
...(token && { Authorization: `token ${token}` }),
_85
},
_85
}
_85
)
_85
}
_85
_85
public async getUser({ username }: { username: string }) {
_85
const response = await this.http.getRaw<
_85
RestEndpointMethodTypes['users']['getByUsername']['response']
_85
>(this.routes.user({ username }), { metric: 'git-user-get' })
_85
_85
return response.data
_85
}
_85
_85
public createTag(
_85
{
_85
owner,
_85
repo,
_85
...tagData
_85
}: {
_85
owner: string
_85
repo: string
_85
} & RestEndpointMethodTypes['git']['createTag']['parameters'],
_85
token?: string
_85
) {
_85
console.info('Tag data:', tagData)
_85
_85
return this.http.post<RestEndpointMethodTypes['git']['createTag']>(
_85
this.routes.tags({ owner, repo }),
_85
tagData,
_85
{
_85
metric: 'git-create-tag',
_85
headers: {
_85
...this.options?.headers,
_85
...(token && { Authorization: `token ${token}` })
_85
},
_85
}
_85
)
_85
}
_85
_85
public searchRepos({ query }: { query: string }) {
_85
return this.http.get<
_85
RestEndpointMethodTypes['search']['repos']['response']
_85
>(this.routes.searchRepos(), {
_85
metric: 'git-search-repos',
_85
params: { q: query },
_85
})
_85
}
_85
}

A variable to store a map of routes for each method implemented in the Client. This structure is not mandatory, but it makes the code more readable when calling the HttpClient methods.

node/github.ts

_85
import type { IOContext, InstanceOptions } from '@vtex/api'
_85
import { ExternalClient } from '@vtex/api'
_85
import type { RestEndpointMethodTypes } from '@octokit/rest'
_85
_85
interface GitRepo {
_85
owner: string
_85
repo: string
_85
}
_85
_85
export default class GithubClient extends ExternalClient {
_85
private routes = {
_85
repo: ({ owner, repo }: GitRepo) => `/repos/${owner}/${repo}`,
_85
user: ({ username }: { username: string }) => `/users/${username}`,
_85
tags: ({ owner, repo }: GitRepo) => `/repos/${owner}/${repo}/git/tags`,
_85
searchRepos: () => '/search/repositories',
_85
}
_85
_85
constructor(context: IOContext, options?: InstanceOptions) {
_85
super('http://api.github.com', context, {
_85
...options,
_85
retries: 2,
_85
headers: {
_85
Accept: 'application/vnd.github.machine-man-preview+json',
_85
'x-vtex-use-https': 'true',
_85
},
_85
})
_85
}
_85
_85
public getRepo({ owner, repo }: GitRepo, token?: string) {
_85
return this.http.get<RestEndpointMethodTypes['repos']['get']['response']>(
_85
this.routes.repo({ owner, repo }),
_85
{
_85
metric: 'git-repo-get',
_85
headers: {
_85
...this.options?.headers,
_85
Accept: 'application/vnd.github.nebula-preview+json',
_85
...(token && { Authorization: `token ${token}` }),
_85
},
_85
}
_85
)
_85
}
_85
_85
public async getUser({ username }: { username: string }) {
_85
const response = await this.http.getRaw<
_85
RestEndpointMethodTypes['users']['getByUsername']['response']
_85
>(this.routes.user({ username }), { metric: 'git-user-get' })
_85
_85
return response.data
_85
}
_85
_85
public createTag(
_85
{
_85
owner,
_85
repo,
_85
...tagData
_85
}: {
_85
owner: string
_85
repo: string
_85
} & RestEndpointMethodTypes['git']['createTag']['parameters'],
_85
token?: string
_85
) {
_85
console.info('Tag data:', tagData)
_85
_85
return this.http.post<RestEndpointMethodTypes['git']['createTag']>(
_85
this.routes.tags({ owner, repo }),
_85
tagData,
_85
{
_85
metric: 'git-create-tag',
_85
headers: {
_85
...this.options?.headers,
_85
...(token && { Authorization: `token ${token}` })
_85
},
_85
}
_85
)
_85
}
_85
_85
public searchRepos({ query }: { query: string }) {
_85
return this.http.get<
_85
RestEndpointMethodTypes['search']['repos']['response']
_85
>(this.routes.searchRepos(), {
_85
metric: 'git-search-repos',
_85
params: { q: query },
_85
})
_85
}
_85
}

The baseUrl argument in super must be a URL starting with http://. For a secure connection, set the header 'x-vtex-use-https' to 'true'. This header changes the request to HTTPS when it leaves the VTEX infrastructure.

node/github.ts

_85
import type { IOContext, InstanceOptions } from '@vtex/api'
_85
import { ExternalClient } from '@vtex/api'
_85
import type { RestEndpointMethodTypes } from '@octokit/rest'
_85
_85
interface GitRepo {
_85
owner: string
_85
repo: string
_85
}
_85
_85
export default class GithubClient extends ExternalClient {
_85
private routes = {
_85
repo: ({ owner, repo }: GitRepo) => `/repos/${owner}/${repo}`,
_85
user: ({ username }: { username: string }) => `/users/${username}`,
_85
tags: ({ owner, repo }: GitRepo) => `/repos/${owner}/${repo}/git/tags`,
_85
searchRepos: () => '/search/repositories',
_85
}
_85
_85
constructor(context: IOContext, options?: InstanceOptions) {
_85
super('http://api.github.com', context, {
_85
...options,
_85
retries: 2,
_85
headers: {
_85
Accept: 'application/vnd.github.machine-man-preview+json',
_85
'x-vtex-use-https': 'true',
_85
},
_85
})
_85
}
_85
_85
public getRepo({ owner, repo }: GitRepo, token?: string) {
_85
return this.http.get<RestEndpointMethodTypes['repos']['get']['response']>(
_85
this.routes.repo({ owner, repo }),
_85
{
_85
metric: 'git-repo-get',
_85
headers: {
_85
...this.options?.headers,
_85
Accept: 'application/vnd.github.nebula-preview+json',
_85
...(token && { Authorization: `token ${token}` }),
_85
},
_85
}
_85
)
_85
}
_85
_85
public async getUser({ username }: { username: string }) {
_85
const response = await this.http.getRaw<
_85
RestEndpointMethodTypes['users']['getByUsername']['response']
_85
>(this.routes.user({ username }), { metric: 'git-user-get' })
_85
_85
return response.data
_85
}
_85
_85
public createTag(
_85
{
_85
owner,
_85
repo,
_85
...tagData
_85
}: {
_85
owner: string
_85
repo: string
_85
} & RestEndpointMethodTypes['git']['createTag']['parameters'],
_85
token?: string
_85
) {
_85
console.info('Tag data:', tagData)
_85
_85
return this.http.post<RestEndpointMethodTypes['git']['createTag']>(
_85
this.routes.tags({ owner, repo }),
_85
tagData,
_85
{
_85
metric: 'git-create-tag',
_85
headers: {
_85
...this.options?.headers,
_85
...(token && { Authorization: `token ${token}` })
_85
},
_85
}
_85
)
_85
}
_85
_85
public searchRepos({ query }: { query: string }) {
_85
return this.http.get<
_85
RestEndpointMethodTypes['search']['repos']['response']
_85
>(this.routes.searchRepos(), {
_85
metric: 'git-search-repos',
_85
params: { q: query },
_85
})
_85
}
_85
}

Configuration options for each request of this Client. It extends AxiosRequestConfig, adding some IO-specific options. In this example, we are setting the number of retries, the Accept header to indicate the desired format, and the x-vtex-use-https header to force the use of HTTPS.

node/github.ts

_85
import type { IOContext, InstanceOptions } from '@vtex/api'
_85
import { ExternalClient } from '@vtex/api'
_85
import type { RestEndpointMethodTypes } from '@octokit/rest'
_85
_85
interface GitRepo {
_85
owner: string
_85
repo: string
_85
}
_85
_85
export default class GithubClient extends ExternalClient {
_85
private routes = {
_85
repo: ({ owner, repo }: GitRepo) => `/repos/${owner}/${repo}`,
_85
user: ({ username }: { username: string }) => `/users/${username}`,
_85
tags: ({ owner, repo }: GitRepo) => `/repos/${owner}/${repo}/git/tags`,
_85
searchRepos: () => '/search/repositories',
_85
}
_85
_85
constructor(context: IOContext, options?: InstanceOptions) {
_85
super('http://api.github.com', context, {
_85
...options,
_85
retries: 2,
_85
headers: {
_85
Accept: 'application/vnd.github.machine-man-preview+json',
_85
'x-vtex-use-https': 'true',
_85
},
_85
})
_85
}
_85
_85
public getRepo({ owner, repo }: GitRepo, token?: string) {
_85
return this.http.get<RestEndpointMethodTypes['repos']['get']['response']>(
_85
this.routes.repo({ owner, repo }),
_85
{
_85
metric: 'git-repo-get',
_85
headers: {
_85
...this.options?.headers,
_85
Accept: 'application/vnd.github.nebula-preview+json',
_85
...(token && { Authorization: `token ${token}` }),
_85
},
_85
}
_85
)
_85
}
_85
_85
public async getUser({ username }: { username: string }) {
_85
const response = await this.http.getRaw<
_85
RestEndpointMethodTypes['users']['getByUsername']['response']
_85
>(this.routes.user({ username }), { metric: 'git-user-get' })
_85
_85
return response.data
_85
}
_85
_85
public createTag(
_85
{
_85
owner,
_85
repo,
_85
...tagData
_85
}: {
_85
owner: string
_85
repo: string
_85
} & RestEndpointMethodTypes['git']['createTag']['parameters'],
_85
token?: string
_85
) {
_85
console.info('Tag data:', tagData)
_85
_85
return this.http.post<RestEndpointMethodTypes['git']['createTag']>(
_85
this.routes.tags({ owner, repo }),
_85
tagData,
_85
{
_85
metric: 'git-create-tag',
_85
headers: {
_85
...this.options?.headers,
_85
...(token && { Authorization: `token ${token}` })
_85
},
_85
}
_85
)
_85
}
_85
_85
public searchRepos({ query }: { query: string }) {
_85
return this.http.get<
_85
RestEndpointMethodTypes['search']['repos']['response']
_85
>(this.routes.searchRepos(), {
_85
metric: 'git-search-repos',
_85
params: { q: query },
_85
})
_85
}
_85
}

The metric parameter gives a name to identify each HTTP call for analytics purposes.

node/github.ts

_85
import type { IOContext, InstanceOptions } from '@vtex/api'
_85
import { ExternalClient } from '@vtex/api'
_85
import type { RestEndpointMethodTypes } from '@octokit/rest'
_85
_85
interface GitRepo {
_85
owner: string
_85
repo: string
_85
}
_85
_85
export default class GithubClient extends ExternalClient {
_85
private routes = {
_85
repo: ({ owner, repo }: GitRepo) => `/repos/${owner}/${repo}`,
_85
user: ({ username }: { username: string }) => `/users/${username}`,
_85
tags: ({ owner, repo }: GitRepo) => `/repos/${owner}/${repo}/git/tags`,
_85
searchRepos: () => '/search/repositories',
_85
}
_85
_85
constructor(context: IOContext, options?: InstanceOptions) {
_85
super('http://api.github.com', context, {
_85
...options,
_85
retries: 2,
_85
headers: {
_85
Accept: 'application/vnd.github.machine-man-preview+json',
_85
'x-vtex-use-https': 'true',
_85
},
_85
})
_85
}
_85
_85
public getRepo({ owner, repo }: GitRepo, token?: string) {
_85
return this.http.get<RestEndpointMethodTypes['repos']['get']['response']>(
_85
this.routes.repo({ owner, repo }),
_85
{
_85
metric: 'git-repo-get',
_85
headers: {
_85
...this.options?.headers,
_85
Accept: 'application/vnd.github.nebula-preview+json',
_85
...(token && { Authorization: `token ${token}` }),
_85
},
_85
}
_85
)
_85
}
_85
_85
public async getUser({ username }: { username: string }) {
_85
const response = await this.http.getRaw<
_85
RestEndpointMethodTypes['users']['getByUsername']['response']
_85
>(this.routes.user({ username }), { metric: 'git-user-get' })
_85
_85
return response.data
_85
}
_85
_85
public createTag(
_85
{
_85
owner,
_85
repo,
_85
...tagData
_85
}: {
_85
owner: string
_85
repo: string
_85
} & RestEndpointMethodTypes['git']['createTag']['parameters'],
_85
token?: string
_85
) {
_85
console.info('Tag data:', tagData)
_85
_85
return this.http.post<RestEndpointMethodTypes['git']['createTag']>(
_85
this.routes.tags({ owner, repo }),
_85
tagData,
_85
{
_85
metric: 'git-create-tag',
_85
headers: {
_85
...this.options?.headers,
_85
...(token && { Authorization: `token ${token}` })
_85
},
_85
}
_85
)
_85
}
_85
_85
public searchRepos({ query }: { query: string }) {
_85
return this.http.get<
_85
RestEndpointMethodTypes['search']['repos']['response']
_85
>(this.routes.searchRepos(), {
_85
metric: 'git-search-repos',
_85
params: { q: query },
_85
})
_85
}
_85
}

The getRaw method retrieves additional information about the response, such as headers. The get method retrieves only the response body.

node/github.ts

_85
import type { IOContext, InstanceOptions } from '@vtex/api'
_85
import { ExternalClient } from '@vtex/api'
_85
import type { RestEndpointMethodTypes } from '@octokit/rest'
_85
_85
interface GitRepo {
_85
owner: string
_85
repo: string
_85
}
_85
_85
export default class GithubClient extends ExternalClient {
_85
private routes = {
_85
repo: ({ owner, repo }: GitRepo) => `/repos/${owner}/${repo}`,
_85
user: ({ username }: { username: string }) => `/users/${username}`,
_85
tags: ({ owner, repo }: GitRepo) => `/repos/${owner}/${repo}/git/tags`,
_85
searchRepos: () => '/search/repositories',
_85
}
_85
_85
constructor(context: IOContext, options?: InstanceOptions) {
_85
super('http://api.github.com', context, {
_85
...options,
_85
retries: 2,
_85
headers: {
_85
Accept: 'application/vnd.github.machine-man-preview+json',
_85
'x-vtex-use-https': 'true',
_85
},
_85
})
_85
}
_85
_85
public getRepo({ owner, repo }: GitRepo, token?: string) {
_85
return this.http.get<RestEndpointMethodTypes['repos']['get']['response']>(
_85
this.routes.repo({ owner, repo }),
_85
{
_85
metric: 'git-repo-get',
_85
headers: {
_85
...this.options?.headers,
_85
Accept: 'application/vnd.github.nebula-preview+json',
_85
...(token && { Authorization: `token ${token}` }),
_85
},
_85
}
_85
)
_85
}
_85
_85
public async getUser({ username }: { username: string }) {
_85
const response = await this.http.getRaw<
_85
RestEndpointMethodTypes['users']['getByUsername']['response']
_85
>(this.routes.user({ username }), { metric: 'git-user-get' })
_85
_85
return response.data
_85
}
_85
_85
public createTag(
_85
{
_85
owner,
_85
repo,
_85
...tagData
_85
}: {
_85
owner: string
_85
repo: string
_85
} & RestEndpointMethodTypes['git']['createTag']['parameters'],
_85
token?: string
_85
) {
_85
console.info('Tag data:', tagData)
_85
_85
return this.http.post<RestEndpointMethodTypes['git']['createTag']>(
_85
this.routes.tags({ owner, repo }),
_85
tagData,
_85
{
_85
metric: 'git-create-tag',
_85
headers: {
_85
...this.options?.headers,
_85
...(token && { Authorization: `token ${token}` })
_85
},
_85
}
_85
)
_85
}
_85
_85
public searchRepos({ query }: { query: string }) {
_85
return this.http.get<
_85
RestEndpointMethodTypes['search']['repos']['response']
_85
>(this.routes.searchRepos(), {
_85
metric: 'git-search-repos',
_85
params: { q: query },
_85
})
_85
}
_85
}

The query parameters are passed using this format, resulting in ?q={{query}} being appended to the request's URL.

node/github.ts

_85
import type { IOContext, InstanceOptions } from '@vtex/api'
_85
import { ExternalClient } from '@vtex/api'
_85
import type { RestEndpointMethodTypes } from '@octokit/rest'
_85
_85
interface GitRepo {
_85
owner: string
_85
repo: string
_85
}
_85
_85
export default class GithubClient extends ExternalClient {
_85
private routes = {
_85
repo: ({ owner, repo }: GitRepo) => `/repos/${owner}/${repo}`,
_85
user: ({ username }: { username: string }) => `/users/${username}`,
_85
tags: ({ owner, repo }: GitRepo) => `/repos/${owner}/${repo}/git/tags`,
_85
searchRepos: () => '/search/repositories',
_85
}
_85
_85
constructor(context: IOContext, options?: InstanceOptions) {
_85
super('http://api.github.com', context, {
_85
...options,
_85
retries: 2,
_85
headers: {
_85
Accept: 'application/vnd.github.machine-man-preview+json',
_85
'x-vtex-use-https': 'true',
_85
},
_85
})
_85
}
_85
_85
public getRepo({ owner, repo }: GitRepo, token?: string) {
_85
return this.http.get<RestEndpointMethodTypes['repos']['get']['response']>(
_85
this.routes.repo({ owner, repo }),
_85
{
_85
metric: 'git-repo-get',
_85
headers: {
_85
...this.options?.headers,
_85
Accept: 'application/vnd.github.nebula-preview+json',
_85
...(token && { Authorization: `token ${token}` }),
_85
},
_85
}
_85
)
_85
}
_85
_85
public async getUser({ username }: { username: string }) {
_85
const response = await this.http.getRaw<
_85
RestEndpointMethodTypes['users']['getByUsername']['response']
_85
>(this.routes.user({ username }), { metric: 'git-user-get' })
_85
_85
return response.data
_85
}
_85
_85
public createTag(
_85
{
_85
owner,
_85
repo,
_85
...tagData
_85
}: {
_85
owner: string
_85
repo: string
_85
} & RestEndpointMethodTypes['git']['createTag']['parameters'],
_85
token?: string
_85
) {
_85
console.info('Tag data:', tagData)
_85
_85
return this.http.post<RestEndpointMethodTypes['git']['createTag']>(
_85
this.routes.tags({ owner, repo }),
_85
tagData,
_85
{
_85
metric: 'git-create-tag',
_85
headers: {
_85
...this.options?.headers,
_85
...(token && { Authorization: `token ${token}` })
_85
},
_85
}
_85
)
_85
}
_85
_85
public searchRepos({ query }: { query: string }) {
_85
return this.http.get<
_85
RestEndpointMethodTypes['search']['repos']['response']
_85
>(this.routes.searchRepos(), {
_85
metric: 'git-search-repos',
_85
params: { q: query },
_85
})
_85
}
_85
}

VTEX API core types:

  • IOContext: Contains the VTEX IO context data, such as account, workspace, locale, authentication token, etc.
  • InstanceOptions: The options for configuring your HttpClient instance.
  • ExternalClient: The base Client class for communicating with external APIs.

Type of the base Client that the custom Client extends from. Used to connect with external APIs.

A variable to store a map of routes for each method implemented in the Client. This structure is not mandatory, but it makes the code more readable when calling the HttpClient methods.

The baseUrl argument in super must be a URL starting with http://. For a secure connection, set the header 'x-vtex-use-https' to 'true'. This header changes the request to HTTPS when it leaves the VTEX infrastructure.

Configuration options for each request of this Client. It extends AxiosRequestConfig, adding some IO-specific options. In this example, we are setting the number of retries, the Accept header to indicate the desired format, and the x-vtex-use-https header to force the use of HTTPS.

The metric parameter gives a name to identify each HTTP call for analytics purposes.

The getRaw method retrieves additional information about the response, such as headers. The get method retrieves only the response body.

The query parameters are passed using this format, resulting in ?q={{query}} being appended to the request's URL.

node/github.ts

_85
import type { IOContext, InstanceOptions } from '@vtex/api'
_85
import { ExternalClient } from '@vtex/api'
_85
import type { RestEndpointMethodTypes } from '@octokit/rest'
_85
_85
interface GitRepo {
_85
owner: string
_85
repo: string
_85
}
_85
_85
export default class GithubClient extends ExternalClient {
_85
private routes = {
_85
repo: ({ owner, repo }: GitRepo) => `/repos/${owner}/${repo}`,
_85
user: ({ username }: { username: string }) => `/users/${username}`,
_85
tags: ({ owner, repo }: GitRepo) => `/repos/${owner}/${repo}/git/tags`,
_85
searchRepos: () => '/search/repositories',
_85
}
_85
_85
constructor(context: IOContext, options?: InstanceOptions) {
_85
super('http://api.github.com', context, {
_85
...options,
_85
retries: 2,
_85
headers: {
_85
Accept: 'application/vnd.github.machine-man-preview+json',
_85
'x-vtex-use-https': 'true',
_85
},
_85
})
_85
}
_85
_85
public getRepo({ owner, repo }: GitRepo, token?: string) {
_85
return this.http.get<RestEndpointMethodTypes['repos']['get']['response']>(
_85
this.routes.repo({ owner, repo }),
_85
{
_85
metric: 'git-repo-get',
_85
headers: {
_85
...this.options?.headers,
_85
Accept: 'application/vnd.github.nebula-preview+json',
_85
...(token && { Authorization: `token ${token}` }),
_85
},
_85
}
_85
)
_85
}
_85
_85
public async getUser({ username }: { username: string }) {
_85
const response = await this.http.getRaw<
_85
RestEndpointMethodTypes['users']['getByUsername']['response']
_85
>(this.routes.user({ username }), { metric: 'git-user-get' })
_85
_85
return response.data
_85
}
_85
_85
public createTag(
_85
{
_85
owner,
_85
repo,
_85
...tagData
_85
}: {
_85
owner: string
_85
repo: string
_85
} & RestEndpointMethodTypes['git']['createTag']['parameters'],
_85
token?: string
_85
) {
_85
console.info('Tag data:', tagData)
_85
_85
return this.http.post<RestEndpointMethodTypes['git']['createTag']>(
_85
this.routes.tags({ owner, repo }),
_85
tagData,
_85
{
_85
metric: 'git-create-tag',
_85
headers: {
_85
...this.options?.headers,
_85
...(token && { Authorization: `token ${token}` })
_85
},
_85
}
_85
)
_85
}
_85
_85
public searchRepos({ query }: { query: string }) {
_85
return this.http.get<
_85
RestEndpointMethodTypes['search']['repos']['response']
_85
>(this.routes.searchRepos(), {
_85
metric: 'git-search-repos',
_85
params: { q: query },
_85
})
_85
}
_85
}

Step 4 - Exporting custom clients

Now that you've created your custom Client, you need to organize and export it for use in your VTEX IO service. Follow these steps:

  1. Create an index.ts file in the node/clients folder.

  2. Inside the index.ts file, import the custom Client you created in the previous step. For example:


    _10
    import GithubClient from "./github.ts";

  3. Define a class called Clients that extends IOClients. This class is used to organize and configure your custom Clients. Within the Clients class, declare a get property for each of your custom Clients. This property allows you to access a specific Client by name. In our example, we've created a Client named github that uses the GithubClient class.


    _10
    import { IOClients } from "@vtex/api";
    _10
    import GithubClient from "./github.ts";
    _10
    _10
    export class Clients extends IOClients {
    _10
    public get github() {
    _10
    return this.getOrSet("github", GithubClient);
    _10
    }
    _10
    }

Now that you have developed and exported your custom Client to communicate with the desired service, your Clients can be accessed and used within your VTEX IO service to perform various tasks, such as interacting with external APIs like GitHub. Learn how to use Clients effectively in the Using Node Clients guide.

Client types

The @vtex/api package provides a structured way to create Clients.

TypeUse case
AppClientCommunication with other IO Services via HTTP calls.
AppGraphQLClientCommunication with other IO GraphQL services.
ExternalClientCommunication with external APIs.
JanusClientCommunication with VTEX Core Commerce APIs through Janus Router.
InfraClientCommunication with VTEX IO Infra services.

Refer to the diagram below to better understand the relationship between Client types.

Note that context is of type IOContext and options is of type InstanceOptions.

InstanceOptions

The InstanceOptions table below provides a detailed description of the available options for configuring your HttpClient instance:

OptionDescription
authTypeSpecifies the authentication type.
timeoutSets the request timeout duration in milliseconds.
memoryCacheConfigures a memory cache layer for caching data.
diskCacheConfigures a disk cache layer for caching data.
baseURLDefines the base URL for making requests.
retriesSpecifies the number of times a request should be retried in case of failure.
exponentialTimeoutCoefficientConfigures the coefficient for exponential timeout backoff strategy.
initialBackoffDelaySets the initial delay before starting exponential backoff retries in milliseconds.
exponentialBackoffCoefficientConfigures the coefficient for exponential backoff retries.
metricsSpecifies an object for accumulating metrics related to requests.
concurrencyDefines the maximum number of concurrent requests.
headersSets default headers to be sent with every request.
paramsSets default query string parameters to be sent with every request.
middlewaresConfigures an array of middleware functions for request processing.
verboseEnables or disables verbose logging for requests and responses.
nameDefines a custom name for the instance.
serverTimingsSets server timings for measuring request and response times.
httpsAgentConfigures the HTTPS agent for making requests over SSL/TLS.

HttpClient methods

Below is a table outlining the methods available in the HttpClient class:

MethodDescription
get(url: string, config?: RequestConfig)Sends an HTTP GET request. Returns only the response body.
getRaw(url: string, config?: RequestConfig)Sends an HTTP GET request. Returns all the response data.
getWithBody(url: string, data?: any, config?: RequestConfig)Sends an HTTP GET request with a request body.
getBuffer(url: string, config?: RequestConfig)Sends an HTTP GET request and resolves with the response data as a buffer along with the headers.
getStream(url: string, config?: RequestConfig)Sends an HTTP GET request and resolves with the response as a readable stream (of type IncomingMessage).
put(url: string, data?: any, config?: RequestConfig)Sends an HTTP PUT request. Returns only the response body.
putRaw(url: string, data?: any, config?: RequestConfig)Sends an HTTP PUT request. Returns all the response data.
post(url: string, data?: any, config?: RequestConfig)Sends an HTTP POST request. Returns only the response body.
postRaw(url: string, data?: any, config?: RequestConfig)Sends an HTTP POST request. Returns all the response data.
patch(url: string, data?: any, config?: RequestConfig)Sends an HTTP PATCH request. Returns only the response body.
head(url: string, config?: RequestConfig)Sends an HTTP HEAD request and resolves when the request is complete.
delete(url: string, config?: RequestConfig)Sends an HTTP DELETE request.
Contributors
1
Photo of the contributor
Was this helpful?
Yes
No
Suggest Edits (GitHub)
Contributors
1
Photo of the contributor
Was this helpful?
Suggest edits (GitHub)
On this page