Migrate an existing BFF to the Commerce Integration Layer

Ask about this Page
Copy for AI
View as Markdown

For existing backend-for-frontend (BFF) systems not already built on top of the Commerce Integration Layer, this page explains why to migrate and how to solve common migration patterns in the layer.

Why migrate

Operating a custom backend-for-frontend (BFF) requires maintaining hosting infrastructure, scaling rules, security controls, and third-party integration code.

Migrating to the Commerce Integration Layer lets you focus on building the commerce experience while commercetools handles the operational complexity of running the BFF:

  • Built-in commercetools platform support: leverage commercetools commerce expertise directly in your architecture. Rather than building custom middleware to manage tokens, resolve catalog structures, or replicate platform logic, you get a unified Experience API whose typed GraphQL schema is automatically derived from your Project configuration (including Product Types, attributes, and Custom Fields). Complex commerce capabilities (such as shopper session lifecycle management, automatic cart merging upon customer sign-in, and presentment scoping by locale, currency, and country) work by default. The concrete, strongly typed GraphQL schema supports automated TypeScript code generation and IDE autocompletion.
  • Managed operations and performance: commercetools owns the underlying infrastructure, provisioning, global deployment, autoscaling under peak traffic, load balancing, and encrypted secret management. Built-in CLI tools validate bundles ahead of time, verify deployment health in CI/CD, and inspect runtime rollout status with automatic rollback safeguards. With structured runtime boundaries and optimization guidance, you can monitor and optimize query and extension performance without maintaining cloud clusters or server runtimes.
  • Reduced integration complexity: instead of orchestrating multiple microservices, writing custom proxy endpoints, or handling authentication stitching across disparate vendors, you integrate external systems through schema extensions that compose directly into the single GraphQL graph. Custom write validation rules apply consistently across every consumer through Managed API Extensions, eliminating duplicated glue code across touchpoints.
  • Consistency across shopper-facing touchpoints: a hand-built BFF is often coupled directly to a single client, such as a web storefront. The Commerce Integration Layer provides one experience backend for web and mobile storefronts. These clients query the same GraphQL schema, and Managed API Extension rules apply across their write operations.
For more on the architectural model and deciding where logic belongs, see What is the Commerce Integration Layer, Choose an extension type, and Best practices.

Migration preparation

Before migrating an existing backend-for-frontend (BFF), planning ahead ensures a smooth and successful transition. Preparing your codebase upfront clarifies scope, consolidates capabilities, and ensures your commerce experience is preserved throughout the migration:

  • Establish baseline test coverage: ensure that critical shopper journeys (catalog browsing, cart operations, customer login, and checkout) are covered by automated integration tests. Running the same tests against both the legacy BFF and the Commerce Integration Layer validates the migration.
  • Consolidate capabilities: if the existing BFF is based on a code template, diff your code against the baseline template to isolate genuine custom features. Audit the endpoints exposed in your current BFF against what your storefront actively consumes, and prune unused endpoints and functionality.
  • Isolate non-shopper workflows: the Commerce Integration Layer is shopper-facing and runs extensions in a stateless, per-request sandbox. Relocate non-shopper workloads (such as feed exports, batch syncs, and inbound webhooks) to Connect, and externalize dependencies on in-memory caches or Redis.
  • Identify secrets and configuration: extract existing configuration and secrets. Store them as Project configuration entries read through ctx.config, and mark secret values as secret so they are encrypted.

Patterns without built-in support

Certain architectural patterns found in existing BFFs are either out of scope by design or not supported by default. Lacking built-in support does not mean a pattern will never be supported in the future, nor does it prevent you from implementing custom solutions (such as authoring custom Schema Extensions or leveraging adjacent commercetools services):
  • Split-tender and multi-provider checkout: the default checkout flow of the layer delegates to commercetools Checkout through beginPayment. It does not orchestrate split-tender checkout (across multiple payment service providers (PSPs) or gift cards) or handle asynchronous checkout completion through unauthenticated webhooks by default. If your business requires this, you can implement custom Schema Extensions that expose custom checkout mutations or interact with low-level payment APIs.
  • Stateful execution and caching: extensions run in a stateless, per-request sandbox without shared in-memory caches or persistent filesystems. Patterns requiring distributed locks or idempotency keys must externalize state to third-party datastores over HTTPS. Use CDN caching only for cacheable cross-request responses.

Architectural migration patterns

Use built-in Commerce Integration Layer features and remove manual commerce plumbing

