BFF
The Backend for Frontend in the SHOPin storefront accelerator is a NestJS app under apps/bff. It sits between the presentation layer and data sources, exposes HTTP routes the storefront calls through the BFF client, and keeps payloads aligned with @core/contracts. Shared configuration literals (not API DTOs) stay in Config & constants.
Runtime: NestJS — modules, controllers, providers, middleware.
Layout
Under apps/bff/src/:
app.module.ts— Root module; register feature modules inimportsand global middleware here when needed.common/— Cross-cutting code: exception filters, language handling, Zod validation helpers, storage, tokens.data-source/—data-source.factory.tsanddata-source.module.ts; wires the active integration set (e.g. mock, Commercetools). Details in Data sources.features/— Domain slices:*.module.ts,*.controller.ts,*.service.ts(plus guards or interceptors when used).i18n/— Locale payloads for the storefront; pairs with Translations andcore/i18n.
Folder names beyond these are illustrative—inspect the repo for the current tree.
Module shape
| Piece | File | Role |
|---|---|---|
| Module | [name].module.ts | Imports, providers, exports |
| HTTP | [name].controller.ts | Routes, request/response handling |
| Service | [name].service.ts | Orchestration; calls DataSourceFactory (or other BFF services) |
NestJS controllers · NestJS providers
Example: controller
Illustrative — navigation may differ in your checkout of the repo. Controllers delegate to services and parse responses with contract schemas:
import { Controller, Get } from '@nestjs/common'
import { NavigationService } from './navigation.service'
import { ApiTags, ApiOkResponse } from '@nestjs/swagger'
import { MainNavigationResponseSchema } from '@core/contracts/navigation/main-navigation'
import type { MainNavigationResponse } from '@core/contracts/navigation/main-navigation'
@Controller('navigation')
@ApiTags('navigation')
export class NavigationController {
constructor(private readonly navigationService: NavigationService) {}
@Get()
@ApiOkResponse({
description: 'Navigation data retrieved successfully',
})
async getNavigation(): Promise<MainNavigationResponse> {
return MainNavigationResponseSchema.strip().parse(
await this.navigationService.getNavigation()
)
}
}
Feature services
BFF services typically:
- Delegate — Use
DataSourceFactory.getServices()for integration-backed operations (product, cart, navigation, etc.). - Combine — Join several integration services in one handler when the contract aggregates multiple sources.
- Stay consistent — Errors and logging follow Error handling and Logging.
Example: single integration service
@Injectable()
export class ProductService {
constructor(private readonly dataSourceFactory: DataSourceFactory) {}
async getProductPage(
productSlug: string,
variantId?: string
): Promise<ProductPageResponse> {
const { productService } = this.dataSourceFactory.getServices()
const response = await productService.getProduct(productSlug, variantId)
return {
breadcrumb: response.breadcrumb,
product: response.product,
}
}
}
Example: multiple services in one response
Illustrative — not every project exposes contentService / searchService; align with data-source.factory.ts and contracts.
async getPdpData(productKey: string, variantId?: string): Promise<ProductPageResponse> {
const { productService, contentService, searchService } = this.dataSourceFactory.getServices()
const [productData, teasers, relatedProducts] = await Promise.all([
productService.getProduct(productKey, variantId),
contentService.getTeasersForProduct(productKey),
searchService.getRelatedProducts(productKey),
])
return {
...productData,
content: { teasers },
recommendations: { relatedProducts },
}
}
Checklist
| Topic | Expectation |
|---|---|
| Data access | Use DataSourceFactory for integration code paths — Data sources |
| Contracts | Request/response shapes from @core/contracts — Maintain contracts |
| Swagger | Decorate routes as the codebase expects (tags, response metadata). |
| Input validation | Zod / pipes consistent with common/validation — Input validation |
| Errors | Stable HTTP responses; no internal stack traces to clients — Error handling |
| Logging | Structured logs — Logging |
DataSourceModule (see data-source.module.ts) registers integration modules and the factory so feature modules stay vendor-agnostic.