Alokai

The middlewareModule

The middlewareModule is the SDK module that connects your frontend to the Server Middleware. It turns your middleware endpoints into typed SDK methods — so instead of manually constructing HTTP requests, you call SDK methods and the module handles the rest.

Behind the scenes, it takes care of:

  • Routing requests to the correct middleware endpoint
  • Forwarding cookies and headers during SSR
  • Switching between client-side and server-side API URLs
  • Error handling and request logging

Setup

You can import middlewareModule directly from the @alokai/connect/sdk package:

import { initSDK, buildModule, middlewareModule } from "@alokai/connect/sdk";
import type { UnifiedEndpoints } from "storefront-middleware/types";

export const sdk = initSDK({
  commerce: buildModule(middlewareModule<UnifiedEndpoints>, {
    apiUrl: "http://localhost:4000/commerce",
  }),
});

If you're using @vue-storefront/next or @vue-storefront/nuxt, middlewareModule is also available as an argument in the defineSdkModule callback — alongside other helpers like buildModule, config, and getRequestHeaders:

sdk/modules/commerce.ts
import { defineSdkModule } from '@vue-storefront/next';
import type { CommerceEndpoints } from "storefront-middleware/types";

export const commerce = defineSdkModule(({ buildModule, config, getRequestHeaders, middlewareModule }) =>
  buildModule(middlewareModule<CommerceEndpoints>, {
    apiUrl: `${config.apiUrl}/commerce`,
    cdnCacheBustingId: config.cdnCacheBustingId,
    defaultRequestConfig: {
      headers: async () => getRequestHeaders(),
    },
    ssrApiUrl: `${config.ssrApiUrl}/commerce`,
  }),
);

In Nuxt, defineSdkModule is auto-imported by the @vue-storefront/nuxt module — no import statement needed.

sdk-modules/commerce.ts
import type { CommerceEndpoints } from 'storefront-middleware/types';

export const commerce = defineSdkModule(({ buildModule, config, getRequestHeaders, middlewareModule }) =>
  buildModule(middlewareModule<CommerceEndpoints>, {
    apiUrl: `${config.apiUrl}/commerce`,
    cdnCacheBustingId: config.cdnCacheBustingId,
    defaultRequestConfig: {
      headers: getRequestHeaders(),
    },
    ssrApiUrl: `${config.ssrApiUrl}/commerce`,
  }),
);

Configuration

Type definition

The middlewareModule communicates with the Alokai Middleware, which can connect to any backend. To get full type safety, you need to pass a type definition of your endpoints as a generic to middlewareModule.

In the Alokai Storefront, endpoint types are exported from storefront-middleware/types.ts. For example, UnifiedEndpoints covers all Unified API methods, and CommerceEndpoints covers raw eCommerce API methods. You can also import endpoint types directly from integration packages (e.g. @vsf-enterprise/sapcc-api).

Here's how a fully typed SDK module looks using defineSdkModule:

sdk/modules/unified.ts
import { defineSdkModule } from '@vue-storefront/next';
import type { UnifiedEndpoints } from 'storefront-middleware/types';

export const unified = defineSdkModule(({ buildModule, config, getRequestHeaders, middlewareModule }) =>
  buildModule(middlewareModule<UnifiedEndpoints>, {
    apiUrl: `${config.apiUrl}/commerce/unified`,
    cdnCacheBustingId: config.cdnCacheBustingId,
    defaultRequestConfig: {
      headers: async () => getRequestHeaders(),
    },
    ssrApiUrl: `${config.ssrApiUrl}/commerce/unified`,
  }),
);

Each module is defined in its own file under sdk/modules/ and re-exported from sdk/modules/index.ts. Then defineSdkConfig ties them together:

sdk/config.ts
import { defineSdkConfig } from '@vue-storefront/next';
import * as modules from '@/sdk/modules';

export function getSdkConfig() {
  return defineSdkConfig(modules);
}
sdk-modules/unified.ts
import type { UnifiedEndpoints } from 'storefront-middleware/types';

export const unified = defineSdkModule(({ buildModule, config, getRequestHeaders, middlewareModule }) =>
  buildModule(middlewareModule<UnifiedEndpoints>, {
    apiUrl: `${config.apiUrl}/commerce/unified`,
    cdnCacheBustingId: config.cdnCacheBustingId,
    defaultRequestConfig: {
      headers: getRequestHeaders(),
    },
    ssrApiUrl: `${config.ssrApiUrl}/commerce/unified`,
  }),
);

Each module is defined in its own file under sdk-modules/ and re-exported from sdk-modules/index.ts. Then defineSdkConfig ties them together:

sdk.config.ts
import * as modules from '@/sdk-modules';

export default defineSdkConfig(modules);

The SDK instance is now fully typed — calling sdk.unified.getProduct({ id: "123" }) will autocomplete parameters and return types based on your endpoint definitions.

Options

