Improve scalability with adapters
25/04/2026 · Craft
How adapters pattern can make your front-end code more readable and resilient.
What are adapters
An adapter is a function — or a set of functions — that transforms one object format into another without losing information. On the front-end, you'd typically use it to translate a backend response into something more digestible for your UI.
The way I see it, adapters give you two things:
- They hide the complexity of the transformation
- If you have multiple data sources, you just add one adapter per source instead of trying to cram everything into the same schema
It's a simple pattern, probably one of the simplest. But I don't see it used as often as it should.
Why you need this on the front-end
Front-end apps keep growing and they handle more and more responsibilities. You need patterns, or at least some structure, to keep things manageable. Adapters are a good starting point.
Unifying data from different sources
If you work on a legacy system — or something that's been through several refactors — you'll eventually deal with multiple APIs that serve the same purpose. This is where adapters help.
Say you have an API returning a list of filters in a specific format. The backend team is migrating the filter system from Elasticsearch to an in-house solution. Problem is, Elasticsearch is flexible and the new system is more limited.
Your legacy front-end is deeply coupled to the old system — some filter queries are literally hardcoded. Migrating everything at once is impossible, for both the front-end and the back-end.
At some point you'll need to add features that use filters. Which system do you pick? The new one, obviously.
But touching the old architecture takes weeks. And you can't build something new on a dying system either.
That's where adapters come in. You create a layer between your app and the APIs. It normalizes everything. One adapter for the old system, one for the new one. Your front-end talks to a single interface, no matter what happens behind the scenes.
The real value during refactoring
Adapters are also useful during refactoring.
Say you have a big legacy codebase, the kind where the same data model lives in 47 files and everyone's afraid to touch it. The team decides to migrate to a new domain model.
Without adapters, you'd need to update every file that touches the old model at the same time. One big bang refactoring. Risky, hard to review, and if something breaks you have no idea where.
With adapters, you can take a different approach. You define your new model, put an adapter in front of the legacy parts, and migrate file by file. Each time you touch a piece of code, you make it use the new model. The adapter handles the bridge between old and new for everything you haven't migrated yet.
You can even run both paths in parallel during a transition period. The adapter pattern gives you the freedom to refactor incrementally, without freezing development for weeks.
This is also where you can combine adapters with feature flags. Roll out the new model to 10% of users, check that nothing breaks, then ramp up. If something goes wrong, the adapter is the single point of investigation.
Testing without depending on the backend
Another thing I like about adapters — they make testing easier. You can test your app with fixtures in your unified format, without hitting a real API. No more tests failing because the backend is down or because the back team changed a field without telling you.
Integration tests still have their place. But for unit tests, adapters remove a lot of friction.
Common anti-patterns
Adapters are simple but people still manage to misuse them. Here are a few things I've seen.
The god adapter
One adapter that handles everything. You start with a simple mapping, then someone adds a special case, then another one, then a flag to handle this different format. Six months later you have a 400-line adapter with 15 conditions and nobody knows what it does anymore.
Keep adapters focused. One adapter = one source format. If you need to handle variations, compose smaller adapters instead of piling conditions.
Adapting just for the sake of it
Some teams add adapters everywhere because "it's clean architecture." If you have one API that returns data in a format that already works for your UI, don't add an adapter. YAGNI applies here as much as anywhere else.
Mixing adapter and business logic
An adapter should transform data. That's it. It shouldn't validate permissions, compute derived values, or make decisions. If you find yourself adding if/else logic about what the user can see, that's not an adapter anymore.
Keep adapters simple. Transform input, produce output.
The hidden mapper
Sometimes an adapter is hiding in plain sight — inside a component, scattered across a reducer, or duplicated in five helper functions. You don't need a dedicated adapter file for every tiny transformation, but if you're mapping the same fields in multiple places, centralize it.
Code example
An adapter in TypeScript looks like this:
interface BackendFilter {
filter_id: number;
filter_label: string;
filter_values: Array<{ value_id: number; value_label: string }>;
}
interface FrontendFilter {
id: number;
label: string;
options: Array<{ value: number; label: string }>;
}
function adaptFilter(backend: BackendFilter): FrontendFilter {
return {
id: backend.filter_id,
label: backend.filter_label,
options: backend.filter_values.map(v => ({
value: v.value_id,
label: v.value_label,
})),
};
}Nothing fancy, but it makes a real difference when you're dealing with many endpoints. You keep the mapping in one place, it's typed, and when the backend changes you update one function instead of hunting through the whole codebase.
Wrap up
Adapters aren't the most exciting pattern. But they're one of the most useful day to day, especially when working on apps with multiple data sources, ongoing migrations, or legacy code that needs incremental refactoring.
They're also a good first step toward a cleaner architecture — once you start isolating your data transformations, it's easier to apply other patterns later.