Developer Notes

Blind wall painted in bright green and yellow. Photo by Peppered Pixels Design Studio

Your frontend deserves its own backend

In recent years, across different customers and projects, I keep running into the same problem. Teams build an API for integration with other components, and then connect their Single Page App (SPA) and management application to that same API. One service, serving everyone.

It works... until it doesn't. As the frontend evolves endpoints need to change because they need different data shapes. Changes in endpoints sooner or later break its consumers.

Another risk arises from piling on too many responsibilities in a single service. This became clear at one customer, when sensitive management endpoints that were strictly intended for internal tooling ended up in the API exposed to external integration consumers. Not through malice, but because everything lived in the same API surface and nobody had drawn the line.

General purpose vs single-purpose

The core of the issue is that not all endpoints are alike. There are different expectations for calls that are consumed within a module versus calls that are consumed outside your module, or even your team. These are best represented with the API and BFF (Backend for Frontend) paradigms.

An API is a contract with the world. It has consumers you may not know about, and it must never break them. You version it carefully, you deprecate slowly, and you think hard before changing a response shape. Its format can vary (JSON, XML, gRPC etc.), but compatibility expectations are strict.

A BFF is the opposite. It exists to serve exactly one client: your SPA, your mobile app, your management application. It can return whatever shape that client needs, including HTML fragments if you're using HTMX. It should evolve freely alongside the client, owned by the same team, deployed on the same cadence.

Sam Newman, who coined the BFF pattern, describes them as a "Single-purpose Edge Services for UIs and external parties". Single-purpose is the key word.

Given the difference in expectations it makes sense to separate these endpoints into independent services.

Keep 'em separated

So how do you determine whether your endpoints are an API or a BFF? There are three main concerns:

Coupling and evolution. A BFF changes when the UI changes: endpoint names, response shapes, query parameters, all fair game. An API must guarantee stability for every consumer. These two requirements are fundamentally incompatible in a single service.

Format. An API speaks machine formats like JSON, XML or gRPC, always. A BFF can return whatever the client needs. If you're building with HTMX, your BFF might return HTML fragments directly. Trying to accommodate both from one service forces compromises that benefit neither.

Authentication model. A BFF authenticates users. It sits at the boundary where a person logs in and gets a session. An API interacts with and authenticates machines or machines delegated by users. These are different trust models with different security boundaries. When you mix them in one service, you either over-expose functionality to machine consumers or under-restrict what authenticated users can reach.

The rule of thumb

If something other than your own frontend is calling it, it's not a BFF, it's an API. Build it accordingly from the start. Treat BFFs like product internal endpoints.

Conversely, do not give in to the temptation to add presentation specific endpoints for a client to an API. Create a new service and apply client specific adaptations there.

This applies to third parties too. If an external party wants to build their own application on top of your platform, they should implement their own BFF on top of your APIs. Not consume yours directly from their frontend.

The cost argument

Splitting an API surface makes sense conceptually but when you need to deploy multiple services for your module, it costs real money. This is the most common counter argument, and it's worth taking seriously. A service is a unit of deployment with its own pipeline, infrastructure, and operational overhead.

The reframe is this: if your BFF and your API evolve at different paces and for different reasons, they should be separate deployment units. That's not overhead, that's the design working correctly. The cost of separation is visible. The cost of not separating shows up later, in a production incident or a security review.

In a modern container-based environment the marginal cost of an additional service is low. The incident cost is not.

Separation pays off

When you enforce the distinction between APIs and BFFs, frontend teams can iterate without fear of breaking integration consumers. API consumers get the stability guarantees they need. Management functionality stays where it belongs: behind a BFF that only the management application can reach.
Next time you add an endpoint, ask yourself: who is this for? Does this endpoint belong here? Split your services to fit their purpose.

Further reading:

No comments yet

Leave a comment

Leave a comment

Please enter your name.
Please enter your e-mail address.
Please enter your comment. You may use basic HTML formatting.

Thank you!

Your comment is submitted and I'll publish it asap.