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

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

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

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

Below we have the complete code example:

node/github.ts

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

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

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

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

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

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

node/github.ts

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

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

node/github.ts

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

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

node/github.ts

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

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

node/github.ts

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

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.

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

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

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

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