Vue Storefront is now Alokai! Learn More
The middlewareModule

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`,
  }),
);

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

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" });