Vue Storefront is now Alokai! Learn More
The middlewareModule

The middlewareModule

Introduction

The middlewareModule is an SDK module that ensures communication with the Server Middleware.

Setup

Depending on which framework you are using, you can get the middlewareModule in different ways. However, the setup is always the same.

In Next, middlewareModule is available in the createSdk function.

Next
// sdk/sdk.config.ts
import { CreateSdkOptions, createSdk } from "@vue-storefront/next";
import { UnifiedEndpoints } from "storefront-middleware/types";

const options: CreateSdkOptions = {
  middleware: {
    apiUrl: "http://localhost:4000",
  },
};

export const { getSdk } = createSdk(
  options,
  ({ buildModule, config, middlewareModule, getRequestHeaders }) => ({
    commerce: buildModule(middlewareModule<UnifiedEndpoints>, {
      apiUrl: config.middlewareUrl + "/commerce",
      defaultRequestConfig: {
        headers: getRequestHeaders(),
      },
    }),
  })
);

In Nuxt, middlewareModule is available in the defineSdkConfig function.

Nuxt
// sdk.config.ts
import { UnifiedEndpoints } from "storefront-middleware/types";

export default defineSdkConfig(
  ({ buildModule, config, middlewareModule, getRequestHeaders }) => ({
    commerce: buildModule(middlewareModule<UnifiedEndpoints>, {
      apiUrl: config.middlewareUrl + "/commerce", // SAP Commerce Cloud integration is available at /commerce endpoint
      defaultRequestConfig: {
        headers: getRequestHeaders(),
      },
    })
  })
);

You can also import it directly from the @vue-storefront/sdk package.

Other
import { initSDK, buildModule, middlewareModule } from "@vue-storefront/sdk";
import { UnifiedEndpoints } from "storefront-middleware/types";

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

Configuration

Type definition

As the middlewareModule is used to communicate with the Alokai Middleware and it's not limited to a specific backend, it requires a type definition of the endpoints.

The type definition should be provided as a generic type to the middlewareModule function.

type Endpoints = {
  getProduct: ({
    id: string,
  }) => Promise<{ id: string; title: string; description: string }>;
};

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

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

In Alokai Storefront, you can find the UnifiedEndpoints type in storefront-middleware/types.ts file. It's a type definition for the endpoints used in the middleware.

Nuxt and Next examples

We're using initSDK approach in this examples to make it more universal. However, you can use createSdk and defineSdkConfig functions in Next and Nuxt as well.

import { UnifiedEndpoints } from "storefront-middleware/types";

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

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

Also, you can import the Endpoints type from integration packages.

import { Endpoints } from "@vsf-enterprise/sapcc-api";

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

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.

Example configuration:

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

export const sdk = initSDK({
  commerce: buildModule(middlewareModule<UnifiedEndpoints>, {
    apiUrl: "/api/commerce",
    ssrApiUrl: "http://localhost:8181/commerce",
    defaultRequestConfig: {
      headers: {
        "X-Api-Key": "123",
      },
    },
    httpClient: async (url, params, config) => {
      // Custom HTTP client
    },
    errorHandler: ({ error, methodName, url, params, config, httpClient }) => {
      // Custom error handler
    },
    logger: {
      request: (url, config) => {
        // custom request logger
      },
      response: (response) => {
        // custom response logger
      },
    }
  }),
});

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

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 @vue-storefront/sdk package.

import { prepareConfig } from "@vue-storefront/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 the defaultRequestConfig option of the middlewareModule function.

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

Add a header to each request

You can add a header to each request by passing it in the defaultRequestConfig option of the middlewareModule function.

export const sdk = initSDK({
  commerce: buildModule(middlewareModule<UnifiedEndpoints>, {
    apiUrl: "http://localhost:8181/commerce",
    defaultRequestConfig: {
      headers: {
        "X-Api-Key": "123",
      },
    },
  }),
});

Add a header to single request

You can add a header to a single request by passing it in the request configuration.

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

Log requests and responses

You can enable logging of the requests and responses by setting the logger option to true in the middlewareModule function.

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

You can also enable logging of the requests and responses in all middleware modules by setting the environment variable ALOKAI_SDK_DEBUG to true. If logger is set to false, the environment variable will be ignored.

If you want to override the default logger, you can pass a custom logger to the logger option.

export const sdk = initSDK({
  commerce: buildModule(middlewareModule<UnifiedEndpoints>, {
    apiUrl: "http://localhost:8181/commerce",
    logger: {
      request: (url, config) => {
        console.log(`Request: ${url}`);
      },
      response: (response) => {
        console.log(`Response: ${response}`);
      },
    },
  }),
});

Replace the default HTTP client with axios

By default, the SDK uses the fetch API. You can replace the default HTTP client with axios by passing it in the httpClient option of the middlewareModule function.

There are some requirements for the custom HTTP client.

  1. It must be an async function accepting the following parameters:
    • url - the URL of the request,
    • params - the parameters of the request,
    • config - the request configuration,
  2. It must include the Access-Control-Allow-Credentials response header (more info here),
  3. It must throw an SdkHttpError if the request fails.
