Our backend team is updating their API and we need to support two versions for a while. What is the best way to handle this in our frontend code? Should I create different services for different versions, or handle it in a base HTTP interceptor? Looking for the most maintainable pattern.
Versioning is best handled by implementing distinct service layers for each API iteration combined with an adapter pattern to decouple frontend models from backend schema changes.
6 answers
You should approach this by decoupling the versioning logic from the core HTTP transport layer to ensure testability.
- Define specific service interfaces for each API version to enforce type safety.
- Implement an adapter pattern to map legacy response payloads to your current frontend model.
- Use dependency injection to swap implementations at the module level rather than at runtime in an interceptor.
- Ensure integration tests cover both versions independently to prevent regressions.
Inject the versioning at the service layer and keep your HTTP interceptors out of the business logic. Interceptors are for auth and global errors, not for routing traffic based on feature evolution.
Keep it modular so when you finally kill V1, you aren't digging through a tangled mess of conditional logic.
I once saw a project try to handle dual versions via an interceptor, and it became an unmaintainable nightmare within six months.
We ended up with headers firing based on global state that nobody could track, leading to bugs that only appeared in production. My advice is to build separate services for the different versions and just deal with the slight code duplication until you retire the legacy one.
Using an interceptor is a quick way to get things working, but it fails to scale because it couples your communication layer to every single endpoint change. Creating distinct services works better when you need to maintain different data transformation logic, as it isolates the impact of breaking changes to a single file. However, if the differences are merely header variations, the interceptor approach is technically acceptable provided you isolate the logic to prevent it from ballooning into a maintenance bottleneck.
Stop trying to force the interceptor to be a router, because you are just begging for a headache every time a payload contract changes. An HTTP interceptor is a global concern, whereas versioning is a concern of the data layer. By treating versions as separate services, you allow the frontend to consume clean, predictable models, even if the backend is in a messy state of transition.
You should prioritize having separate service classes that inherit from a base provider or use a shared interface. This keeps your component code completely agnostic of the version being fetched. It makes your dependency injection graphs cleaner and ensures that when the time comes to delete V1, you are deleting a self-contained service file rather than hunting down logic inside an interceptor.
Think about the long-term maintainability of your CI/CD pipelines. If you have to mock a service for a unit test, having a separate service makes that trivial. Having to mock an interceptor that dynamically changes headers or URLs based on the path is much harder to manage. Invest in the upfront effort of explicit service separation and your future self will thank you when the deprecation phase finally arrives.
Interceptors for versioning are a trap for developers who prefer convenience over system stability. You need to keep the versioning logic inside the service layer where it can be explicitly typed and tested. If you bury it in an interceptor, you lose visibility into exactly what version is being requested, which makes debugging distributed systems significantly harder.

