Add functionality to the request object in the TypeScript SDK.
You can add middleware when creating the TypeScript SDK client. You can add multiple middlewares by using a chain of middleware builder methods.
const client = new ClientBuilder()
.withClientCredentialsFlow(authMiddlewareOptions)
.withHttpMiddleware(httpMiddlewareOptions)
.withLoggerMiddleware()
// Chain additional middleware here
.build();
HttpMiddleware
Handles sending the HTTP request to the Composable Commerce API.
type HttpMiddlewareOptions = {
host: string;
credentialsMode?: 'omit' | 'same-origin' | 'include';
includeResponseHeaders?: boolean;
includeOriginalRequest?: boolean;
includeRequestInErrorResponse?: boolean;
maskSensitiveHeaderData?: boolean;
timeout?: number;
enableRetry?: boolean;
retryConfig?: {
maxRetries?: number;
retryDelay?: number;
backoff?: boolean;
maxDelay?: number;
retryOnAbort?: boolean;
retryCodes?: Array<number | string>;
};
httpClient: Function<fetch | axiosInstance>;
httpClientOptions?: object; // will be passed as a second argument to your httpClient function for configuration
getAbortController?: () => AbortController;
};
// using a proxy agent
import { HttpsProxyAgent } from 'https-proxy-agent';
const agent = new HttpsProxyAgent('http://8.8.8.8:8888');
const options: HttpMiddlewareOptions = {
host: 'https://api.europe-west1.gcp.commercetools.com',
includeResponseHeaders: true,
maskSensitiveHeaderData: true,
includeOriginalRequest: false,
includeRequestInErrorResponse: false,
enableRetry: true,
retryConfig: {
maxRetries: 3,
retryDelay: 200,
backoff: false,
retryCodes: [503],
},
httpClient: fetch,
httpClientOptions: { agent } // this will be passed to fetch ()
};
const client = new ClientBuilder()
.withHttpMiddleware(options)
// ...
.build();
AuthMiddleware
Handles generating, authenticating, and refreshing auth tokens used when making authenticated requests to the Composable Commerce API.
withRefreshTokenFlow
) for new requests.options
parameter, which you can pass to each middleware.withClientCredentialsFlow
type AuthMiddlewareOptions = {
host: string
projectKey: string
credentials: {
clientId: string
clientSecret: string
}
scopes?: Array<string>
oauthUri?: string
httpClient: Function
httpClientOptions?: object
tokenCache?: TokenCache
}
const options: AuthMiddlewareOptions {
host: 'https://auth.europe-west1.gcp.commercetools.com',
projectKey: 'test-project-key',
credentials: {
clientId: process.env.CTP_CLIENT_ID,
clientSecret: process.env.CTP_CLIENT_SECRET
},
scopes: [`manage_project:${projectKey}`],
httpClient: fetch
}
const client = new ClientBuilder()
.withClientCredentialsFlow(options)
// ...
.build()
withPasswordFlow
type PasswordAuthMiddlewareOptions = {
host: string;
projectKey: string;
credentials: {
clientId: string;
clientSecret: string;
user: {
username: string;
password: string;
};
};
scopes?: Array<string>;
tokenCache?: TokenCache;
oauthUri?: string;
httpClient: Function<fetch | axiosInstance>;
httpClientOptions?: object;
};
const options: PasswordAuthMiddlewareOptions = {
host: 'https://auth.europe-west1.gcp.commercetools.com',
projectKey: 'test-project-key',
credentials: {
clientId: process.env.CTP_CLIENT_ID,
clientSecret: process.env.CTP_CLIENT_SECRET,
user: {
username: process.env.USERNAME,
password: process.env.PASSWORD,
},
},
scopes: [`manage_project:${projectKey}`],
httpClient: fetch,
};
const client = new ClientBuilder()
.withPasswordFlow(options)
// ...
.build();
withAnonymousSessionFlow
type AnonymousAuthMiddlewareOptions = {
host: string;
projectKey: string;
credentials: {
clientId: string;
clientSecret: string;
anonymousId?: string;
};
scopes?: Array<string>;
oauthUri?: string;
httpClient: Function<fetch | axiosInstance>;
httpClientOptions?: object
tokenCache?: TokenCache;
};
const options: AnonymousAuthMiddlewareOptions = {
host: 'https://auth.europe-west1.gcp.commercetools.com',
projectKey: 'test-project-key',
credentials: {
clientId: process.env.CTP_CLIENT_ID,
clientSecret: process.env.CTP_CLIENT_SECRET,
anonymousId: process.env.CTP_ANONYMOUS_ID, // a unique id
},
scopes: [`manage_project:${projectKey}`],
httpClient: fetch,
};
const client = new ClientBuilder()
.withAnonymousSessionFlow(options)
// ...
.build();
withRefreshTokenFlow
type RefreshAuthMiddlewareOptions = {
host: string;
projectKey: string;
credentials: {
clientId: string;
clientSecret: string;
};
refreshToken: string;
tokenCache?: TokenCache;
oauthUri?: string;
httpClient: Function<fetch | axiosInstance>;
httpClientOptions?: object;
};
const options: RefreshAuthMiddlewareOptions = {
host: 'https://auth.europe-west1.gcp.commercetools.com',
projectKey: 'test-project-key',
credentials: {
clientId: process.env.CTP_CLIENT_ID,
clientSecret: process.env.CTP_CLIENT_SECRET,
},
refreshToken: 'bXvTyxc5yuebdvwTwyXn==',
tokenCache: TokenCache,
scopes: [`manage_project:${projectKey}`],
httpClient: fetch,
};
const client = new ClientBuilder()
.withRefreshTokenFlow(options)
// ...
.build();
withExistingTokenFlow
Attaches an access token Authorization header.
type ExistingTokenMiddlewareOptions = {
force?: boolean;
};
const authorization: string = 'Bearer G8GLDqrUMYzaOjhdFGfK1HRIOAtj7qQy';
const options: ExistingTokenMiddlewareOptions = {
force: true,
};
const client = new ClientBuilder()
.withExistingTokenFlow(authorization, options)
// ...
.build();
CorrelationIdMiddleware
X-Correlation-ID
entry to the request headers.type CorrelationIdMiddlewareOptions = {
generate: () => string;
};
const options: CorrelationIdMiddlewareOptions = {
generate: () => 'cd260fc9-c575-4ba3-8789-cc4c9980ee4e', // Replace with your own UUID or a generator function
};
const client = new ClientBuilder()
.withCorrelationIdMiddleware(options)
// ...
.build();
UserAgentMiddleware
User-Agent
header to every request. By default it adds the SDK (and its version) and the running process (and its version) to the request. For example:'User-Agent': 'commercetools-sdk-javascript-v2/2.1.4 node.js/18.13.0'
type HttpUserAgentOptions = {
name?: string;
version?: string;
libraryName?: string;
libraryVersion?: string;
contactUrl?: string;
contactEmail?: string;
customAgent?: string;
};
const options: HttpUserAgentOptions = {
name: 'test-client-agent',
version: 'x.y.z',
};
const client = new ClientBuilder()
.withUserAgentMiddleware(options)
// ...
.build();
QueueMiddleware
Use QueueMiddleware to reduce concurrent HTTP requests.
type QueueMiddlewareOptions = {
concurrency: number;
};
const options: QueueMiddlewareOptions = {
concurrency: 20,
};
const client = new ClientBuilder()
.withQueueMiddleware(options)
// ...
.build();
ErrorMiddleware
handler
function via options. If specified, this function is invoked with the error object, request, response, and next function when an error occurs.type ErrorHandlerOptions = {
error: HttpErrorType;
request: MiddlewareRequest;
response: MiddlewareResponse;
next: Next;
};
type ErrorMiddlewareOptions = {
handler?: (args: ErrorHandlerOptions) => Promise<MiddlewareResponse>;
};
const errorMiddlewareOptions: ErrorMiddlewareOptions = {
handler: async (args: ErrorHandlerOptions): Promise<MiddlewareResponse> => {
const { error, request, response, next } = args;
// handle error here
if ('NetworkError'.includes(error.code) && response.retryCount == 0) {
return next(request)
}
return response
},
};
const client = new ClientBuilder()
.withErrorMiddleware(errorMiddlewareOptions)
// ...
.build();
TelemetryMiddleware
withTelemetryMiddleware
requires the @commercetools/ts-sdk-apm package. You can install it by using any one of the following commands:npm install @commercetools/ts-sdk-apm
yarn add @commercetools/ts-sdk-apm
// Required import
import {
createTelemetryMiddleware,
TelemetryMiddlewareOptions,
} from '@commercetools/ts-sdk-apm';
const telemetryOptions: TelemetryMiddlewareOptions = {
createTelemetryMiddleware,
apm: () => typeof require('newrelic'), // installed npm `newrelic` package
tracer: () => typeof require('/absolute-path-to-a-tracer-module'),
customMetrics: {
newrelic: true,
datadog: true,
},
};
const client = new ClientBuilder()
.withTelemetryMiddleware(telemetryOptions)
// ...
.build();
LoggerMiddleware
options
parameter, which accepts a custom logger function, and another optional parameter to be used within the custom logger function.type LoggerMiddlewareOptions = {
loggerFn?: (options: MiddlewareResponse) => void
}
const loggerMiddlewareOptions: LoggerMiddlewareOptions = {
loggerFn: (response: MiddlewareResponse) => {
console.log('Response is: ', response)
},
}
const client = new ClientBuilder()
.withLoggerMiddleware(loggerMiddlewareOptions)
// ...
.build();
Concurrent modification middleware
409 Conflict
HTTP status code.By default, it takes the correct version from the error response and resends the request. This behavior can be overridden by providing a custom function.
type ConcurrentModificationMiddlewareOptions = {
concurrentModificationHandlerFn: (
version: number,
request: MiddlewareRequest,
response: MiddlewareResponse
) => Promise<Record<string, any> | string | Buffer>;
};
const options: ConcurrentModificationMiddlewareOptions = {
concurrentModificationHandlerFn: (version, request) => {
console.log(`Concurrent modification error, retry with version ${version}`);
const body = request.body as Record<string, any>;
body.version = version;
return Promise.resolve(body);
},
};
const client = new ClientBuilder()
.withConcurrentModificationMiddleware(options)
// ...
.build();
Custom middleware
Certain use cases, such as adding headers to API requests, may require you to create custom middleware.
X-External-User-ID
.function createCustomHeaderMiddleware() {
return (next: Next): Next => (request: MiddlewareRequest) => {
const newRequest = {
...request,
headers: {
...request.headers,
'X-External-User-ID': 'custom-header-value',
},
};
return next(newRequest);
};
}
.withMiddleware()
method. Using this method, the SDK calls your middleware before calling other middlewares.BeforeExecutionMiddleware
or AfterExecutionMiddleware
.const client = new ClientBuilder()
.withMiddleware(createCustomHeaderMiddleware())
// ...
.build();
BeforeExecutionMiddleware
.withBeforeExecutionMiddleware()
.import {
type Next,
type Client,
type MiddlewareRequest,
type BeforeExecutionMiddlewareOptions,
type MiddlewareResponse,
ClientBuilder,
} from '@commercetools/ts-client';
function before(options: BeforeExecutionMiddlewareOptions) {
return (next: Next): Next => {
return (req: MiddlewareRequest) => {
// Logic to be executed goes here
// option will contain { name: 'before-middleware-fn' }
console.log(options); // { name: 'before-middleware-fn' }
return next(req);
};
};
}
const client: Client = new ClientBuilder()
.withProjectKey('projectKey')
.withBeforeExecutionMiddleware({
name: 'before-middleware-fn',
middleware: before,
})
// ...
.build();
AfterExecutionMiddleware
.withAfterExecutionMiddleware()
.import {
type Next,
type Client,
type MiddlewareRequest,
type AfterExecutionMiddlewareOptions,
type MiddlewareResponse,
ClientBuilder,
} from '@commercetools/ts-client';
function after(options: AfterExecutionMiddlewareOptions) {
return (next: Next): Next => {
return (req: MiddlewareRequest) => {
// Logic to be executed goes here
// option will contain { name: 'after-middleware-fn' }
console.log(options); // { name: 'after-middleware-fn' }
return next(req);
};
};
}
const client: Client = new ClientBuilder()
.withProjectKey('projectKey')
.withAfterExecutionMiddleware({
name: 'after-middleware-fn',
middleware: after,
})
// ...
.build();
The custom middleware function has the following Type definition/signature:
export type Middleware = (
next: Next
) => (request: MiddlewareRequest) => Promise<MiddlewareResponse>;