Vue Storefront is now Alokai! Learn More
Creating a Custom Integration

Creating a Custom Integration

If you're looking to create a custom integration for Alokai, you're in the right place. This guide will walk you through the process of creating a custom integration from scratch.

Generating integration boilerplate is supported since @alokai/cli@v1.1.0.

To help you scaffold a project, you can use our CLI to generate the boilerplate for your integration. To do that, run the following command:

yarn alokai-cli integration generate <integration-name>

You can check the examples and flags with their descriptions by running yarn alokai-cli integration generate --help.

Code structure

The command generates code in two main locations:

  1. apps/storefront-middleware/integrations/<integration-name> - Contains the code for the Alokai Middleware.
  2. apps/storefront-unified-<framework>/integrations/<integration-name> - Contains the SDK module that will be used in the Storefront.

Alokai Middleware

An integration used in the Alokai Middleware serves as a bridge between the Storefront and the 3rd party service (like commerce backend).

The structure of the Alokai Middleware integration is the following:

apps/storefront-middleware/integrations/<integration-name>/
├── ai/                           # Instructions for the AI agents
├── api/                          # API endpoints
├── client/                       # HTTP Client implementation
├── extensions/                   # Extensions for the integration
│   └── unified/                  # Unified extension
│       ├── api/                  # Unified API method implementations
│       ├── commons/              # Shared utilities
│       ├── normalizers/          # Unified data normalizers
│       └── types/                # Unified type definitions
├── types/                        # Integration-specific types
├── config.ts                     # Config for the Alokai Middleware
├── index.server.ts              # Entry point of the integration
└── index.ts                     # Exports the config and unified extension

SDK Module

The command generates two SDK modules that will be used to communicate with the integration registered in the Alokai Middleware and its unified extension.

The structure of the SDK modules is the following:

apps/storefront-unified-<framework>/sdk/modules/<integration-name>/
├── <integration-name>.ts                      # SDK module to communicate with the integration's raw API
└── unified-<integration-name>.ts              # SDK module to communicate with the integration's unified extension

How to use the integration

1

First, you need to register the integration in the Alokai Middleware.

+ import { config as <integration-name>Config } from './integrations/<integration-name>';

export const config = {
  integrations: {
+   <integration-name>: <integration-name>Config,
  },
};

2

Then, export the SDK modules from the sdk/modules/index.ts file.

+ export * from './<integration-name>';
+ export * from './unified-<integration-name>';

3

Now, you can use the integration in the Storefront.

const sdk = await getSdk();

await sdk.<integration-name>.exampleMethod({ id: '1' });
await sdk.unified<integration-name>.getCart({ cartId: '1' });

AI support

Integration boilerplate ships with instructions for AI code editors (e.g., Windsurf or Cursor). They will help the editor's agent to:

  • implement an HTTP client connecting the boilerplate with the desired 3rd party platform (e.g., commerce or CMS),
  • implement API methods fetching non-unified data from the 3rd party platform (e.g., product data),
  • implement Unified API methods fetching non-unified data via an existing API method and normalizing it to the unified format with a normalizer.

For the instructions to work, the content of the /ai/general-rules.md file has to be added to the main AI context file (e.g., .windsurfrules or .cursorrules).

Raw API, Unified API, and Normalizers

The integration consists of three main components that work together to provide a seamless experience:

  1. Raw API Methods - These methods communicate directly with the 3rd party service (e.g., commerce backend) and return data in its native format.
  2. Unified API Methods - These methods use raw API methods and normalizers to transform the data into a unified format that can be consumed by the Storefront. Here's an example:
import type { SfProduct } from '@alokai/connect';
import { getNormalizers } from '@alokai/connect/integration-kit';

import { defineApi } from '../../commons';

export const getProducts = defineApi.getProducts(async (context) => {
  const { normalizeProductCatalogItem } = getNormalizers(context);

  // Call the raw API method to fetch data from the 3rd party service
  const result = await context.api.exampleMethod({ id: '1' });

  // Normalize the data to the unified format
  const rawProducts: SfProduct[] = result.data;
  const products = rawProducts.map(normalizeProductCatalogItem);

  // Return the normalized data to the Storefront
  return { products };
});
  1. Normalizers - These are functions that transform data from the 3rd party service's format into the unified format expected by the Storefront. They are used within unified API methods to ensure consistent data structure across different integrations.

The flow of data is as follows:

  1. The Storefront calls a unified API method
  2. The unified API method calls a raw API method to fetch data from the 3rd party service
  3. The raw data is then normalized using normalizers
  4. The normalized data is returned to the Storefront

This architecture ensures that the Storefront always receives data in a consistent format, regardless of the underlying 3rd party service.

Example getProducts method

To illustrate the data flow, let's examine the getProducts method. We'll use Fake Store API as our example commerce backend.

1

Create a new file at my-integration/api/getProducts/index.ts with the following content:

import { IntegrationContext } from '../../types/context';

export interface FakeStoreProduct {
  id: number;
  title: string;
  price: number;
  description: string;
  category: string;
  image: string;
  rating: {
    rate: number;
    count: number;
  };
}

/**
 * Fetches products from the Fake Store API
 * 
 * @param context - The integration context containing the HTTP client
 * @returns Promise containing an array of products with their details
 */
export async function getProducts(context: IntegrationContext): Promise<FakeStoreProduct[]> {
  const response = await context.client('https://fakestoreapi.com/products');
  const data = await response.json();
  return data as FakeStoreProduct[];
}

2

Export the method in my-integration/api/index.ts:

export * from './getProducts';

3

Implement the Product Catalog Normalizer

Open my-integration/extensions/unified/normalizers/product/catalog.ts and import the FakeStoreProduct type:

import type { FakeStoreProduct } from '../../../../api/getProducts';

Then, update the normalizer to handle Fake Store data:

export const normalizeProductCatalogItem = defineNormalizer.normalizeProductCatalogItem(
  (context, rawProduct: FakeStoreProduct) => {
    const primaryImage = context.normalizers.normalizeImage({
      url: rawProduct.image,
      alt: rawProduct.title,
    });
    
    const price = context.normalizers.normalizeDiscountablePrice({
      value: rawProduct.price,
      currency: 'USD',
    });
    
    const rating = context.normalizers.normalizeRating({
      value: rawProduct.rating.rate,
      count: rawProduct.rating.count,
    });

    return {
      id: rawProduct.id.toString(),
      name: rawProduct.title,
      price: price,
      primaryImage: primaryImage,
      quantityLimit: 100,
      rating: rating,
      sku: `FS-${rawProduct.id}`,
      slug: rawProduct.title.toLowerCase().replace(/\s+/g, '-'),
    };
  },
);

4

Implement the Unified API Method

Open my-integration/extensions/unified/api/product/getProducts.ts and import the necessary types and utilities:

import type { SfProduct } from '@alokai/connect';
import { getNormalizers } from '@alokai/connect/integration-kit';
import { defineApi } from '../../commons';

Finally, implement the unified getProducts method:

export const getProducts = defineApi.getProducts(async (context) => {
  const { normalizeProductCatalogItem } = getNormalizers(context);

  const rawProducts = await context.api.getProducts();
  const products = rawProducts.map(normalizeProductCatalogItem);

  return { products };
});

5

Test the Integration

To test the integration, call the getProducts and unified getProducts methods in the Storefront:

const sdk = await getSdk();

const products = await sdk.unified.getProducts();
console.log(products);

const rawProducts = await sdk.getProducts();
console.log(rawProducts);

The method should return the products in the unified format.