BFF Versioning
BFF Versioning
BFF versioning is the strategy for managing breaking changes to the BFF-Pattern contract between the backend-for-frontend and its frontend client. Because the BFF owns the API contract for exactly one frontend, versioning is simpler than versioning a public API — but it still requires a deliberate strategy to support zero-downtime deployments.
Why BFF Versioning Is Different
A traditional API serves many unknown consumers; versioning is defensive and long-lived. A BFF serves one frontend team; versioning is coordinated and can be short-lived. The frontend team and the BFF team are often the same team, so the "contract" is an internal coordination mechanism rather than a public commitment.
That said, versioning still matters for:
- Blue/green deployments — old Angular build and new Angular build may coexist briefly
- Canary releases — routing a percentage of users to a new Angular build that expects a new BFF API shape
- Mobile apps — if a BFF also serves a mobile client, mobile release cycles are slower; old versions stay in the field longer
Three Versioning Strategies
| Strategy | URL Example | Pros | Cons |
|---|---|---|---|
| URL path versioning | /api/v1/products, /api/v2/products | Simple, cacheable, easy to route, visible in logs | URL "pollution"; Angular service class must embed version in base URL |
| Header versioning | Accept: application/vnd.myapp.v2+json | Clean URLs; version is metadata, not resource identity | Harder to test in browser; less visible; caching requires Vary header |
| Query parameter | /api/products?version=2 | Easy to add without route changes | Pollutes query strings; harder to cache; easy to omit by mistake |
Recommendation for BFF Serving Angular
URL path versioning (/api/v1/, /api/v2/) is the pragmatic choice. Reasons:
- Angular's
HttpClientservice can be given a base URL constant (/api/v2) so the version is defined in one place - Routes are trivially distinguishable in Spring-Cloud-Gateway predicates and access logs
- During deployment, old and new routes can coexist on the same BFF instance
- Testing is trivial —
curl /api/v2/productsworks without setting custom headers
Versioning in Spring Cloud Gateway
Spring-Cloud-Gateway makes it straightforward to maintain two versions simultaneously with RewritePath filters:
spring:
cloud:
gateway:
routes:
- id: products-v2
uri: lb://products-service
predicates:
- Path=/api/v2/products/**
filters:
- RewritePath=/api/v2/products/(?<segment>.*), /products/${segment}
- id: products-v1-compat
uri: lb://products-service
predicates:
- Path=/api/v1/products/**
filters:
- RewritePath=/api/v1/products/(?<segment>.*), /products/${segment}
# ResponseTransformer filter adapts v2 response shape to v1 contract
- name: ResponseTransformerThe products-service itself only needs to maintain one internal API; the BFF handles shape adaptation via the ResponseTransformer filter for the v1 compatibility layer.
Lifecycle of a BFF Version
v1 deployed (Angular build A)
↓
v2 deployed alongside v1 (Angular build B canary)
↓
Traffic shifts to build B → all users on v2
↓
v1 routes removed (after N days/weeks)
The rule for BFF is to maintain N and N-1 simultaneously during deployment. Once 100% of traffic is on the new Angular build, the old version can be removed from gateway configuration.
Angular Side: Isolating the Version
Define the API version once in Angular so changing it is a single-line update:
// environment.ts
export const environment = {
apiBase: '/api/v2'
};
// product.service.ts
@Injectable({ providedIn: 'root' })
export class ProductService {
private readonly base = `${environment.apiBase}/products`;
constructor(private http: HttpClient) {}
getProducts(params: ProductQueryParams): Observable<PageResponse<ProductView>> {
return this.http.get<PageResponse<ProductView>>(this.base, {
params: toHttpParams(params)
});
}
}Version Negotiation via Gateway Rewriting
For cases where the Angular client and the downstream service have fallen out of sync on response shape, the BFF can act as an adapter:
// GatewayFilter that transforms a v2 response into v1 shape for backwards compat
@Component
public class V1ResponseAdapterFilter implements GatewayFilter {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
return chain.filter(exchange.mutate()
.response(new V1ResponseAdapter(exchange.getResponse()))
.build());
}
}This keeps the downstream service at its current shape while the BFF provides the translation layer — consistent with the Response-Transformation pattern.
Related Concepts
Related API Design Patterns
- REST-API-Design — BFF-Versioning covers gateway-level and frontend-environment versioning strategies; REST-API-Design covers public API versioning lifecycle (Sunset/Deprecation headers) — they are complementary notes, explicitly cross-linked
Backlinks
- BFF-Pattern — versioning is part of BFF lifecycle management
- Spring-Cloud-Gateway —
RewritePathand filter chain enable multi-version routing - Response-Transformation — v1-compat layer uses response transformation
- P6-BFF-Angular-Integration — ANG-05 covers the full versioning strategy