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>;