import axios from "axios";
import { SdkHttpError } from "@vue-storefront/sdk";

export const sdk = initSDK({
  commerce: buildModule(middlewareModule<UnifiedEndpoints>, {
    apiUrl: "http://localhost:8181/commerce",
    httpClient: async (url, params, config) => {
      try {
        const { data } = await axios(url, {
          ...config,
          data: params,
          withCredentials: true, // Includes the `Access-Control-Allow-Credentials` response header
        });

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

It's important to include the Access-Control-Allow-Credentials response header in the custom HTTP client. Otherwise, the cookies won't be sent with the request.

Add a custom error handler

You can add a custom error handler by passing it in the errorHandler option of the middlewareModule function.

import { SdkHttpError } from "@vue-storefront/sdk";
import { refreshToken } from "./handlers/refreshToken"; // Custom implementation of the refresh token logic

const options: Options = {
  apiUrl: "https://api.example.com",
  errorHandler: async ({
    error,
    methodName,
    url,
    params,
    config,
    httpClient,
  }) => {
    if (error.status === 401 && methodName !== "login") {
      // Refresh the token
      await refreshToken();
      // Retry the request
      return httpClient(url, params, config);
    }

    throw error;
  },
};

Add cookies to the request during SSR

Normally, the cookies are sent with the request during CSR. However, during SSR, you need to pass them manually.

@vue-storefront/next and @vue-storefront/nuxt provide a getRequestHeaders helper that returns the cookies.

To use it, you need to pass the getRequestHeaders function to the defaultRequestConfig option of the middlewareModule function.

sdk/config.ts
import { defineSdkConfig } from "@vue-storefront/next";
import { UnifiedEndpoints } from "storefront-middleware/types";

export function getSdkConfig() {
  return defineSdkConfig(({ buildModule, config, middlewareModule, getRequestHeaders }) => ({
    commerce: buildModule(middlewareModule<UnifiedEndpoints>, {
      apiUrl: config.middlewareUrl + "/commerce",
      defaultRequestConfig: {
        headers: getRequestHeaders(),
      },
    }),
  }));
}
Nuxt
// sdk.config.ts
import { defineSdkConfig } from "@vue-storefront/nuxt";
import { UnifiedEndpoints } from "storefront-middleware/types";

export default defineSdkConfig(
  ({ buildModule, config, middlewareModule, getRequestHeaders }) => ({
    commerce: buildModule(middlewareModule<UnifiedEndpoints>, {
      apiUrl: config.middlewareUrl + "/commerce",
      defaultRequestConfig: {
        headers: getRequestHeaders(),
      },
    }),
  })
);

While Nuxt ensures that cookies are sent with the request without any additional configuration, in Next, you need to pass the headers to getSdk function.

App Router
import { headers } from "next/headers";
import { getSdk } from "@/sdk";

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

  return (
    // ...
  );
}
Pages Router
import { GetServerSideProps } from "next";
import { getSdk } from "@/sdk";

export default function SsrPage({ result }: any) {
  return (
    // ...
  );
}

export const getServerSideProps: GetServerSideProps = async ({ req }) => {
  const sdk = getSdk({
    getRequestHeaders: () => req.headers,
  });

  return {
    props: {
      result: await sdk.commerce.getProduct({ id: "123" }),
    },
  };
};

Add cookies to the request during CSR

Cookies are being sent with the request during Client-Side Rendering by default.

However, you can always specify the headers manually by passing them to the defaultRequestConfig option of the middlewareModule...

export const sdk = initSDK({
  commerce: buildModule(middlewareModule<UnifiedEndpoints>, {
    apiUrl: "http://localhost:8181/commerce",
    defaultRequestConfig: {
      headers: {
        Cookie: "name=value",
      },
    },
  }),
});

...or by passing them to the request configuration.

import { prepareConfig } from "@vue-storefront/sdk";

const product = await sdk.commerce.getProduct(
  { id: "123" },
  prepareConfig({
    headers: {
      Cookie: "name=value",
    },
  })
);

Use the middlewareModule with a custom integration

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

This example assumes that you have a custom Api Client that has been implemented as described in the Creating an API Client guide. It also assumes that the integration has been added to the middleware.config.ts and it's available at the /custom endpoint.

First, let's export the Endpoints type as CustomEndpoints from the custom integration package. That way, you won't need to install the custom integration package on the frontend and you'll avoid naming conflicts.

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

// ...

Then, let's initialize the SDK with the middlewareModule and the CustomEndpoints type.

import { initSDK, buildModule, middlewareModule } from "@vue-storefront/sdk";
import { CustomEndpoints } from "storefront-middleware/types";

export const sdk = initSDK({
  custom: buildModule(middlewareModule<CustomEndpoints>, {
    apiUrl: "http://localhost:8181/custom",
  }),
});

Now, you can use the custom module to make requests to the custom integration endpoints.

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