Back
Illustration of a person thinking with a question mark.

I thought I understood..

I thought I understood API Contracts

The human explanation

An API contract is like ordering a takeaway and expecting it to arrive a certain way - the right dishes, the right portions, everything as requested.

Most of the time, that works. You trust that what you ordered is what you’ll get.

But what if it doesn’t. The fried rice turns up as plain boiled rice, the noodles are a small portion instead of a large and the wings are missing! Even these minor changes make it harder to use what you’ve been given.

You realise how much you were relying on that expectation being consistent.

The technical explanation

An API contract defines the expected shape of the data between the frontend and backend.

When your frontend makes a request, it’s expecting a specific structure back - certain fields, certain nesting, certain values.

In practice, this often ends up being an implicit contract. You define a type or interface on the frontend and then assume the API will always return data that matches it.

The problem is that this contract isn’t actually enforced on the frontend at runtime. Even with TypeScript, you’re only checking the shape of the data at compile time, the API can still return something unexpected.

In many applications, that data is then passed directly into components. The UI ends up depending on the exact structure of the API response, including field names and nesting.

A more flexible approach is to introduce a mapping layer between the API and your UI. Instead of passing the response straight through, you transform it into a shape that’s designed specifically for your components.

This creates a stable contract on the frontend side, even if the underlying API changes.

Why this matters

When your UI depends directly on API responses, even small changes can have a big impact.

Renaming a field, changing a nested structure, or returning slightly different data can break multiple parts of the UI. What seemed like a simple change on the backend becomes a wider frontend issue.

It also makes your components harder to work with. Instead of dealing with clean, intentional data, you end up handling deeply nested structures and backend-specific naming throughout your UI.

By shaping API data before it reaches your components, you 1) reduce coupling between frontend and backend 2) make components easier to read and reason about 3) create a more stable interface for your UI 4) make it easier to evolve both sides independently

This becomes especially important in larger applications, where shared components rely on the same data across multiple parts of the product and possibly across different devices/platforms.

The real talk

I used to take whatever the API gave me and pass it straight into my components.

If the API returned something deeply nested, I’d just dig into it. If the naming wasn’t ideal, I’d work around it in the UI.

It worked.. until it didn’t.

I remember running into this with a global navigation component. It was used across multiple pages, and it relied directly on the structure coming from the backend.

At the time, that felt fine, it meant less work up front. But it also meant the component was tightly coupled to the API shape.

When something changed in the backend (even something small like a field rename or structure tweak) it didn’t just break in one place. It had knock-on effects across multiple parts of the UI.

Fixing it wasn’t just updating a single component. It meant tracking down everywhere that structure was being used and updating each one individually.

That was the moment it clicked for me that I wasn’t really working with an API contract, I was just relying on whatever the backend happened to return.

The shift was introducing a mapping layer, even something simple, where I transformed the API response into a shape that made sense for the UI.

Instead of adapting my UI to the API, I started adapting the API to the UI.

It felt like extra work at first, but it actually simplified everything else. Components became easier to read, shared components became more stable, and backend changes stopped rippling through the entire frontend.

Now I think of API responses as an input, not something my UI should depend on directly.