# Multistore

Multistore allows you to build multiple stores with a single instance of the Vue Storefront application. This document will guide you through the configuration process.

# Requirements

  • Vue Storefront EPCC 1.0.0-rc.2 or later

# Configuration

# Prepare multistore configuration

In order to configure multistore, you have to provide a multistore property containing:

  • storeConfigCacheStorageFactory - an asynchronous function that returns cache storage instance for configurations of stores. It has to return an object implementing StoreConfigCacheStorage interface:
export interface StoreConfigCacheStorage {
  /**
   * Gets a store configuration from cache storage based on the `key` value.
   */
  get(key: string): Promise<StoreConfig>,
  /**
   * Sets a store configuration in the cache storage with identifier equals `key` value.
   */
  set(key: string, value: StoreConfig): Promise<StoreConfig>,
}
  • updateFn - an asynchronous function that returns the configuration of one or more stores. It has to implement the UpdateFnType type:










 

import { ConfigOptions } from '@moltin/sdk';

type StoreConfig = ConfigOptions & {
  client_id: string;
  client_secret: string;
  publicConfig: Record<string, any>,
  [key: string]: any
};

type Host = string;
type UpdateFnType = (domain: string) => Promise<Record<Host, StoreConfig>>

# Example implementation

Create a new file called multistore.config.js in the root directory of your project:

// multistore.config.js
const NodeCache = require('node-cache');

module.exports = {
  async storeConfigCacheStorageFactory () {
    const client = new NodeCache({
      stdTTL: 3600
    });

    return {
      async get(key) {
        return client.get(key);
      },
      async set(key, value) {
        return client.set(key, value);
      }
    };
  },
  async updateFn () {
    return {
      'de.clothes.local': {
        client_id: 'some-client-id',
        client_secret: 'some-client-secret'
      },
      'pl.clothes.local': {
        client_id: 'some-other-client-id',
        client_secret: 'some-othet-client-secret'
      },
      'en.jewelery.local': {
        client_id: 'some-even-different-client-id',
        client_secret: 'some-even-different-client-secret'
      }
    };
  }
};

Then, import the configuration and add it to the epcc configuration object in the middleware.config.js:



 






 




























// middleware.config.js
require('dotenv').config();
const multistore = require('./multistore.config');

module.exports = {
  integrations: {
    epcc: {
      location: '@vsf-enterprise/epcc-api/server',
      configuration: {
        multistore, // add multistore configuration
        // You can remove global client_id and client_secret if the multistore configuration is available
        // client_id: 'some-22-client-id',
        // client_secret: 'some-22-client-secret',
        secure_cookies: process.env.NODE_ENV === 'production',
        forgotten_password_token_expiration: '10m',
        tax_data: {
          en: {
            default: {
              rate: 0.2
            },
            reduced: {
              rate: 0.3
            }
          },
          pl: {
            default: {
              rate: 0.23
            },
            medical: {
              rate: 0.08
            }
          }
        }
      }
    }
  }
};

# Testing locally

The simplest way is adding new domains to the /etc/hosts file:


 
 
 

127.0.0.1	localhost
127.0.0.1	de.clothes.local
127.0.0.1	pl.clothes.local
127.0.0.1	en.jewelery.local

Then you can use custom domains locally - with one store available at http://pl.clothes.local:3000 and another at http://de.clothes.local:3000.

# Public part of the store configuration

It's possible to expose parts of each store's configuration by declaring a publicConfig property. Properties inside publicConfig will be available from your Nuxt app.

// multistore.config.js
module.exports = {
  // ...
  async updateFn (domain) {
    return {
      'de.clothes.local': {
        client_id: 'some-client-id',
        client_secret: 'some-client-secret',
        publicConfig: {
          greeting: 'Tschüs!'
        }
      },
      'pl.clothes.local': {
        client_id: 'some-other-client-id',
        client_secret: 'some-othet-client-secret',
        publicConfig: {
          greeting: 'Cześć!'
        }
      },
      'en.jewelery.local': {
        client_id: 'some-even-different-client-id',
        client_secret: 'some-even-different-client-secret',
        publicConfig: {
          greeting: 'Hi!'
        }
      }
    };
  }
};

