Add a new integration
Add a new data source in the SHOPin storefront accelerator by creating a package under integrations/, implementing a service provider and NestJS services that satisfy @core/contracts, then registering the module in the BFF DataSourceFactory.
Use integrations/commercetools-api or integrations/mock-api as blueprints. The walkthrough uses the placeholder name example-api—rename types, tokens, and env vars to match your provider. Steps and code are illustrative; align file names and peer dependency versions with a live integration in the repo. Add the package under integrations/ so it is included by the root package.json workspaces.
1. Create the package directory
mkdir integrations/example-api
cd integrations/example-api
2. Package metadata
Add package.json, tsconfig.json, and any SDK dependencies your API needs. Mirror scripts and peerDependencies from an existing integration rather than copying version numbers from this page.
package.json (shape only):
{
"name": "@integrations/example-api",
"version": "0.0.0",
"private": true,
"exports": {
".": {
"types": "./dist/index.d.ts",
"default": "./dist/index.js"
}
},
"scripts": {
"build": "tsc --build",
"dev": "tsc --build --watch",
"lint": "eslint --fix ./src --max-warnings=0",
"format": "prettier --write ./src",
"check-types": "tsc --noEmit"
},
"dependencies": {
"@core/contracts": "*",
"@core/i18n": "*",
"@config/constants": "*"
},
"devDependencies": {
"@core/eslint-config": "*",
"@core/prettier-config": "*",
"@core/typescript-config": "*"
},
"peerDependencies": {
"@nestjs/common": "^11.1.3",
"@nestjs/config": "^4.0.2"
}
}
tsconfig.json
{
"extends": "@core/typescript-config/nestjs.json",
"compilerOptions": {
"outDir": "dist",
"emitDecoratorMetadata": true,
"experimentalDecorators": true
},
"include": ["src"],
"exclude": ["dist", "node_modules"]
}
3. Service provider
The BFF DataSourceFactory calls getServices() on a registered provider token. Each integration supplies:
interfaces.ts— exports the provider type asBaseServiceProvider<ReturnType<…Impl['getServices']>>(data-source-interfaces.ts). That ties the return shape toPartial<AllServices>(same keys the BFF expects across data sources). The file **import type**s the implementation class only.<integration-name>-service-provider.ts— Nest@Injectable()class thatimplementsthat type, injects domain services fromsrc/services/, andgetServices()returns an object whose keys are service names and values are those instances.
Reference: mock-api, commercetools-api. An auth provider package such as commercetools-auth uses BaseAuthServiceProvider and getAuthServices() instead.
// src/interfaces.ts
import type { BaseServiceProvider } from '@core/contracts/core/data-source-interfaces'
import type { ExampleServiceProviderImpl } from './example-service-provider'
export type ExampleServiceProvider = BaseServiceProvider<
ReturnType<ExampleServiceProviderImpl['getServices']>
>
// src/example-service-provider.ts
import { Injectable } from '@nestjs/common'
import type { ExampleServiceProvider } from './interfaces'
import { ProductService } from './services/product.service'
import { NavigationService } from './services/navigation.service'
@Injectable()
export class ExampleServiceProviderImpl implements ExampleServiceProvider {
constructor(
private readonly productService: ProductService,
private readonly navigationService: NavigationService
) {}
getServices() {
return {
productService: this.productService,
navigationService: this.navigationService,
}
}
}
Domain classes in src/services/ implement @core/contracts (step 5); this class only composes them.
4. API client module
src/client/client.module.ts — Wire ConfigModule / ConfigService, expose a client token (e.g. EXAMPLE_CLIENT), return your SDK handle from useFactory.
import { Module } from '@nestjs/common'
import { ConfigModule, ConfigService } from '@nestjs/config'
export const EXAMPLE_CLIENT = 'EXAMPLE_CLIENT'
@Module({
imports: [ConfigModule],
providers: [
{
provide: EXAMPLE_CLIENT,
inject: [ConfigService],
useFactory: (configService: ConfigService) => {
const token = configService.getOrThrow<string>('EXAMPLE_TOKEN')
const url = configService.getOrThrow<string>('EXAMPLE_URL')
return { url, token }
},
},
],
exports: [EXAMPLE_CLIENT],
})
export class ExampleClientModule {}
Document new variable names in the package README when you contribute the integration upstream.
5. Domain services
Implement each contract interface under src/services/, export from src/services/index.ts.
import { Injectable } from '@nestjs/common'
import type { ProductService as ProductServiceContract } from '@core/contracts/core/data-source-interfaces'
import type { ProductResponse } from '@core/contracts/product/product'
@Injectable()
export class ProductService implements ProductServiceContract {
async getProduct(
productKey: string,
variantId?: string
): Promise<ProductResponse> {
// Implement API calls here; map data to contract format and return
throw new Error('Not implemented')
}
}
6. Main NestJS module
Name the file <integration-name>-api.module.ts (e.g. example-api.module.ts). Existing integrations typically use @Global().
import { Global, Module } from '@nestjs/common'
import { ExampleServiceProviderImpl } from './example-service-provider'
import { ProductService } from './services/product.service'
import { NavigationService } from './services/navigation.service'
import { ExampleClientModule } from './client/client.module'
@Global()
@Module({
imports: [ExampleClientModule],
providers: [
ProductService,
NavigationService,
{
provide: 'EXAMPLE_SERVICE_PROVIDER',
useClass: ExampleServiceProviderImpl,
},
],
exports: ['EXAMPLE_SERVICE_PROVIDER'],
})
export class ExampleApiModule {}
7. Package exports
src/index.ts
export * from './example-api.module'
export * from './client/client.module'
export * from './services'
export * from './interfaces'
8. Data source key (optional)
To select the integration via config, extend config/constants/src/data-source/index.ts (types, ALLOWED_DATA_SOURCES, default)—illustrative names:
export type DataSource = 'mock-set' | 'commercetools-set' | 'example-set'
export const ALLOWED_DATA_SOURCES: readonly DataSource[] = [
'mock-set',
'commercetools-set',
'example-set',
] as const
export const DEFAULT_DATA_SOURCE: DataSource = 'mock-set'
Use the same string in the BFF switch and in the runtime configuration that sets DATA_SOURCE (or your project’s equivalent).
9. BFF DataSourceFactory
In apps/bff/src/data-source/data-source.factory.ts, inject the new provider token and return its getServices() from the appropriate branch (dedicated case, mixed composition, or single provider). See Data sources.
import type { ExampleServiceProvider } from '@integrations/example-api'
constructor(
@Inject('EXAMPLE_SERVICE_PROVIDER')
private readonly exampleServiceProvider: ExampleServiceProvider,
) {}
getServices() {
switch (this.dataSource) {
case 'commercetools-set':
return this.commercetoolsServiceProvider.getServices()
case 'mock-set':
return this.mockServiceProvider.getServices()
case 'example-set':
return this.exampleServiceProvider.getServices()
default:
throw new Error(
`Unknown data source: ${this.dataSource}. Allowed values: ${ALLOWED_DATA_SOURCES.join(', ')}`
)
}
}
10. BFF DataSourceModule
Import the new module in apps/bff/src/data-source/data-source.module.ts:
import { ExampleApiModule } from '@integrations/example-api'
@Module({
imports: [
MockApiModule,
CommercetoolsApiModule,
ExampleApiModule,
],
})
Wire @integrations/example-api (or your package name) into apps/bff/package.json dependencies if the monorepo does not already resolve it.
11. Package README and tooling
- Add README.md listing required env vars and any setup notes for this integration only.
- Copy
.gitignore,.lintstagedrc, eslint, prettier,turbo.jsonfromintegrations/mock-apiorintegrations/commercetools-api.
12. Verify
From the repository root, run npm run build (and your usual typecheck) so the new package and BFF compile. Exercise the new branch with DATA_SOURCE (or your equivalent) set to your new key.
Summary
| Step | Outcome |
|---|---|
Package under integrations/ | NestJS module + provider token + services |
| Contracts | Types from @core/contracts / data-source interfaces |
| Config | Optional new DataSource union + env |
| BFF | Factory branch + DataSourceModule import + BFF dependency |