Refactoring Wooster's API Layer: A Clean Service-Based Approach with Axios
Why Move to Axios?
After building Wooster's initial MVP with the fetch API, it was time for an upgrade. While fetch worked well, Axios offers some helpful features out of the box that would make my code cleaner and more maintainable. More importantly, I wanted to establish a pattern that would scale as the application grows.
Following the Docs
Like any good developer, I first spent three hours looking for "the perfect way" to implement Axios, only to end up exactly where I should have started: the official documentation. Sometimes the simplest approach is the best one, even if it does make you feel a bit daft for not trying it first.
The Implementation
1. Setting Up the Instance
First, I created a configured Axios instance following the official pattern (after trying three "clever" approaches that all ended in tears):
This gave me everything fetch did, plus some features I didn't know I needed until I had them:
- A base URL for all requests
- Automatic timeout handling
- Default headers
Rather like getting a dishwasher - you don't realize how much time you've been wasting until you stop doing it the hard way.
2. Authentication
I added a simple interceptor for authentication:
3. Type Definitions
Before implementing the services, I defined clear interfaces for our API types:
4. Service-Based API Organization
Instead of having all API calls in one file, I organized them into domain-specific services:
This approach provides several benefits:
- Domain separation - related API calls are grouped together
- Type safety throughout the entire request/response cycle
- Consistent error handling
- Easier testing and mocking
- Better IDE autocompletion
Using the Services
Here's how these services simplify our API calls:
The service pattern gives us cleaner API calls with built-in type safety and better error handling, regardless of how we manage state.
Benefits of This Approach
- Type Safety: Full TypeScript coverage from request to response
- Domain Organization: API calls are grouped by feature
- Consistent Patterns: Each service follows the same structure
- Better Maintainability: Easy to find and modify related endpoints
- Scalability: New features can easily follow the established pattern
- Improved Developer Experience: Better autocomplete and type inference
Key Lessons
- Keep It Simple: Following documentation patterns often leads to cleaner code
- Think in Domains: Organizing by feature makes code more maintainable
- Type Everything: Strong typing catches errors before they reach production
- Consistent Patterns: Using consistent naming and structure makes the codebase more predictable
What's Next?
With this foundation in place, I have a clean, type-safe API layer that's ready to grow with my application. But first, I had to fix all those tests I broke... which led me down quite the rabbit hole with Mock Service Worker.