An opinionated wrapper around axios library that provides a consistent response structure, built-in retry logic, and simplified error handling for HTTP requests.
Documentation β’ GitHub β’ Issues
- π Consistent Response Format: All responses follow a standardized structure
- π Built-in Retry Logic: Configurable retry mechanism for failed requests
- π‘οΈ Error Handling: Comprehensive error handling with meaningful messages
- π TypeScript Support: Full TypeScript support with generic types
- π§ Header Management: Easy header manipulation and management
- β±οΈ Timeout Support: Configurable request timeouts
- π― Axios Compatible: Built on top of axios with full compatibility
- π Authentication Support: Built-in support for various auth methods
- π Response Interceptors: Automatic response processing and error handling
- π¦ Request Interceptors: Custom request modification capabilities
- Installation
- Quick Start
- Common Usage Cases
- Advanced Features
- Error Handling
- Configuration Options
- Real-World Examples
- Troubleshooting
- API Reference
- Best Practices
- Contributing
npm install @akadenia/apiimport { AxiosApiClient } from "@akadenia/api";
const client = new AxiosApiClient({
baseUrl: "https://api.example.com",
headers: {
"Content-Type": "application/json",
"Authorization": "Bearer your-token"
}
});// GET request
const response = await client.get("/users");
if (response.success) {
console.log("Users:", response.data);
} else {
console.error("Error:", response.message);
}// Fetch a single user
const userResponse = await client.get<User>("/users/1");
if (userResponse.success) {
const user = userResponse.data;
console.log(`${user.firstName} ${user.lastName}`);
}
// Fetch with query parameters
const usersResponse = await client.get<User[]>("/users", {
params: { page: 1, limit: 10 }
});
// Fetch with custom headers
const response = await client.get("/protected-resource", {
headers: { "X-Custom-Header": "value" }
});
// Fetch with response type configuration
const pdfResponse = await client.get("/documents/report.pdf", {
responseType: "blob"
});// Create a new user
interface CreateUserRequest {
firstName: string;
lastName: string;
email: string;
}
const newUser: CreateUserRequest = {
firstName: "John",
lastName: "Doe",
email: "john@example.com"
};
const response = await client.post<User, CreateUserRequest>("/users", newUser);
if (response.success) {
console.log("User created:", response.data);
} else {
console.error("Creation failed:", response.message);
}
// POST with form data
const formData = new FormData();
formData.append("file", fileInput.files[0]);
formData.append("description", "User avatar");
const uploadResponse = await client.post("/upload", formData, {
headers: { "Content-Type": "multipart/form-data" }
});// Update an existing user
const updatedUser = {
firstName: "Jane",
lastName: "Smith"
};
const response = await client.put<User>("/users/1", updatedUser);
if (response.success) {
console.log("User updated:", response.data);
}// Partial update
const partialUpdate = {
firstName: "Jane"
};
const response = await client.patch<User>("/users/1", partialUpdate);
if (response.success) {
console.log("User partially updated:", response.data);
}// Delete a user
const response = await client.delete("/users/1");
if (response.success) {
console.log("User deleted successfully");
} else {
console.error("Deletion failed:", response.message);
}
// Delete with confirmation
const deleteResponse = await client.delete("/users/1", {
data: { confirm: true, reason: "User requested deletion" }
});// Initialize with token
const client = new AxiosApiClient({
baseUrl: "https://api.example.com",
headers: {
"Authorization": "Bearer your-jwt-token"
}
});
// Update token dynamically
client.setHeader("Authorization", "Bearer new-token");
// Handle token expiration
try {
const response = await client.get("/protected-endpoint");
if (!response.success && response.message === "Unauthorized") {
// Refresh token logic
const newToken = await refreshToken();
client.setHeader("Authorization", `Bearer ${newToken}`);
// Retry the request
return await client.get("/protected-endpoint");
}
return response;
} catch (error) {
console.error("Authentication failed:", error);
}const client = new AxiosApiClient({
baseUrl: "https://api.example.com",
headers: {
"X-API-Key": "your-api-key",
"X-API-Version": "v1"
}
});
// Rotate API keys
client.setHeader("X-API-Key", "new-api-key");const client = new AxiosApiClient({
baseUrl: "https://api.example.com",
headers: {
"Authorization": "Basic " + btoa("username:password")
}
});// Define your data types
interface User {
id: number;
firstName: string;
lastName: string;
email: string;
createdAt: string;
}
interface CreateUserRequest {
firstName: string;
lastName: string;
email: string;
}
interface UpdateUserRequest {
firstName?: string;
lastName?: string;
email?: string;
}
interface PaginatedResponse<T> {
data: T[];
total: number;
page: number;
limit: number;
}
// Use generics for type safety
const response = await client.post<User, CreateUserRequest>("/users", {
firstName: "John",
lastName: "Doe",
email: "john@example.com"
});
if (response.success) {
// TypeScript knows this is a User
const user: User = response.data;
console.log(user.firstName); // β
Type-safe
}
// Paginated responses
const usersResponse = await client.get<PaginatedResponse<User>>("/users", {
params: { page: 1, limit: 10 }
});
if (usersResponse.success) {
const { data: users, total, page } = usersResponse.data;
console.log(`Page ${page}: ${users.length} of ${total} users`);
}// Set headers during initialization
const client = new AxiosApiClient({
baseUrl: "https://api.example.com",
headers: {
"Content-Type": "application/json",
"Authorization": "Bearer initial-token"
}
});
// Set headers dynamically
client.setHeader("X-API-Key", "your-api-key");
client.setHeader("Authorization", "Bearer new-token");
// Headers are automatically included in all requests
const response = await client.get("/protected-endpoint");
// Override headers for specific requests
const responseWithCustomHeaders = await client.get("/special-endpoint", {
headers: { "X-Custom-Header": "override-value" }
});try {
const response = await client.get("/users/999");
if (response.success) {
console.log("User found:", response.data);
} else {
// Handle different error types
switch (response.message) {
case "Resource Not Found":
console.log("User doesn't exist");
break;
case "Unauthorized":
console.log("Please log in again");
break;
case "Network Error":
console.log("Check your internet connection");
break;
case "Bad Request":
console.log("Invalid request parameters");
break;
case "Internal Server Error":
console.log("Server error, please try again later");
break;
default:
console.error("Unexpected error:", response.message);
}
}
} catch (error) {
console.error("Request failed:", error);
}const client = new AxiosApiClient({
baseUrl: "https://api.example.com",
retries: 5, // Retry up to 5 times
retryDelay: (retryCount) => {
// Exponential backoff: 1s, 2s, 4s, 8s, 16s
return Math.pow(2, retryCount) * 1000;
},
onRetry: (retryCount, error) => {
console.log(`Retry attempt ${retryCount} for error: ${error.message}`);
// You could log to monitoring service here
}
});
// Linear backoff
const linearClient = new AxiosApiClient({
baseUrl: "https://api.example.com",
retries: 3,
retryDelay: (retryCount) => retryCount * 1000, // 1s, 2s, 3s
});
// Custom retry logic
const customRetryClient = new AxiosApiClient({
baseUrl: "https://api.example.com",
retries: 3,
retryDelay: (retryCount) => {
if (retryCount === 1) return 1000; // 1s
if (retryCount === 2) return 5000; // 5s
return 10000; // 10s
}
});const client = new AxiosApiClient({
baseUrl: "https://api.example.com",
timeout: 10000, // 10 seconds
retries: 2
});
// For long-running operations, you might want a longer timeout
const longRunningClient = new AxiosApiClient({
baseUrl: "https://api.example.com",
timeout: 60000, // 1 minute
retries: 1
});
// For quick operations
const quickClient = new AxiosApiClient({
baseUrl: "https://api.example.com",
timeout: 5000, // 5 seconds
retries: 0
});// JSON responses
const jsonResponse = await client.get("/users");
// File downloads
const fileResponse = await client.get("/files/document.pdf", {
responseType: "blob"
});
// Form data
const formResponse = await client.post("/upload", formData, {
headers: { "Content-Type": "multipart/form-data" }
});
// Array buffer responses
const bufferResponse = await client.get("/files/image.jpg", {
responseType: "arraybuffer"
});
// Stream responses
const streamResponse = await client.get("/files/large-file.zip", {
responseType: "stream"
});// Access the underlying axios instance for advanced use cases
const axiosInstance = client.getInstance();
// You can still use axios interceptors
axiosInstance.interceptors.request.use((config) => {
console.log("Request:", config.method?.toUpperCase(), config.url);
return config;
});
axiosInstance.interceptors.response.use((response) => {
console.log("Response:", response.status, response.config.url);
return response;
});
// Or use axios directly for one-off requests
const directResponse = await axiosInstance.get("/special-endpoint");
// Custom axios configuration
const customAxiosInstance = axios.create({
baseURL: "https://api.example.com",
timeout: 15000,
validateStatus: (status) => status < 500 // Don't reject if status is less than 500
});const client = new AxiosApiClient({
baseUrl: "https://api.example.com"
});
const axiosInstance = client.getInstance();
// Request interceptor for logging
axiosInstance.interceptors.request.use((config) => {
console.log(`[${new Date().toISOString()}] ${config.method?.toUpperCase()} ${config.url}`);
return config;
});
// Response interceptor for data transformation
axiosInstance.interceptors.response.use((response) => {
// Transform response data if needed
if (response.data && response.data.items) {
response.data = response.data.items;
}
return response;
});const client = new AxiosApiClient({
baseUrl: "https://api.example.com"
});
const axiosInstance = client.getInstance();
// Custom error interceptor
axiosInstance.interceptors.response.use(
(response) => response,
(error) => {
if (error.response?.status === 429) {
// Rate limiting - wait and retry
return new Promise(resolve => {
setTimeout(() => {
resolve(axiosInstance.request(error.config));
}, 1000);
});
}
return Promise.reject(error);
}
);All responses follow this consistent structure:
// Success Response
{
success: true,
data: T, // Your actual data
status: number, // HTTP status code
statusText: string, // HTTP status text
headers: object, // Response headers
config: object, // Request configuration
request: object // Request object
}
// Error Response
{
success: false,
message: string, // Human-readable error message
data?: any, // Error response data (if available)
error: any, // Original error object
status?: number, // HTTP status code (if available)
statusText?: string // HTTP status text (if available)
}The library provides standardized error messages for common HTTP status codes:
400- Bad Request401- Unauthorized403- Forbidden404- Resource Not Found422- Unprocessable Entity429- Too Many Requests500- Internal Server Error502- Bad Gateway503- Service Unavailable504- Gateway Timeout- Network errors and unknown errors are also handled
// Pattern 1: Check success flag
const response = await client.get("/users");
if (response.success) {
return response.data;
} else {
throw new Error(response.message);
}
// Pattern 2: Try-catch with success check
try {
const response = await client.get("/users");
if (response.success) {
return response.data;
}
throw new Error(response.message);
} catch (error) {
console.error("Failed to fetch users:", error.message);
throw error;
}
// Pattern 3: Handle specific error types
const response = await client.get("/users");
if (!response.success) {
switch (response.status) {
case 401:
await refreshToken();
return await client.get("/users");
case 404:
return [];
default:
throw new Error(response.message);
}
}
return response.data;const client = new AxiosApiClient(
baseUrl, // Required: Base URL for all requests
headers, // Optional: Default headers
timeout, // Optional: Request timeout in milliseconds (default: 30000)
retries, // Optional: Number of retry attempts (default: 3)
retryDelay, // Optional: Function to calculate delay between retries
onRetry // Optional: Callback function called on each retry
);// Exponential backoff (recommended)
const exponentialClient = new AxiosApiClient(
"https://api.example.com",
{},
30000,
3,
(retryCount) => Math.pow(2, retryCount) * 1000,
(retryCount, error) => console.log(`Retry ${retryCount}: ${error.message}`)
);
// Linear backoff
const linearClient = new AxiosApiClient(
"https://api.example.com",
{},
30000,
3,
(retryCount) => retryCount * 1000
);
// Custom retry logic
const customClient = new AxiosApiClient(
"https://api.example.com",
{},
30000,
3,
(retryCount) => {
if (retryCount === 1) return 1000;
if (retryCount === 2) return 5000;
return 10000;
}
);interface User {
id: number;
firstName: string;
lastName: string;
email: string;
createdAt: string;
}
interface CreateUserRequest {
firstName: string;
lastName: string;
email: string;
}
interface UpdateUserRequest {
firstName?: string;
lastName?: string;
email?: string;
}
class UserService {
private client: AxiosApiClient;
constructor() {
this.client = new AxiosApiClient({
baseUrl: "https://api.example.com",
headers: {
"Content-Type": "application/json",
"Authorization": "Bearer your-token"
},
retries: 3,
timeout: 10000
});
}
async getUsers(page: number = 1, limit: number = 10): Promise<User[]> {
const response = await this.client.get<User[]>("/users", {
params: { page, limit }
});
if (response.success) {
return response.data;
}
throw new Error(response.message);
}
async getUserById(id: number): Promise<User> {
const response = await this.client.get<User>(`/users/${id}`);
if (response.success) {
return response.data;
}
throw new Error(response.message);
}
async createUser(userData: CreateUserRequest): Promise<User> {
const response = await this.client.post<User>("/users", userData);
if (response.success) {
return response.data;
}
throw new Error(response.message);
}
async updateUser(id: number, userData: UpdateUserRequest): Promise<User> {
const response = await this.client.put<User>(`/users/${id}`, userData);
if (response.success) {
return response.data;
}
throw new Error(response.message);
}
async deleteUser(id: number): Promise<void> {
const response = await this.client.delete(`/users/${id}`);
if (!response.success) {
throw new Error(response.message);
}
}
async searchUsers(query: string): Promise<User[]> {
const response = await this.client.get<User[]>("/users/search", {
params: { q: query }
});
if (response.success) {
return response.data;
}
throw new Error(response.message);
}
}
// Usage
const userService = new UserService();
try {
const users = await userService.getUsers(1, 20);
console.log(`Found ${users.length} users`);
const newUser = await userService.createUser({
firstName: "John",
lastName: "Doe",
email: "john@example.com"
});
console.log("Created user:", newUser);
const updatedUser = await userService.updateUser(newUser.id, {
firstName: "Jane"
});
console.log("Updated user:", updatedUser);
} catch (error) {
console.error("User service error:", error.message);
}interface Product {
id: number;
name: string;
price: number;
description: string;
category: string;
stock: number;
}
interface Order {
id: number;
userId: number;
products: Array<{ productId: number; quantity: number }>;
total: number;
status: 'pending' | 'confirmed' | 'shipped' | 'delivered';
createdAt: string;
}
class EcommerceAPI {
private client: AxiosApiClient;
constructor(apiKey: string) {
this.client = new AxiosApiClient({
baseUrl: "https://api.ecommerce.com",
headers: {
"X-API-Key": apiKey,
"Content-Type": "application/json"
},
retries: 3,
timeout: 15000
});
}
async getProducts(category?: string, page: number = 1): Promise<Product[]> {
const params: any = { page };
if (category) params.category = category;
const response = await this.client.get<Product[]>("/products", { params });
if (response.success) {
return response.data;
}
throw new Error(response.message);
}
async getProduct(id: number): Promise<Product> {
const response = await this.client.get<Product>(`/products/${id}`);
if (response.success) {
return response.data;
}
throw new Error(response.message);
}
async createOrder(orderData: Omit<Order, 'id' | 'createdAt'>): Promise<Order> {
const response = await this.client.post<Order>("/orders", orderData);
if (response.success) {
return response.data;
}
throw new Error(response.message);
}
async updateOrderStatus(orderId: number, status: Order['status']): Promise<Order> {
const response = await this.client.patch<Order>(`/orders/${orderId}`, { status });
if (response.success) {
return response.data;
}
throw new Error(response.message);
}
}interface UploadResponse {
id: string;
filename: string;
size: number;
url: string;
uploadedAt: string;
}
class FileUploadService {
private client: AxiosApiClient;
constructor() {
this.client = new AxiosApiClient({
baseUrl: "https://api.files.com",
headers: {
"Authorization": "Bearer your-token"
},
timeout: 60000, // Longer timeout for file uploads
retries: 2
});
}
async uploadFile(file: File, description?: string): Promise<UploadResponse> {
const formData = new FormData();
formData.append("file", file);
if (description) {
formData.append("description", description);
}
const response = await this.client.post<UploadResponse>("/upload", formData, {
headers: { "Content-Type": "multipart/form-data" }
});
if (response.success) {
return response.data;
}
throw new Error(response.message);
}
async uploadMultipleFiles(files: File[]): Promise<UploadResponse[]> {
const formData = new FormData();
files.forEach((file, index) => {
formData.append(`files[${index}]`, file);
});
const response = await this.client.post<UploadResponse[]>("/upload/multiple", formData, {
headers: { "Content-Type": "multipart/form-data" }
});
if (response.success) {
return response.data;
}
throw new Error(response.message);
}
async downloadFile(fileId: string): Promise<Blob> {
const response = await this.client.get(`/files/${fileId}`, {
responseType: "blob"
});
if (response.success) {
return response.data;
}
throw new Error(response.message);
}
}// Problem: Frequent network errors
const client = new AxiosApiClient({
baseUrl: "https://api.example.com",
retries: 5, // Increase retries
retryDelay: (retryCount) => Math.pow(2, retryCount) * 1000, // Exponential backoff
timeout: 30000 // Increase timeout
});// Problem: Token expiration
const client = new AxiosApiClient({
baseUrl: "https://api.example.com"
});
// Solution: Implement token refresh
const axiosInstance = client.getInstance();
axiosInstance.interceptors.response.use(
(response) => response,
async (error) => {
if (error.response?.status === 401) {
const newToken = await refreshToken();
client.setHeader("Authorization", `Bearer ${newToken}`);
error.config.headers.Authorization = `Bearer ${newToken}`;
return axiosInstance.request(error.config);
}
return Promise.reject(error);
}
);// Problem: 429 Too Many Requests
const client = new AxiosApiClient({
baseUrl: "https://api.example.com",
retries: 3,
retryDelay: (retryCount) => retryCount * 2000 // Wait 2s, 4s, 6s
});// Problem: Large files timing out
const client = new AxiosApiClient({
baseUrl: "https://api.example.com",
timeout: 300000, // 5 minutes for large uploads
retries: 1 // Don't retry large uploads
});// Enable detailed logging
const client = new AxiosApiClient({
baseUrl: "https://api.example.com"
});
const axiosInstance = client.getInstance();
// Log all requests
axiosInstance.interceptors.request.use((config) => {
console.log("π Request:", {
method: config.method?.toUpperCase(),
url: config.url,
headers: config.headers,
data: config.data
});
return config;
});
// Log all responses
axiosInstance.interceptors.response.use(
(response) => {
console.log("β
Response:", {
status: response.status,
url: response.config.url,
data: response.data
});
return response;
},
(error) => {
console.log("β Error:", {
status: error.response?.status,
url: error.config?.url,
message: error.message
});
return Promise.reject(error);
}
);new AxiosApiClient({
baseUrl: string,
headers?: Headers,
timeout?: number, // default: 30000
retries?: number, // default: 3
retryDelay?: (retryCount: number) => number,
onRetry?: (retryCount: number, error: any) => void
})get<TResponse>(url: string, config?: AxiosRequestConfig): Promise<AkadeniaApiResponse<TResponse>>post<TResponse, TBody>(url: string, data?: TBody, config?: AxiosRequestConfig): Promise<AkadeniaApiResponse<TResponse>>put<TResponse, TBody>(url: string, data?: TBody, config?: AxiosRequestConfig): Promise<AkadeniaApiResponse<TResponse>>patch<TResponse, TBody>(url: string, data?: TBody, config?: AxiosRequestConfig): Promise<AkadeniaApiResponse<TResponse>>delete<TResponse>(url: string, config?: AxiosRequestConfig): Promise<AkadeniaApiResponse<TResponse>>
setHeader(name: string, value: string): void- Set a header for all future requestsgetInstance(): AxiosInstance- Get the underlying axios instance
interface AkadeniaApiSuccessResponse<T = any> extends AxiosResponse<T> {
success: true;
message?: string;
}
interface AkadeniaApiErrorResponse {
success: false;
message: string;
data?: any;
error: any;
status?: number;
statusText?: string;
}
type AkadeniaApiResponse<T = any> = AkadeniaApiSuccessResponse<T> | AkadeniaApiErrorResponse;- Always check
response.successbefore accessingresponse.data - Use TypeScript generics for better type safety
- Configure retry logic for production environments
- Set appropriate timeouts based on your API's response times
- Handle errors gracefully using the standardized error messages
- Use header management for authentication and API keys
- Implement exponential backoff for retry strategies
- Use interceptors for cross-cutting concerns like logging and authentication
- Handle rate limiting with appropriate retry delays
- Validate responses before processing data
- Use appropriate response types for different content (JSON, blob, etc.)
- Implement proper error boundaries in your application
- Monitor and log API performance and errors
- Use environment-specific configurations for different deployment stages
We welcome contributions! Please feel free to submit a Pull Request.
git clone https://github.com/akadenia/AkadeniaAPI.git
cd AkadeniaAPI
npm install
npm run build
npm testWe follow Conventional Commits for our semantic release process. We prefer commit messages to include a scope in parentheses for better categorization and changelog generation.
type(scope): description
[optional body]
[optional footer]
## β
Preferred - with scope
feat(api): add new retry configuration options
fix(auth): resolve token refresh issue
docs(readme): add troubleshooting section
chore(deps): update axios to latest version
## β Less preferred - without scope
feat: add new retry configuration options
fix: resolve token refresh issue
docs: add troubleshooting section
chore: update axios to latest versionapi- API client functionalityauth- Authentication and authorizationtypes- TypeScript type definitionsdocs- Documentation updatesdeps- Dependency updatestest- Test-related changesbuild- Build and build toolingci- CI/CD configuration
feat- New featuresfix- Bug fixesdocs- Documentation changesstyle- Code style changes (formatting, etc.)refactor- Code refactoringtest- Adding or updating testschore- Maintenance tasks
For support, please open an issue on GitHub.