Multi-API Architecture
How to scale the API client pattern for multiple services and endpoints
Scalable Multi-API Client Architecture
Overview
This document describes how to extend our standard API client architecture to support connections to multiple distinct API services, each potentially with its own base URL, authentication strategy, and configuration. It builds upon the foundational patterns established in the API Client Architecture and Authentication Architecture guides.
Purpose & Scope
This guide is for developers who need to integrate the application with more than one backend API service. It covers:
- Defining multiple API client instances.
- Implementing different authentication strategies per API.
- Managing environment-specific configurations for multiple APIs.
Prerequisites
To effectively set up and manage multiple API services as described here, it's important to first be familiar with:
- API Client Architecture: A thorough understanding of the API Client Architecture is crucial, as this guide extends those mandatory patterns (
BaseApiClient,PublicApiClient,AuthenticatedApiClient). - Authentication Architecture: Knowledge of the Authentication Architecture, particularly
tokenServiceand the general principles of token management for authenticated clients. - Authentication Strategies: Basic familiarity with common API authentication mechanisms like Bearer tokens, API Keys, and OAuth2, as the examples will involve implementing strategies for these.
- Environment Variables: Understanding how to use environment variables (e.g.,
process.env.MAIN_API_URL) for configuring service URLs and secrets.
Supporting Multiple API Services
As applications grow, they often need to connect to various backend services—such as a main application API, a payment gateway, an analytics service, or other third-party APIs. The architectural patterns described in this guide are designed to scale elegantly to support these multi-API scenarios, ensuring maintainability and flexibility:
This guide builds upon the foundational BaseApiClient defined in the API Client Architecture. For context, the BaseApiClient typically includes a constructor to set up an Axios instance with base URL and timeout, along with common HTTP methods (get, post, etc.) that handle responses and errors.
1. Base Client Configuration (Reference)
The BaseApiClient (detailed in the API Client Architecture) provides the common foundation. Specific configurations per API service (like baseURL, timeout, custom headers) are passed via an ApiClientConfig object when instantiating concrete clients.
2. Different Authentication Strategies
To handle various authentication mechanisms for different API services, we define an AuthStrategy interface and concrete implementations. These strategies encapsulate the logic for applying authentication to requests and handling auth-specific errors.
The AuthStrategy interface defines the contract for all authentication strategies.
Note: The code examples throughout this document use conceptual imports with paths like
@/core/domains/...or commented imports like// import { TokenService } from.... These are illustrative examples that would need to be adapted to match your actual project structure.
3. Multiple API Clients
With the base clients and authentication strategies defined, we can now construct specific API client instances. The PublicApiClient is used for non-authenticated endpoints, and the AuthenticatedApiClient is configured with an appropriate AuthStrategy for endpoints requiring authentication.
PublicApiClient extends BaseApiClient and requires no special authentication handling.
Now, we can create and export instances for each API service your application needs to communicate with:
4. Domain-Specific Implementations
Once you have specific client instances (like paymentApi or analyticsApi), you would use them within your domain-specific API service files to define the actual methods your application will call. Here are a couple of examples:
5. Environment-Specific Configuration
6. Error Handling per Service
When dealing with multiple API services, it's often beneficial to have service-specific error types that can extend a common base ApiError. This allows for more granular error handling and identification of which service an error originated from.
Error Class Definitions:
The following classes would typically reside in a shared error definitions file, for example, core/shared/api/errors.ts.
Example Usage in a Domain API:
This shows how PaymentApiError could be thrown from within a payment domain method.
7. Monitoring and Logging
8. Testing with Multiple APIs
Benefits of This Scalable Approach
- Service Isolation: Each API has its own client instance and configuration
- Authentication Flexibility: Different auth strategies per service
- Environment Management: Easy to switch between dev/staging/prod
- Error Handling: Service-specific error types and handling
- Type Safety: Full TypeScript support across all APIs
- Testability: Can mock each API independently
- Monitoring: Add logging/metrics per service
- Maintainability: Clear separation of concerns
Design Principles
Core Architectural Principles
-
Polymorphic API Client Interfaces
- Each API client shares common interface but may have unique implementation
- Authentication strategies can be swapped without changing client code
- Error handling adapted to each service's specific requirements
-
Strategy Pattern for Authentication
- Authentication logic encapsulated in strategy objects
- Clients delegate authentication to the appropriate strategy
- New auth mechanisms can be added without modifying existing clients
-
Environment-Based Configuration
- Configuration driven by environment rather than hardcoded
- Clear separation between development, staging, and production environments
- No code changes required when moving between environments
Trade-offs and Design Decisions
| Decision | Benefits | Drawbacks | Rationale |
|---|---|---|---|
| Strategy pattern for auth | Easily add new auth methods, cleaner code | More classes and interfaces | Provides flexibility as app grows and adds more external services |
| Service-specific clients | Clean domain boundaries, appropriate error handling | More boilerplate compared to single client | Different APIs have different requirements and error structures |
| Environment config objects | Single source of truth for all environments | Larger config files | Simplifies deployment and environment switching |
| Base client with extensions | Reduces code duplication | Can lead to inheritance issues | Balance between code reuse and flexibility |
Constraints and Considerations
- Each client should handle its own rate limiting and backoff strategies
- Environment-specific configuration should never be committed with sensitive keys
- Error handling must be customized per service but follow consistent patterns
- Authentication state must be properly synchronized across clients when tokens refresh
Implementation Considerations
Performance Implications
- Client Initialization: Initialize clients lazily when first needed, not all at startup
- Request Batching: Group multiple calls to the same service when possible
- Caching Strategy: Implement appropriate caching per API based on data volatility
- Header Optimization: Minimize custom headers to reduce request size
Security Considerations
- API Key Storage: Environment variables for development, secure storage for production
- Token Refresh: Ensure secure handling of refresh tokens across different services
- Error Exposure: Filter sensitive information from error messages before logging
- Certificate Management: Properly manage client certificates for secure services
Scalability Aspects
- The architecture scales easily with additional services
- New authentication strategies can be implemented without disrupting existing ones
- Domain-specific wrappers provide clear boundaries as the application grows
- Environment configuration system adapts well to new deployment targets
Related Documents
- API Client Architecture - Core client implementation details
- Authentication Architecture - Token management and auth flows
- Error Handling Architecture - Global and service-specific error handling
- API Client with Request Queueing - Queue mechanism for auth token refresh
Migration Strategy
To migrate to this multi-API architecture:
- Start with your main API using the pattern
- Add new API services one at a time
- Implement appropriate auth strategies
- Update domains to use specific API clients
- Add monitoring and error handling
- Update tests for each service
By adopting this scalable architecture, our team can confidently integrate with multiple API services while maintaining a clean, organized, and maintainable codebase.