Skip to main content
Version: next

Adding a configuration provider

Discover how configuration providers customize your app's settings, ensuring efficient and tailored functionality for every server request.

What is a configuration provider?

The goal is to give access to a configuration object that contains all the configuration of the store. This configuration object can contain any kind of configuration. It can be flags about a feature activation, some credentials to connect to a remote service, etc.

However, not all applications will need every single configuration. For instance, a shop that chose to use Algolia won't have the same configurations than a shop that chose Elasticsearch for product search. The goal of the configuration providers is to define the configurations needed for the specific modules you use in your application.

On server start, the configuration providers will then be combined to create the global configuration object. This configuration object will then be available on each server request.

This can be illustrated by starting your Front-Commerce server in development mode. If your server is running on http://localhost:3000 and you open the URL /__front-commerce/debug, you will have a dump of the configuration for this specific request under the section config.

{
"allShops": {
"store:default": {
"id": "default",
"url": "http://localhost:3000",
"locale": "en-GB",
"magentoStoreCode": "default",
"currency": "EUR"
},
"store:fr": {
"id": "fr",
"url": "http://fr.localhost:3000",
"locale": "fr-FR",
"magentoStoreCode": "fr",
"currency": "EUR"
}
},
"currentShopId": "default",
"shop": {
"id": "default",
"url": "http://localhost:3000",
"locale": "en-GB",
"magentoStoreCode": "default",
"currency": "EUR"
},
"cache": {
"defaultMaxBatchSize": 100,
"strategies": [
{
"implementation": "Redis",
"supports": "*",
"disabledFor": [],
"config": {
"host": "localhost"
}
}
]
},
"contentSecurityPolicy": {
"__dangerouslyDisable": false,
"directives": {
"scriptSrc": [],
"frameSrc": [],
"styleSrc": [],
"imgSrc": [],
"fontSrc": [],
"connectSrc": [],
"frameAncestors": [],
"baseUri": []
},
"reportOnly": {
"scriptSrc": false,
"frameSrc": false,
"styleSrc": false,
"imgSrc": false,
"fontSrc": false,
"frameAncestors": false,
"connectSrc": false
}
},
"cors": {
"origin": {
"referal-0": "http://magento23.test"
}
},
"device": {
"memoizationMaxAge": 60000,
"type": "pc",
"viewportWidthInPx": 1400
},
"public": {
"analytics": {
"disableDevWarning": false
},
"deprecations": {
"ignore": "",
"trace": ""
},
"compatEnv": {
"FRONT_COMMERCE_WEB_DEV_ANALYTICS_WARNING_DISABLE": "true"
},
"password": {
"disableHint": false
}
"shop":{
"id": "default",
"url": "http://localhost:3000",
"locale": "en-GB",
"currency": "EUR"
}
},
"magento": {
"endpoint": "http://magento23.test",
"version": "2",
"proxiedPaths": []
}
}
tip

When the FRONT_COMMERCE_ENV environment is not set to production, for example in a staging environment, the configuration will still be available in the __front-commerce/debug page, but the page will be secured with a token configurable with the FRONT_COMMERCE_DEBUG_TOKEN environment variable.

Then you can access the endpoint with the token in the URL: /__front-commerce/debug?token=your-token.

Configuration provider definition

In practice, a configuration provider is an object with five properties: a name, a schema, static values (available independently from the request), a slowValuesOnEachRequest function to resolve values depending on the request and a fetchRemoteConfiguration function to fetch values remotely.

const serviceConfigurationProvider = {
name,
schema,
values,
slowValuesOnEachRequest,
fetchRemoteConfiguration,
};

See the sections below to understand what each key stands for.

name (mandatory)

The identifier of the configuration provider. This is needed for configuration providers' registration. See Inject a configuration provider for more details.

const name = "serviceProvider";

schema (optional)

It's a function returning an object representing the definition of the fields that will appear in the global configuration object if the configuration provider is registered. It can define things like field formats, default values or environment variables.

The schema definition is based on convict, a library developed by Mozilla to validate configurations. For a single field, the schema will have these keys:

  • doc (string): A description of the field and pointers to learn how to get its value if it requires a manual operation
  • format (string, array or function): The formatter used for this field's value. See how validation works in convict.
  • default (any): A default value. If you don't have a default value, you still have to set one by using the null. If there is no default value, it won't be considered as a field definition.
  • env (string, optional): The name of the environment variable that should be used as the value. If none is given, the configuration won't be configurable by environment. Please keep in mind that environment variables should still match Front-Commerce's naming convention.

Thus, if we want to define a configuration named serviceKey available in req.config.serviceKey that would take its value from the environment variable FRONT_COMMERCE_SERVICE_KEY, we would use this schema definition:

