# TypeScript SDK middleware 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. ```ts const client = new ClientBuilder() .withClientCredentialsFlow(authMiddlewareOptions) .withHttpMiddleware(httpMiddlewareOptions) .withLoggerMiddleware() // Chain additional middleware here .build(); ``` ## HttpMiddleware Handles sending the HTTP request to the commerce API. ```ts 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; }; httpClient: Function; 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(); ``` ### HttpMiddleware options The following options can be passed to `httpMiddlewareOptions` when building a client: | Option | Default | Description | | --- | --- | --- | | `host` | — | The API host URL. | | `httpClient` | — | The HTTP client function to use (for example, `fetch`). | | `enableRetry` | `false` | Enable automatic retries on network errors and selected 5xx responses. | | `timeout` | `undefined` | Maximum time in milliseconds before a request is aborted. Required when `enableRetry` is `true`. | | `maxRetries` | `10` | Maximum number of retry attempts per request. | | `backoff` | `true` | Use exponential backoff between retries. | | `retryDelay` | `200` | Milliseconds to wait before the first retry. | | `maxDelay` | `undefined` | Maximum milliseconds between retries, used to cap exponential backoff. | | `includeOriginalRequest` | `false` | Include the original client request in successful responses. | | `includeRequestInErrorResponse` | `false` | Include the original request in error responses. | | `maskSensitiveHeaderData` | `true` | Redact sensitive headers, such as the `Authorization` header, from any included request data. | For configuration guidance and examples, see [Best practices](/dev-tooling/ts-sdk-best-practices.md). ## AuthMiddleware Handles generating, authenticating, and refreshing auth tokens used when making authenticated requests to the commerce API. The SDK manages the auth token lifecycle, and there is usually no need to interact or access the authentication token. Expired tokens are automatically discarded and new tokens are generated (or refreshed if using `withRefreshTokenFlow`) for new requests. You can use any one of the following authentication flows. The main difference in the authentication flows is the `options` parameter, which you can pass to each middleware. ### withClientCredentialsFlow Handles authentication for the [client credentials flow](/api/authorization.md#client-credentials-flow) of the commerce API. ```ts type AuthMiddlewareOptions = { host: string projectKey: string credentials: { clientId: string clientSecret: string } scopes?: Array 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 Handles authentication for the [password flow](/api/authorization.md#password-flow) of the commerce API. ```ts type PasswordAuthMiddlewareOptions = { host: string; projectKey: string; credentials: { clientId: string; clientSecret: string; user: { username: string; password: string; }; }; scopes?: Array; tokenCache?: TokenCache; oauthUri?: string; httpClient: Function; 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 Handles authentication for the [anonymous session flow](/api/authorization.md#tokens-for-anonymous-sessions) of the commerce API. ```ts type AnonymousAuthMiddlewareOptions = { host: string; projectKey: string; credentials: { clientId: string; clientSecret: string; anonymousId?: string; }; scopes?: Array; oauthUri?: string; httpClient: Function; 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 Handles authentication for the [refresh token flow](/api/authorization.md#refresh-token-flow) of the commerce API. ```ts type RefreshAuthMiddlewareOptions = { host: string; projectKey: string; credentials: { clientId: string; clientSecret: string; }; refreshToken: string; tokenCache?: TokenCache; oauthUri?: string; httpClient: Function; 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. ```ts type ExistingTokenMiddlewareOptions = { force?: boolean; }; const authorization: string = 'Bearer G8GLDqrUMYzaOjhdFGfK1HRIOAtj7qQy'; const options: ExistingTokenMiddlewareOptions = { force: true, }; const client = new ClientBuilder() .withExistingTokenFlow(authorization, options) // ... .build(); ``` ## CorrelationIdMiddleware [CorrelationIdMiddleware](https://github.com/commercetools/commercetools-sdk-typescript/blob/master/packages/sdk-client/src/sdk-middleware-correlation-id/correlation-id.ts) adds the [correlation ID](/api/general-concepts.md#correlation-id) to the request headers. ```ts 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 [UserAgentMiddleware](https://github.com/commercetools/commercetools-sdk-typescript/blob/master/packages/sdk-client/src/sdk-middleware-user-agent/user-agent.ts) adds a customizable `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'` ```ts 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. ```ts type QueueMiddlewareOptions = { concurrency: number; }; const options: QueueMiddlewareOptions = { concurrency: 20, }; const client = new ClientBuilder() .withQueueMiddleware(options) // ... .build(); ``` ## ErrorMiddleware Use ErrorMiddleware to handle HTTP errors by providing a custom error `handler` function via options. If specified, this function is invoked with the error object, request, response, and next function when an error occurs. ```ts type ErrorHandlerOptions = { error: HttpErrorType; request: MiddlewareRequest; response: MiddlewareResponse; next: Next; }; type ErrorMiddlewareOptions = { handler?: (args: ErrorHandlerOptions) => Promise; }; const errorMiddlewareOptions: ErrorMiddlewareOptions = { handler: async (args: ErrorHandlerOptions): Promise => { 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 Allows integrating analytics and monitoring services (such as New Relic or Dynatrace) into your TypeScript SDK applications. For more information, see [Observability](/dev-tooling/observability.md). `withTelemetryMiddleware` requires the [@commercetools/ts-sdk-apm](https://www.npmjs.com/package/@commercetools/ts-sdk-apm) package. You can install it by using any one of the following commands: ```bash title="Install with npm" npm install @commercetools/ts-sdk-apm ``` ```bash title="Install with yarn" yarn add @commercetools/ts-sdk-apm ``` ```ts // 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 Logs incoming requests and response objects. You can add an optional `options` parameter, which accepts a custom logger function, and another optional parameter to be used within the custom logger function. ```ts 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 This middleware manages concurrent modification errors. It retries the request if the API returns a `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. ```ts type ConcurrentModificationMiddlewareOptions = { concurrentModificationHandlerFn: ( version: number, request: MiddlewareRequest, response: MiddlewareResponse ) => Promise | string | Buffer>; }; const options: ConcurrentModificationMiddlewareOptions = { concurrentModificationHandlerFn: (version, request) => { console.log(`Concurrent modification error, retry with version ${version}`); const body = request.body as Record; 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. The following code example demonstrates how to create custom middleware that includes a value for the header `X-External-User-ID`. ```ts function createCustomHeaderMiddleware() { return (next: Next): Next => (request: MiddlewareRequest) => { const newRequest = { ...request, headers: { ...request.headers, 'X-External-User-ID': 'custom-header-value', }, }; return next(newRequest); }; } ``` You can add this custom middleware by using the `.withMiddleware()` method. Using this method, the SDK calls your middleware before calling other middlewares. To add custom middleware that the SDK should call before or after the execution to commerce API, use `BeforeExecutionMiddleware` or `AfterExecutionMiddleware`. ```ts const client = new ClientBuilder() .withMiddleware(createCustomHeaderMiddleware()) // ... .build(); ``` ### BeforeExecutionMiddleware This middleware runs before the SDK calls the commerce API, and is useful for preprocessing requests. This middleware has access to the request and response object as well as the options included in `.withBeforeExecutionMiddleware()`. ```ts 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 This middleware runs after the SDK calls the commerce API, and is useful for checking the API response for further actions (for example, custom retry implementations and post-response processing). This middleware has access to the request and response object as well as the options included in `.withAfterExecutionMiddleware()`. ```ts 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: ```ts export type Middleware = ( next: Next ) => (request: MiddlewareRequest) => Promise; ``` ## Related pages - [Area overview page with navigation](/dev-tooling.md) - [Previous page: Get started](/dev-tooling/ts-sdk-getting-started.md) - [Next page: Best practices](/dev-tooling/ts-sdk-best-practices.md)