# 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 implementingStoreConfigCacheStorage
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 theUpdateFnType
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:
- Looks for
Origin
header in the request with a fallback to theHost
header. - Normalizes the
Origin
/Header
(e.g.https://pl.clothes.local
topl.clothes.local
) - Checks if the normalized value is present in the cache using the
get
method returned bystoreConfigCacheStorageFactory
, if so, returns it. - 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;
- Saves configuration of each fetched store in the cache using the
set
method returned bystoreConfigCacheStorageFactory
.
# 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'
}
};
}