const schema = () => ({
serviceKey: {
doc: "The key to get access to our remote service",
format: String,
default: null,
env: "FRONT_COMMERCE_SERVICE_KEY",
},
});

A configuration provider's schema is not limited to a single field though. You can set multiple configuration keys but also nest deeper objects. For instance, if we want to group a key and a secret into a single service key, we would write the following schema:

const schema = () => ({
service: {
key: {
doc: "The key to get access to our remote service",
format: String,
default: null,
env: "FRONT_COMMERCE_SERVICE_KEY",
},
secret: {
doc: "The secret to get access to our remote service",
format: String,
default: null,
env: "FRONT_COMMERCE_SERVICE_SECRET",
},
},
});

You could then access the values with req.config.service.key or req.config.service.secret.

Please note that if another configuration provider's schema also defined a service key, it would merge the definitions and in your final req.config.service you would have both the keys from the other configuration provider's schema and from the above schema.

values (optional)

Most of the time default values in the schema is sufficient. However, in some cases you may need to fetch some values from an API, a dynamic file, etc. This is what values is for.

It is an optional promise that should return the missing values in your schema. It will override default values and env values from the schema with the new values. However only keys from the values result will take precedence, the default values and env values will be kept for other keys. For instance, if we implemented the following values promise, the secret would still be process.env.FRONT_COMMERCE_SERVICE_SECRET from the above schema.

const values = fetch("https://api.example.com/my-service-key")
.then((response) => response.json())
.then((key) => ({
service: {
key: key,
},
}));

Please note that this promise is launched only once on server bootstrap. If the configuration changes over time on your API, the req.config.service.key value will still be the same.

If it needs to change over time, please use slowValuesOnEachRequest or fetchRemoteConfiguration instead.

slowValuesOnEachRequest (optional)

note

slowValuesOnEachRequest is a low level API in Front-Commerce. You shouldn't be need it in most cases.

slowValuesOnEachRequest is a function that extracts configuration values from the current request. For instance, depending on the URL, the configuration currentShopId will get a different id and thus display different information.

const slowValuesOnEachRequest = (req) => {
const url = req.originalUrl;
const shopId = getShopIdFromUrl(url);
return {
currentShopId: shopId,
};
};
caution

This part is named slowValuesOnEachRequest because we want to stress the fact that it can have a huge impact on your website performance. Avoid its usage as much as you can and try to use values instead. If this is the only way to setup your configuration make sure to memoize its result to limit the performance impact as much as possible.

import memoize from "lodash/memoize";

const getShopIdFromHostname = memoize((hostname) => {
/* The definition of the `currentShopId` variable should live here */
return {
currentShopId: currentShopId,
};
});

const slowValuesOnEachRequest = (req) => {
const hostname = req.hostname;
return getShopIdFromHostname(req.hostname);
};

fetchRemoteConfiguration (optional)

note

fetchRemoteConfiguration is a low level API in Front-Commerce. You shouldn't need it in most cases.

fetchRemoteConfiguration is a function that is called at a later stage in Front-Commerce request handling cycle to receive a fully initialized request object, so that, for instance, you can instantiate a loader to retrieve some configuration from Magento.

caution

Like slowValuesOnEachRequest, fetchRemoteConfiguration can have a huge impact on the performances. If you really need to implement it, please make sure to memoize its result to limit the performance impact as much as possible.

Inject a configuration provider

Assuming that we have defined the following configuration provider:

./config-providers/acmeConfigProvider.ts
export default {
name: "acme-config",
schema: () => ({
service: {
key: {
doc: "The key to get access to our remote service",
format: String,
default: null,
env: "FRONT_COMMERCE_SERVICE_KEY",
},
secret: {
doc: "The secret to get access to our remote service",
format: String,
default: null,
env: "FRONT_COMMERCE_SERVICE_SECRET",
},
},
}),
};

From your application configuration

You can add the configuration to your front-commerce.config.ts file.

front-commerce.config.ts
import { defineConfig } from "@front-commerce/core/config";
import themeChocolatine from "@front-commerce/theme-chocolatine";
import acmeConfigProvider from "./config-providers/acmeConfigProvider";

export default defineConfig({
extensions: [themeChocolatine()],
configuration: {
providers: [acmeConfigProvider],
},
});

From an extension

Alternatively, you can add a configuration provider from an extension in the extensions definition.

./acme-extension.ts
import { defineExtension } from "@front-commerce/core";
import acmeConfigProvider from "./config-providers/acmeConfigProvider";

export default defineExtension({
name: "acme-extension",
configuration: {
providers: [acmeConfigProvider],
},
});
info

You can learn more about extensions in the Register an extensions guide.