Then, these properties will be available in your Nuxt app as part of the context object by calling $vsf.$epcc.api.getCurrentStorePublicConfig().

import {
  onMounted,
  useContext
} from '@nuxtjs/composition-api';

export default {
  setup() {
    // ...
    const { $vsf } = useContext();

    onMounted(async () => {
      const publicCfg = await $vsf.$epcc.api.getCurrentStorePublicConfig();
      console.log(publicCfg.greeting); // Prints 'Tschüs!', 'Cześć!' or 'Hi!' depending on the store
    });
  }
};

# Accessing the current store's configuration in the extendApiMethods

Configuration consists of merged base middleware's configuration and fetched current store's configuration excluding multistore and storage keys. It's accessible via context.config.











 









module.exports = {
  integrations: {
    '{INTEGRATION_NAME}': {
      // ...
      extensions: (extensions) => [
        ...extensions,
        {
          name: 'extension-name',
          extendApiMethods: {
            customMethod: async (context, params) => {
              const configuration = context.config;
              return {}
            }
          }
        }
      ],
    }
  }
};

For the mywebsite.vsf as a host and the following configuration:

// middleware.config.js
module.exports = {
  integrations: {
    epcc: {
      location: '@vsf-enterprise/epcc-api/server',
      configuration: {
        multistore: {
          async updateFn () {
            'mywebsite.vsf': {
              client_id: 'cid-1',
              client_secret: 'csecret-1',
              someSecretCustomProperty: 'overwriten-secret',
              publicConfig: {
                algoliaPublicKey: '1232'
              }
            }
          }
        },
        someSecretCustomProperty: 'base-secret',
        forgotten_password_token_expiration: '10m'
      }
    }
  }
};

The value of the configuration would be equal:

{
  forgotten_password_token_expiration: '10m',
  client_id: 'cid-1',
  client_secret: 'csecret-1',
  someSecretCustomProperty: 'overwriten-secret',
  publicConfig: {
    algoliaPublicKey: '1232'
  }
}

# Returning only the requested domain's configuration from updateFn

If the configurations fetched inside updateFn are very large, you might want to only return the configuration for an exact domain. You can do this with the /config/domain/${domain} endpoint.

const axios = require('axios'); // you have to install Axios at first

// multistore.config.js
module.exports = {
  // ...
  async updateFn (domain) {
    return await axios.get(`/config/domain/${domain}`);
  }
};

# How it works

The application:

  1. Looks for Origin header in the request with a fallback to the Host header.
  2. Normalizes the Origin/Header (e.g. https://pl.clothes.local to pl.clothes.local)
  3. Checks if the normalized value is present in the cache using the get method returned by storeConfigCacheStorageFactory, if so, returns it.
  4. Otherwise, it looks for a key with mentioned value in the configuration storage returned by updateFn:
const normalizedOrigin = 'pl.clothes.local';
const configurationStorage = await updateFn();

// Whole config:
return configurationStorage[normalizedOrigin];

// Public config:
return configurationStorage[normalizedOrigin]?.publicConfig;
  1. Saves configuration of each fetched store in the cache using the set method returned by storeConfigCacheStorageFactory.

# Setting optional properties of Moltin client per store

Return additional properties from updateFn.

WARNING

storage and custom_fetch keys are used internally by API Client, so passing them won't work, as values will be overwritten when passing to the Moltin client.

// multistore.config.js
async updateFn () {
  return {
    'de.clothes.local': {
      client_id: 'some-client-id',
      client_secret: 'some-client-secret',
      host: 'a-different-domain.com',
      custom_property: '123'
    },
    'pl.clothes.local': {
      client_id: 'some-other-client-id',
      client_secret: 'some-othet-client-secret',
      language: 'PL'
    },
    'en.jewelery.local': {
      client_id: 'some-even-different-client-id',
      client_secret: 'some-even-different-client-secret'
    }
  };
}