Olivier Lowe

This technical article is available in English.

REST Was an Implementation Detail. Prove It.

We said the frontend depended on backend capabilities, not REST. Then we replaced REST with GraphQL.

Transport independence should be demonstrated, not merely claimed.

The architecture already looked independent from REST. Application use cases depended on capabilities. The Orders feature did not know about URLs or HTTP verbs. Angular composition selected the concrete transport at runtime.

Structurally, everything looked right.

But an interface and an architecture diagram are not proof that something is replaceable.

The Claim

The claim was deliberately specific:

The frontend Application depends on the backend capabilities it needs, not on REST.

If that was true, another transport should be able to satisfy the same capabilities without forcing the higher-level frontend architecture to change.

RestOrderApi ───────┐
                    │
                    ├── same capability boundary
                    │
GraphqlOrderApi ────┘
                    ↓
             same Application
                    ↓
                same Domain

Define the Proof Before the Change

Before introducing GraphQL, I defined what success meant.

Application changes     = 0
Domain changes          = 0
use-case changes        = 0
capability contracts    = unchanged
Orders feature changes  = 0
browser journey changes = 0

The controlled variable was narrow:

RestOrderApi + REST server
              ↓
GraphqlOrderApi + GraphQL server

This was not a migration project, and it was not an attempt to prove that GraphQL was better than REST.

GraphQL was an architectural probe.

First, Make the Choice Explicit

Before introducing a second transport, the runtime selection of the existing REST adapter needed clearer ownership.

The running application had to choose a concrete implementation somewhere.

app.config.ts
     ↓
ORDER_API_PROVIDER
     ↓
RestOrderApi

Extracting that selection did not change product behavior. REST still powered the existing application and the already proven cancellation journey.

It simply made the substitution point explicit before another implementation was introduced.

GraphQL Had to Fit the Existing Capability

The new GraphqlOrderApi grew capability by capability until it could satisfy the same backend responsibilities already expected by the Application:

getOrder()
→ Promise<Order>

listOrders()
→ Promise<OrderSummary[]>

placeOrder()
→ Promise<void>

cancelOrder()
→ Promise<void>

Then GraphQL challenged the boundary in an interesting way.

A GraphQL mutation could return an updated Order. But the existing Application contract did not ask for one.

await orderApi.cancelOrder(order.id);

order.cancel();

The Application already owned that transition after the remote operation succeeded.

Changing the capability contract just because GraphQL offered a different response shape would have allowed the new transport to reshape higher-level behavior.

So the adapter changed to fit the capability. The capability did not change to fit GraphQL.

GraphqlOrderApi
implements OrderApi

A Second Transport Had to Be Real

Adapter tests alone were not enough.

They could prove that GraphqlOrderApi sent a request to /graphql and mapped a GraphQL-shaped response into the existing frontend model.

But a mocked response could not prove that a real GraphQL boundary parsed a document, validated a schema, executed resolvers, changed state, and returned the expected result.

So the experiment included a real development GraphQL server.

GraphqlOrderApi
      ↓
HTTP POST /graphql
      ↓
GraphQL schema + resolvers
      ↓
shared development state
      ↓
GraphQL response

The mutations were verified through persisted state, not merely successful responses.

ORD-1001
Draft → Submitted

ORD-1002
Submitted → Cancelled

Subsequent GraphQL queries returned those new states. The second transport was now exercising real behavior.

Run the Same Journey Twice

Even that was not the decisive proof. We now had two adapters and two real development backends, but the architectural claim concerned the whole frontend journey.

The strongest test already existed:

e2e/features/orders/cancel-order.spec.ts

The test described what the customer did. It contained no knowledge of whether REST or GraphQL was behind the capability boundary.

So the same browser journey was executed against both transports:

npm run e2e:rest
→ 1 passed

npm run e2e:graphql
→ 1 passed

The execution path changed:

REST

ORDER_API
   ↓
RestOrderApi
   ↓
REST server

GRAPHQL

ORDER_API
   ↓
GraphqlOrderApi
   ↓
GraphQL server

But the higher-level journey remained the same:

same customer journey
          │
   ┌──────┴──────┐
   │             │
 REST         GraphQL
   │             │
   └──────┬──────┘
          │
   persisted state
          │
 authoritative reload
          │
      Cancelled

What Changed — and What Didn't

CHANGED

transport adapter
transport protocol
development backend

UNCHANGED

Domain
Application use cases
capability contracts
Orders feature
OrderPage
Playwright journey
user-visible behavior

What It Proved — and What It Didn't

The result does not mean that REST and GraphQL are equivalent.

It does not prove that GraphQL is better.

And it does not prove that every transport in every system can be exchanged without cost.

For one verified Orders journey, REST could be replaced with GraphQL while the higher-level frontend architecture remained unchanged.

That is narrower than claiming complete transport independence across the entire application.

It is also stronger, because the claim is supported by an actual substitution experiment.

The Lesson

Calling REST an implementation detail is easy. Hiding it behind an interface is also easy.

The more useful question is whether another transport can take its place without forcing the higher-level architecture to follow it.

Transport independence should be demonstrated, not merely claimed.

CONTINUE THE ARCHITECTURE JOURNEY

The substitution experiment is only part of the story.

This experiment comes from my book Building Software Around the Business — Not Around the Framework — Frontend Architecture with TypeScript, Angular, and React.

The book follows the architecture from the first frontend behavior through Domain and Application boundaries, state ownership, capability segregation, runtime composition, and the final REST-to-GraphQL substitution proof.

Explore the frontend architecture book →