Chapter 11: TypeScript Integration
9/1/25About 3 min
Chapter 11: TypeScript Integration
Learning Objectives
- Master Axios's TypeScript type definitions
- Learn to create type-safe API clients
- Implement interface type constraints
- Master advanced applications of generics
- Build a fully typed HTTP service
11.1 Basic Type Definitions
Axios TypeScript Basics
import axios, { AxiosInstance, AxiosResponse, AxiosRequestConfig } from 'axios';
// Basic type definitions
interface User {
id: number;
name: string;
email: string;
createdAt: string;
}
interface ApiResponse<T> {
code: number;
message: string;
data: T;
}
// Basic usage
async function getUser(id: number): Promise<User> {
const response: AxiosResponse<ApiResponse<User>> = await axios.get(`/api/users/${id}`);
return response.data.data;
}
11.2 Generic API Client
Type-Safe API Client
class TypedApiClient {
private client: AxiosInstance;
constructor(baseURL: string, config?: AxiosRequestConfig) {
this.client = axios.create({
baseURL,
...config
});
this.setupInterceptors();
}
private setupInterceptors(): void {
this.client.interceptors.response.use(
(response) => response,
(error) => {
console.error('API Error:', error);
return Promise.reject(error);
}
);
}
// Generic GET method
async get<T>(url: string, config?: AxiosRequestConfig): Promise<T> {
const response: AxiosResponse<ApiResponse<T>> = await this.client.get(url, config);
return response.data.data;
}
// Generic POST method
async post<T, D = any>(url: string, data?: D, config?: AxiosRequestConfig): Promise<T> {
const response: AxiosResponse<ApiResponse<T>> = await this.client.post(url, data, config);
return response.data.data;
}
// Generic PUT method
async put<T, D = any>(url: string, data?: D, config?: AxiosRequestConfig): Promise<T> {
const response: AxiosResponse<ApiResponse<T>> = await this.client.put(url, data, config);
return response.data.data;
}
// Generic DELETE method
async delete<T>(url: string, config?: AxiosRequestConfig): Promise<T> {
const response: AxiosResponse<ApiResponse<T>> = await this.client.delete(url, config);
return response.data.data;
}
}
11.3 Service Layer Encapsulation
Typed Service Layer
// User-related types
interface CreateUserRequest {
name: string;
email: string;
password: string;
}
interface UpdateUserRequest {
name?: string;
email?: string;
}
interface UserListParams {
page?: number;
limit?: number;
search?: string;
}
interface PaginatedResponse<T> {
data: T[];
total: number;
page: number;
limit: number;
}
// User service class
class UserService {
private api: TypedApiClient;
constructor(api: TypedApiClient) {
this.api = api;
}
async getUsers(params?: UserListParams): Promise<PaginatedResponse<User>> {
return this.api.get<PaginatedResponse<User>>('/users', { params });
}
async getUser(id: number): Promise<User> {
return this.api.get<User>(`/users/${id}`);
}
async createUser(userData: CreateUserRequest): Promise<User> {
return this.api.post<User, CreateUserRequest>('/users', userData);
}
async updateUser(id: number, userData: UpdateUserRequest): Promise<User> {
return this.api.put<User, UpdateUserRequest>(`/users/${id}`, userData);
}
async deleteUser(id: number): Promise<void> {
return this.api.delete<void>(`/users/${id}`);
}
}
11.4 Advanced Type Applications
Conditional Types and Mapped Types
// Conditional response type mapping
type ResponseData<T> = T extends string ? string :
T extends number ? number :
T extends boolean ? boolean :
T extends Array<infer U> ? U[] :
T;
// API endpoint type mapping
type ApiEndpoints = {
'/users': {
GET: { params?: UserListParams; response: PaginatedResponse<User> };
POST: { data: CreateUserRequest; response: User };
};
'/users/:id': {
GET: { response: User };
PUT: { data: UpdateUserRequest; response: User };
DELETE: { response: void };
};
};
// Type-safe request function
type ExtractParams<T> = T extends { params: infer P } ? P : never;
type ExtractData<T> = T extends { data: infer D } ? D : never;
type ExtractResponse<T> = T extends { response: infer R } ? R : never;
class TypeSafeApiClient {
private client: AxiosInstance;
constructor(baseURL: string) {
this.client = axios.create({ baseURL });
}
async request<
Path extends keyof ApiEndpoints,
Method extends keyof ApiEndpoints[Path]
>(
method: Method,
path: Path,
options?: {
params?: ExtractParams<ApiEndpoints[Path][Method]>;
data?: ExtractData<ApiEndpoints[Path][Method]>;
}
): Promise<ExtractResponse<ApiEndpoints[Path][Method]>> {
const response = await this.client.request({
method: method as string,
url: path as string,
params: options?.params,
data: options?.data
});
return response.data;
}
}
11.5 Error Handling Types
Typed Error Handling
// Error type definitions
interface ApiError {
code: string;
message: string;
details?: Record<string, any>;
}
interface ValidationError extends ApiError {
code: 'VALIDATION_ERROR';
fields: Record<string, string[]>;
}
interface AuthError extends ApiError {
code: 'AUTH_ERROR';
reason: 'INVALID_TOKEN' | 'TOKEN_EXPIRED' | 'INSUFFICIENT_PERMISSIONS';
}
// Typed error handler
class TypedErrorHandler {
static handle(error: any): never {
if (axios.isAxiosError(error)) {
if (error.response) {
const apiError = error.response.data as ApiError;
switch (apiError.code) {
case 'VALIDATION_ERROR':
throw new ValidationErrorException(apiError as ValidationError);
case 'AUTH_ERROR':
throw new AuthErrorException(apiError as AuthError);
default:
throw new ApiErrorException(apiError);
}
} else if (error.request) {
throw new NetworkErrorException(error.message);
}
}
throw new UnknownErrorException(error.message);
}
}
// Custom error classes
class ApiErrorException extends Error {
constructor(public apiError: ApiError) {
super(apiError.message);
this.name = 'ApiErrorException';
}
}
class ValidationErrorException extends ApiErrorException {
constructor(public validationError: ValidationError) {
super(validationError);
this.name = 'ValidationErrorException';
}
}
11.6 Decorator Pattern
Enhancing APIs with Decorators
// Cache decorator
function Cache(ttl: number = 300000) {
return function(target: any, propertyName: string, descriptor: PropertyDescriptor) {
const method = descriptor.value;
const cache = new Map<string, { data: any; timestamp: number }>();
descriptor.value = async function(...args: any[]) {
const key = JSON.stringify(args);
const cached = cache.get(key);
if (cached && Date.now() - cached.timestamp < ttl) {
console.log(`Cache hit: ${propertyName}`);
return cached.data;
}
const result = await method.apply(this, args);
cache.set(key, { data: result, timestamp: Date.now() });
return result;
};
};
}
// Retry decorator
function Retry(maxAttempts: number = 3, delay: number = 1000) {
return function(target: any, propertyName: string, descriptor: PropertyDescriptor) {
const method = descriptor.value;
descriptor.value = async function(...args: any[]) {
let lastError: Error;
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
try {
return await method.apply(this, args);
} catch (error) {
lastError = error as Error;
if (attempt === maxAttempts) break;
console.log(`Retrying ${propertyName} (${attempt}/${maxAttempts})`);
await new Promise(resolve => setTimeout(resolve, delay * attempt));
}
}
throw lastError;
};
};
}
// Service class using decorators
class EnhancedUserService {
constructor(private api: TypedApiClient) {}
@Cache(300000) // 5-minute cache
async getUser(id: number): Promise<User> {
return this.api.get<User>(`/users/${id}`);
}
@Retry(3, 1000) // Max 3 retries, 1-second interval
async createUser(userData: CreateUserRequest): Promise<User> {
return this.api.post<User>('/users', userData);
}
}
11.7 Configuration Types
Strongly-Typed Configuration
interface ApiClientConfig {
baseURL: string;
timeout?: number;
auth?: {
type: 'bearer' | 'basic' | 'api-key';
credentials: string | { username: string; password: string };
};
retry?: {
attempts: number;
delay: number;
};
cache?: {
enabled: boolean;
ttl: number;
};
}
class ConfigurableApiClient {
private client: AxiosInstance;
private config: Required<ApiClientConfig>;
constructor(config: ApiClientConfig) {
this.config = this.mergeWithDefaults(config);
this.client = this.createAxiosInstance();
}
private mergeWithDefaults(config: ApiClientConfig): Required<ApiClientConfig> {
return {
baseURL: config.baseURL,
timeout: config.timeout ?? 10000,
auth: config.auth ?? { type: 'bearer', credentials: '' },
retry: config.retry ?? { attempts: 3, delay: 1000 },
cache: config.cache ?? { enabled: false, ttl: 300000 }
};
}
private createAxiosInstance(): AxiosInstance {
const instance = axios.create({
baseURL: this.config.baseURL,
timeout: this.config.timeout
});
this.setupAuth(instance);
return instance;
}
private setupAuth(instance: AxiosInstance): void {
if (this.config.auth.type === 'bearer') {
instance.defaults.headers.common['Authorization'] =
`Bearer ${this.config.auth.credentials}`;
}
// Other authentication methods...
}
}
Chapter Summary
- TypeScript provides complete type support for Axios
- Generics allow for type-safe API clients
- Interface definitions ensure data structure consistency
- The decorator pattern enhances API functionality
- Strongly-typed configuration improves code quality
Key Takeaways
- Use generics to create reusable, type-safe APIs
- Define interfaces to specify data contracts clearly
- Typed errors improve debugging efficiency
- Decorators enhance functionality without breaking code structure