The middlewareModule accepts the following options:

  • apiUrl - the URL of the middleware server.
  • ssrApiUrl - (Optional) the URL of the middleware server during SSR.
  • defaultRequestConfig - (Optional) default request config for each request.
  • httpClient - (Optional) a custom HTTP client.
  • errorHandler - (Optional) a custom error handler for HTTP requests.
  • logger - (Optional) a flag to enable logging of the requests and responses. You can also pass a custom logger.
  • cdnCacheBustingId - (Optional) a middleware version identifier that will be attached to all GET requests for the sake of bypassing the CDN cache stored for a different version of the application. Read more about cache busting on MDN.

Here's an example showing all available options:

sdk/modules/commerce.ts
import { defineSdkModule } from '@vue-storefront/next';
import type { CommerceEndpoints } from 'storefront-middleware/types';

const GIT_COMMIT_HASH = process.env.GIT_COMMIT_HASH;

export const commerce = defineSdkModule(({ buildModule, config, getRequestHeaders, middlewareModule }) =>
  buildModule(middlewareModule<CommerceEndpoints>, {
    apiUrl: `${config.apiUrl}/commerce`,
    ssrApiUrl: `${config.ssrApiUrl}/commerce`,
    cdnCacheBustingId: GIT_COMMIT_HASH,
    defaultRequestConfig: {
      headers: async () => getRequestHeaders(),
    },
    httpClient: async (url, params, config) => {
      // Custom HTTP client
    },
    errorHandler: ({ error, methodName, url, params, config, httpClient }) => {
      // Custom error handler
    },
    logger: {
      onRequest: ({ url, config, params }) => {
        // Custom request logger
      },
      onResponse: ({ url, config, response, responseTime }) => {
        // Custom response logger
      },
    },
  }),
);

Usage

Once you have added the middlewareModule to your SDK, you can use it to make requests to the Server Middleware.

const product = await sdk.commerce.getProduct({ id: "123" });

Additionally, each method of this module allows you to pass a custom request configuration. To do it, you need to import prepareConfig helper from @alokai/connect/sdk package.

import { prepareConfig } from "@alokai/connect/sdk";

const product = await sdk.commerce.getProduct(
  { id: "123" },
  prepareConfig({
    method: "GET",
    headers: {
      "X-Custom-Header": "123",
    },
  })
);

Examples

Send a GET request

By default, each request is a POST request. You can change it by passing a custom request configuration.

const product = await sdk.commerce.getProduct(
  { id: "123" },
  prepareConfig({
    method: "GET",
  })
);

Use a GET method by default

You can use a GET method by default by passing it in defaultRequestConfig:

sdk/modules/commerce.ts
export const commerce = defineSdkModule(({ buildModule, config, getRequestHeaders, middlewareModule }) =>
  buildModule(middlewareModule<CommerceEndpoints>, {
    apiUrl: `${config.apiUrl}/commerce`,
    defaultRequestConfig: {
      method: "GET",
      headers: async () => getRequestHeaders(),
    },
    ssrApiUrl: `${config.ssrApiUrl}/commerce`,
  }),
);

Add a header to each request

Pass static headers in defaultRequestConfig:

sdk/modules/commerce.ts
export const commerce = defineSdkModule(({ buildModule, config, getRequestHeaders, middlewareModule }) =>
  buildModule(middlewareModule<CommerceEndpoints>, {
    apiUrl: `${config.apiUrl}/commerce`,
    defaultRequestConfig: {
      headers: {
        "X-Api-Key": "123",
      },
    },
    ssrApiUrl: `${config.ssrApiUrl}/commerce`,
  }),
);

The headers field also accepts an async function, which is useful when you need to combine request headers with custom values or fetch a token dynamically before each request:

sdk/modules/commerce.ts
export const commerce = defineSdkModule(({ buildModule, config, getRequestHeaders, middlewareModule }) =>
  buildModule(middlewareModule<CommerceEndpoints>, {
    apiUrl: `${config.apiUrl}/commerce`,
    defaultRequestConfig: {
      headers: async () => ({
        ...(await getRequestHeaders()),
        Authorization: `Bearer ${await getAccessToken()}`,
      }),
    },
    ssrApiUrl: `${config.ssrApiUrl}/commerce`,
  }),
);

Add a header to a single request

Pass headers in the prepareConfig call for a single request:

const product = await sdk.commerce.getProduct(
  { id: "123" },
  prepareConfig({
    headers: {
      "X-Custom-Header": "123",
    },
  })
);

Log requests and responses

Set the logger option to true to enable request and response logging:

sdk/modules/commerce.ts
export const commerce = defineSdkModule(({ buildModule, config, getRequestHeaders, middlewareModule }) =>
  buildModule(middlewareModule<CommerceEndpoints>, {
    apiUrl: `${config.apiUrl}/commerce`,
    defaultRequestConfig: {
      headers: async () => getRequestHeaders(),
    },
    logger: true,
    ssrApiUrl: `${config.ssrApiUrl}/commerce`,
  }),
);

You can also enable logging across all middleware modules by setting the ALOKAI_SDK_DEBUG environment variable to true. If logger is explicitly set to false, the environment variable is ignored.

To override the default logger, pass a custom logger object:

