Introduction
Poke-Nex is built using Clean Architecture principles, creating a clear separation of concerns between data fetching, data transformation, and business logic. This architectural pattern ensures maintainability, testability, and scalability as the application grows.Why Clean Architecture?
Clean Architecture was chosen for several key reasons:- Separation of Concerns: Each layer has a single, well-defined responsibility
- Testability: Business logic can be tested independently of external APIs
- Maintainability: Changes to API structure don’t cascade through the entire codebase
- Flexibility: Easy to swap out data sources (REST to GraphQL, different APIs, etc.)
- Type Safety: TypeScript types are defined at each layer for compile-time safety
Architecture Layers
The application is structured in three main layers:Fetchers
Located in
lib/api/, these handle raw API communicationAdapters
Located in
adapters/, these transform API data to app modelsServices
Located in
services/, these contain business logic and orchestrationLayer 1: Fetchers (Data Access)
Location:src/lib/api/pokemon.api.ts
Responsibility: Make HTTP requests to external APIs and return raw API responses.
Fetchers work directly with API types (
ApiPokemonResponse) and handle HTTP-level concerns like caching, error status codes, and retries.Layer 2: Adapters (Data Transformation)
Location:src/adapters/pokemon-detail.adapter.ts, src/adapters/pokemon-summary.adapter.ts
Responsibility: Transform raw API data structures into clean, application-specific models.
Adapters convert from
ApiPokemonResponse (what the API returns) to PokemonDetail (what the app uses). They handle data normalization, unit conversion, and field mapping.Layer 3: Services (Business Logic)
Location:src/services/pokemon.service.ts
Responsibility: Orchestrate data fetching and transformation, handle errors, and provide a clean API for components.
Services return a standardized
ServiceResponse<T> type that always includes both data and error fields, making error handling predictable.Data Flow Overview
- Component calls a service method (e.g.,
getPokemonDetail()) - Service calls the appropriate fetcher (e.g.,
fetchPokemonByID()) - Fetcher makes HTTP request and returns raw API data
- Service passes raw data through adapter (e.g.,
adaptPokemon()) - Adapter transforms data to application model
- Service returns normalized data to component
Type Safety
Each layer has its own TypeScript types:Benefits in Practice
Easy Testing
Mock services without touching real APIs. Test adapters with fixture data.
API Changes Isolated
When PokeAPI changes, only fetchers and adapters need updates.
Multiple Data Sources
Easily support both REST and GraphQL APIs (see
fetchPokemonListGQL)Consistent Error Handling
All errors normalized to
DisplayError format for predictable UI handlingNext Steps
Clean Architecture Details
Deep dive into each layer with complete code examples
Data Flow
Trace a request from component to API and back