The Commerce Integration Layer has typical shopper flows built-in. This means that existing BFF code becomes redundant and can be retired and handled by the Commerce Integration Layer instead:

  • Direct shopper-facing actions: delete action endpoints and controllers that merely proxy standard commerce resources; issue standard Experience API GraphQL queries and mutations instead of routing requests through bespoke intermediate controllers.
  • Query the commercetools platform: the Experience API queries the commercetools platform within a managed trust boundary, removing the need for commercetools SDK clients and server-side credential custody.
  • Shopper session and token lifecycle: delete custom token-minting middleware and platform refresh handlers; the layer manages shopper sessions and IdP exchanges by default. If your web storefront stores session tokens in HttpOnly cookies, retain the same-origin proxy logic that reads the cookie and forwards the bearer token.
  • Anonymous Cart handling: delete manual Cart reconciliation routines; passing an anonymous session token on login automatically merges the Cart into the customer session.
  • Shopper context: delete header interceptors and currency resolvers; setting locale, currency, and country on session creation automatically scopes presentment for all downstream operations. If your storefront requires channel-specific pricing, call PUT /session/distribution-channel to scope the session instead of maintaining custom channel-resolution middleware.

Adopt Managed API Extensions to replace write-path validation

Consider a common scenario. When a shopper adds an item to their Cart, you need to run custom validation, such as checking external purchase quotas, regional restrictions, or inventory reservations, and block the operation if validation fails. In a legacy BFF, custom middleware often performs this validation before forwarding the request to the commercetools platform. However, if that middleware or external service times out, the legacy BFF might add the item to the Cart anyway or leave data in an unverified state.

In the Commerce Integration Layer, this logic moves to Managed API Extensions. These extensions run synchronously inside the transactional write path of the commercetools platform under strict timeout constraints, so adapt the checks accordingly:
  • Transactional validation guarantee: the write operation is applied only if validation explicitly succeeds; any handler error or timeout automatically aborts the transaction.
  • Lean, deterministic execution: keep extension handlers focused on fast, predictable evaluations (such as checking Line Item constraints or verifying customer eligibility) to ensure executions finish well within the timeout limit.
  • Offload slow external dependencies: relocate non-critical, slow external checks, such as legacy enterprise resource planning (ERP) calls or secondary verifications, to a background consumer, such as a Connect application triggered by Subscriptions.

Offload non-shopper workloads and background tasks

A legacy BFF can often become a catch-all backend for tasks that extend beyond storefront requests, such as running scheduled jobs, receiving third-party webhooks, or routing in-store traffic. Because Commerce Integration Layer extensions run only for individual requests and do not host background jobs, these workloads belong in services built specifically for them:

  • Scheduled and batch jobs: move cron-driven tasks (such as feed exports, catalog syncs, and sitemap generation) to Connect, as extensions cannot run long-lived background daemons.
  • Inbound third-party webhooks: route incoming HTTP callbacks (such as CMS publish triggers or asynchronous payment notifications) to Connect applications rather than to a Commerce Integration Layer extension.
  • Process platform events asynchronously: use Subscriptions to deliver platform events to an external background consumer for processing.
  • Point-of-sale and in-store operations: offload in-store workflows and POS broker integrations to InStore rather than routing them through storefront BFF endpoints.

Shift caching to a thin proxy layer

The Commerce Integration Layer runs in a stateless, per-request sandbox without shared in-memory caches. Instead of trying to cache inside the layer or calling remote caches from extensions, shift caching into a CDN or edge proxy layer in front of the layer:
  • Remove in-memory caches: delete in-memory stores and cache clients from your BFF; the Commerce Integration Layer does not persist state across requests.
  • Add caching in the proxy layer: cache public, read-heavy GraphQL queries at the CDN or edge proxy layer, keyed by query and presentment context (locale, currency, country).

Align storefront search with the built-in search model

Custom search resolvers can return Product entity stubs containing only { id }. The router then resolves the Product fields requested by the client:
  • Built-in product search: use the built-in product search without maintaining custom backend query handlers.
  • Entity stubs: return lightweight Product stubs ({ id }) from search resolvers, and let the router resolve the requested attributes and prices.
  • Third-party provider overrides: implement a Schema Extension to query an external search provider while letting the Commerce Integration Layer handle catalog enrichment.
  • Manual search stitching retirement: delete legacy BFF aggregation logic that manually joined external search hits with commerce catalog data.

Adopt an incremental migration strategy

Every migration path depends on your storefront architecture and the capabilities of your legacy BFF. Consider these guidelines for migrating capabilities gradually behind a reverse proxy or API gateway:

  • Same-origin reverse proxy routing: place a proxy in front of both systems to route migrated requests to the Commerce Integration Layer while directing unmigrated routes to your legacy BFF.
  • Migrate session and auth first: transition to the Commerce Integration Layer authorization model.
  • Migrate one capability at a time: cut over discrete domains (such as catalog browsing, cart operations, or customer profiles) one at a time and validate them in production.