sdk/modules/commerce.ts
export const commerce = defineSdkModule(({ buildModule, config, getRequestHeaders, middlewareModule }) =>
  buildModule(middlewareModule<CommerceEndpoints>, {
    apiUrl: `${config.apiUrl}/commerce`,
    defaultRequestConfig: {
      headers: async () => getRequestHeaders(),
    },
    logger: {
      onRequest: ({ url, config, params }) => {
        console.log(`Request: ${config.method} ${url}`, params);
      },
      onResponse: ({ url, config, responseTime }) => {
        console.log(`Response: ${config.method} ${url} in ${responseTime.toFixed()}ms`);
      },
    },
    ssrApiUrl: `${config.ssrApiUrl}/commerce`,
  }),
);

Replace the default HTTP client with axios

By default, the SDK uses the fetch API. You can replace it via the httpClient option.

A custom HTTP client must:

  1. Be an async function accepting url, params, and config parameters.
  2. Send credentials with the request (e.g. withCredentials: true in axios) so cookies are forwarded.
  3. Throw an SdkHttpError if the request fails.
sdk/modules/commerce.ts
import axios from "axios";
import { SdkHttpError } from "@alokai/connect/sdk";

export const commerce = defineSdkModule(({ buildModule, config, getRequestHeaders, middlewareModule }) =>
  buildModule(middlewareModule<CommerceEndpoints>, {
    apiUrl: `${config.apiUrl}/commerce`,
    defaultRequestConfig: {
      headers: async () => getRequestHeaders(),
    },
    httpClient: async (url, params, config) => {
      try {
        const { data } = await axios(url, {
          ...config,
          data: params,
          withCredentials: true,
        });

        return data;
      } catch (error: any) {
        throw new SdkHttpError({
          statusCode: error.response?.status || 500,
          message: error.response?.data?.message || error.message,
          cause: error,
        });
      }
    },
    ssrApiUrl: `${config.ssrApiUrl}/commerce`,
  }),
);

Without withCredentials: true (axios) or credentials: 'include' (fetch), cookies won't be sent with cross-origin requests.

Add a custom error handler

Use the errorHandler option to intercept and handle errors. For example, to automatically refresh an expired token and retry:

sdk/modules/commerce.ts
import { refreshToken } from "@/handlers/refreshToken";

export const commerce = defineSdkModule(({ buildModule, config, getRequestHeaders, middlewareModule }) =>
  buildModule(middlewareModule<CommerceEndpoints>, {
    apiUrl: `${config.apiUrl}/commerce`,
    defaultRequestConfig: {
      headers: async () => getRequestHeaders(),
    },
    errorHandler: async ({ error, methodName, url, params, config, httpClient }) => {
      if (error.status === 401 && methodName !== "login") {
        await refreshToken();
        return httpClient(url, params, config);
      }

      throw error;
    },
    ssrApiUrl: `${config.ssrApiUrl}/commerce`,
  }),
);

Add cookies to the request during SSR

During CSR, cookies are sent automatically. During SSR, the getRequestHeaders helper provided by defineSdkModule takes care of forwarding them — as shown in the Setup and Configuration sections.

In Next.js, the getSdk function from @/sdk calls Next.js's cookies() and headers() internally, so you just await it in any server component:

app/page.tsx
import { getSdk } from "@/sdk";

export default async function SsrPage() {
  const sdk = await getSdk();
  const product = await sdk.commerce.getProduct({ id: "123" });

  return (
    // ...
  );
}

In Nuxt, cookies are forwarded automatically — no extra setup needed.

Add cookies to the request during CSR

Cookies are sent with every request during Client-Side Rendering automatically — no extra configuration is needed. The SDK uses credentials: "include" under the hood, so the browser attaches all relevant cookies to each request.

If you need to include a custom cookie, set it via document.cookie before making the SDK call — the browser will include it in all subsequent requests to the matching domain:

document.cookie = "name=value; path=/";

const product = await sdk.commerce.getProduct({ id: "123" });

Use the middlewareModule with a custom integration

You can use middlewareModule with any custom integration by providing a type definition of its endpoints.

This example assumes you have a custom API Client implemented as described in the Creating an API Client guide, and that the integration is registered in middleware.config.ts at the /custom endpoint.

First, re-export the Endpoints type from the custom integration package so your frontend doesn't need a direct dependency on it:

storefront-middleware/types.ts
export { Endpoints as CustomEndpoints } from "custom-integration-api-client";

// ...

Then, create a new SDK module for the custom integration:

sdk/modules/custom.ts
import { defineSdkModule } from "@vue-storefront/next";
import type { CustomEndpoints } from "storefront-middleware/types";

export const custom = defineSdkModule(({ buildModule, config, getRequestHeaders, middlewareModule }) =>
  buildModule(middlewareModule<CustomEndpoints>, {
    apiUrl: `${config.apiUrl}/custom`,
    defaultRequestConfig: {
      headers: async () => getRequestHeaders(),
    },
    ssrApiUrl: `${config.ssrApiUrl}/custom`,
  }),
);

Now you can call the custom integration endpoints through the SDK:

const result = await sdk.custom.someMethod({ id: "123" });

On this page