DEV Community

Cover image for Building an API Layer That Works for You - More Axios vs Fetch
limacodes
limacodes

Posted on

Building an API Layer That Works for You - More Axios vs Fetch

Last time we talked about Axios vs Fetch and how productivity wins over preference.

Now let’s take that a step further — how do you structure your requests so they stop being random lines of code all over your project?

Let’s be real.

Most apps start with a few fetch calls.

Then someone adds Axios.

Then you have five files doing the same thing differently.

Headers here, interceptors there, random try/catch everywhere.

Bam — you’re debugging your own chaos...

So the next logical move is to stop thinking about “which library” and start thinking “what’s my API layer supposed to do?”


One Layer to Rule Them All

Your API layer should have a few clear jobs:

  • make requests
  • handle errors
  • manage tokens
  • keep types clean
  • stay easy to read

You want a single place that knows how your app talks to the outside world.

Not magic, not complex — just consistent.

Here’s a simple example using Axios:

import axios, { AxiosInstance } from 'axios'

const api: AxiosInstance = axios.create({
  baseURL: 'https://my-api.com',
  headers: {
    'Content-Type': 'application/json'
  }
})

api.interceptors.request.use((config) => {
  const token = localStorage.getItem('token')
  if (token) config.headers.Authorization = `Bearer ${token}`
  return config
})
Enter fullscreen mode Exit fullscreen mode

Done.

Every request now runs through this logic.

No more repeating headers in every file or crazy guessing which request forgot the token.


Types Are Your Safety Net

When you’re using TypeScript, you can make your API smarter.

Define the shape of your data once — and let the compiler yell when something’s off.

interface User {
  id: number
  name: string
  email: string
}

const getUsers = async (): Promise<User[]> => {
  const { data } = await api.get<User[]>('/users')
  return data
}
Enter fullscreen mode Exit fullscreen mode

Now every part of your app knows what a User looks like.

No more undefined surprises later.


Catch Once, Handle Everywhere

The next mess we create as devs is with error handling.

You probably have 10 different try/catch blocks doing almost the same thing.

Centralize that.

api.interceptors.response.use(
  (response) => response,
  (error) => {
    const message = error.response?.data?.message || 'Something went wrong'
    console.error(message)
    return Promise.reject(error)
  }
)
Enter fullscreen mode Exit fullscreen mode

This way, you log or format errors in one spot — not all over the codebase.

You can even send them to a toast system or a log service later.


Stop Copying, Start Importing

If you find yourself writing new API files that look like the old ones, stop.

Make one folder for all requests.

src/
 └── api/
     ├── client.ts
     ├── users.ts
     ├── products.ts
     └── types.ts
Enter fullscreen mode Exit fullscreen mode

Every file just imports the same api instance.

You keep things clean, and onboarding someone new becomes painless.


Some Thoughts

If your app grows, this layer grows with it.

Want retries? Add them once.

Need a loading indicator? Wrap the requests.

Switch base URLs? One line change.

The goal is not to build something fancy — it’s to stop writing the same thing twice.


Where This Fits in 2025... I'm a bit in the past when it comes to coding... you know I don't like breaking things.

If you’re using Next.js or React Server Components, you’ll probably use the built-in fetch for server data — it’s faster and edge-ready.

But an Axios client like this still makes sense for:

  • client-side calls that need auth tokens
  • Node services or microservices
  • scripts and RPA jobs that hit APIs directly

You can even plug this Axios client into React Query or TanStack Query and let it handle caching, retries, and background refresh.

That’s where productivity really kicks in.


Wrapping Up

Axios or Fetch — it doesn’t really matter once you understand why you’re calling them.

What matters is how much control you have over that process.

Your API layer is your contract with the world outside your app.

Keep it clear. Keep it simple.

And if it makes your day easier, you’re already doing it right.

Top comments (0)