API Client Architecture
Established patterns for API client implementation across the project
API Client Architecture Guide
Overview
This guide outlines the established API client architecture for our project, which we strongly recommend for all API implementations. It employs a public/protected pattern using separate client instances. Adopting this approach is key to ensuring type safety, clear intent, and maintainability across our services. The following sections detail this pattern and the reasons for its selection.
Purpose & Scope
This guide serves as the primary reference for understanding and implementing the standard API client architecture within our project. Its purpose is to:
- Define the core structure for
BaseApiClient,PublicApiClient, andAuthenticatedApiClient. - Establish the standard pattern for how domain-specific API services should be created and used.
- Explain the rationale behind these architectural choices to ensure consistency, maintainability, and type safety.
This document is intended for all developers involved in creating or interacting with API services. It covers the foundational client setup; more specialized topics like detailed authentication flows, request queueing, and multi-API configurations are detailed in subsequent related documents.
Prerequisites
To fully grasp the concepts presented in this guide, we recommend a basic understanding of:
- TypeScript: Familiarity with classes, inheritance, and interfaces.
- HTTP Clients/Axios: Basic knowledge of how HTTP clients like Axios operate, including concepts like request/response interceptors (though interceptors are detailed further in linked documents).
- API Design Fundamentals: General understanding of RESTful APIs and common authentication patterns.
Why This Pattern?
We evaluated several approaches before choosing this architecture:
Alternative Patterns Considered
-
Skip Auth Flag Pattern (Not Recommended)
- ❌ Runtime complexity
- ❌ Easy to forget flags
- ❌ Not type-safe
-
URL Pattern Configuration (Not Recommended)
- ❌ Centralized configuration to maintain
- ❌ Can become outdated
- ❌ Hidden behavior
-
Method Prefix Convention (Not Recommended)
- ❌ Still mixing concerns
- ❌ Naming conventions can be inconsistent
Our Chosen Pattern: Type-Safe Domain API
We chose the inheritance-based public/protected pattern because it provides:
- Clear Separation: Two distinct client classes -
PublicApiClientandAuthenticatedApiClient - Explicit Intent: API structure clearly shows which endpoints are public vs authenticated
- Type Safety: TypeScript ensures proper usage without runtime checks
- Better Organization: Domain APIs are organized with
publicandprotectednamespaces - No Magic Flags: No need for
skipAuthor similar runtime configurations - Inheritance Benefits: Authenticated client extends public client, avoiding duplication
- Scalability: Easy to add new API clients for different services
Our Standard API Client Architecture
Core API Client Classes
Our core API client architecture is built upon three key classes, typically organized into separate files as shown below:
The BaseApiClient provides foundational properties and methods common to all API clients.
To make these API client classes usable, singleton instances are typically created and exported from a central module. This pattern ensures consistent client configuration and accessibility throughout the application. The example below shows how this might be structured:
Domain API Implementation Pattern
For consistency and to leverage the benefits of the core API clients, all domain APIs should adhere to the following pattern. This structure promotes clear separation of public and protected endpoints and ensures type-safe interactions:
Example Implementations
Product Domain API
User Domain API
Hook Implementation Pattern
Hooks must use the appropriate API methods:
Do's and Don'ts
Do's ✅
Don'ts ❌
Error Handling
Each API client handles errors appropriately:
Testing API Implementations
Migration Guide
If you have existing code using different patterns, follow these steps:
- Create domain API class with public/protected structure
- Move public endpoints to the
publicobject - Move authenticated endpoints to the
protectedobject - Update all imports to use the new structure
- Remove any
skipAuthflags or similar workarounds - Update tests to mock the correct client
Enforcement
- Code Reviews: Reject any PRs not following this pattern
- Linting: ESLint rules to prevent direct API client imports in domains
- Architecture Tests: Automated tests to verify pattern compliance
Scalability for Multiple APIs
This pattern scales excellently when your app needs multiple API services:
Different Authentication Strategies
Multiple Service Clients
Note: This section provides a brief overview of how this architecture supports multiple API services. For a comprehensive treatment of this topic, including detailed implementation strategies, configuration approaches for different environments, and advanced authentication strategies, please refer to the Multi-API Architecture document.
For detailed multi-API implementation, see Multi-API Architecture.
Summary
Adhering to this API client pattern is highly encouraged for all API implementations in our project, as it provides significant benefits:
- Clear Intent: Obvious which endpoints require authentication
- Type Safety: Full TypeScript support
- Consistency: Same pattern across all domains
- Maintainability: Easy to add new endpoints
- Security: Reduced risk of accidental auth bypasses
- Developer Experience: Better IntelliSense and discoverability
- Scalability: Supports multiple API services with different auth strategies
By consistently applying this established standard, we ensure our API integrations are robust, maintainable, and easier for all team members to work with.