axios-engine
v1.3.2
Published
A platform-agnostic networking library built on Axios for React Native, React, Next.js, and Node.js
Maintainers
Readme
@yunush/api-engine
A platform-agnostic networking library built on Axios, designed to work seamlessly across React Native, React (Web), Next.js, and Node.js.
Features
- 🔐 Token injection — automatically attaches Bearer tokens to every request
- ♻️ Token refresh — handles 401 responses, retries the original request, and queues concurrent calls during refresh
- 🔁 Retry logic — configurable retry attempts for network failures
- ⏱️ Response delay — runtime-toggleable delays for testing loading states
- 🖥️ Backend-controlled delay — honour
X-API-Delayresponse headers from your server - 🪵 Structured logging — request/response logs with timing, opt-in per environment
- 🎯 Normalised errors — every error follows
{ status, message, data }regardless of source - 🧩 Fully typed — complete TypeScript definitions included
Installation
npm install @yunush/api-engine axios
# or
yarn add @yunush/api-engine axios
axiosis a peer dependency and must be installed alongside the library.
Quick Start
import { createApiEngine } from "@yunush/api-engine";
export const api = createApiEngine({
baseURL: "https://api.myapp.com",
});
// Use it anywhere
const users = await api.get("/users");
const created = await api.post("/users", { name: "Alice" });Configuration
createApiEngine({
baseURL: "https://api.myapp.com",
// Return the current auth token (sync or async)
getToken: async () => AsyncStorage.getItem("token"),
// Called on 401 — return a fresh token
refreshToken: async () => {
const res = await fetch("/auth/refresh");
const { token } = await res.json();
return token;
},
// Called when refresh fails
onLogout: () => router.push("/login"),
// Retry network failures up to N times
retry: 2,
// Static delay applied to every response (ms)
responseDelay: 0,
// Log requests and responses
enableLogging: true,
// Any extra Axios defaults
axiosConfig: { timeout: 15_000 },
});| Option | Type | Default | Description |
|---|---|---|---|
| baseURL | string | — | Base URL for all requests |
| getToken | () => string \| null | — | Returns the current auth token |
| refreshToken | () => Promise<string> | — | Fetches a new token on 401 |
| onLogout | () => void | — | Called when refresh fails |
| retry | number | 0 | Network error retry count |
| responseDelay | number | 0 | Static delay in ms |
| enableLogging | boolean | false | Enable console logging |
| axiosConfig | AxiosRequestConfig | {} | Extra Axios options |
Response Delay
Useful for testing loading states, simulating slow networks, and demos.
import { setResponseDelay } from "@yunush/api-engine";
// Enable a 5-second delay globally
setResponseDelay(true, 5000);
// Disable it
setResponseDelay(false);Backend-Controlled Delay
Your backend can control delay without a client deploy by setting the X-API-Delay header:
// Express example
app.get("/users", (req, res) => {
res.set("X-API-Delay", "3000"); // 3-second delay
res.json({ users: [] });
});Admin Config Pattern
const config = await api.get<{ apiDelayEnabled: boolean; apiDelayTime: number }>("/app-config");
setResponseDelay(config.apiDelayEnabled, config.apiDelayTime);This lets admins toggle API throttling at runtime without redeploying your app.
Platform Examples
React Native (AsyncStorage)
import AsyncStorage from "@react-native-async-storage/async-storage";
import { createApiEngine } from "@yunush/api-engine";
export const api = createApiEngine({
baseURL: "https://api.myapp.com",
getToken: () => AsyncStorage.getItem("token"),
refreshToken: async () => {
const res = await fetch("https://api.myapp.com/auth/refresh");
const { token } = await res.json();
await AsyncStorage.setItem("token", token);
return token;
},
onLogout: () => {
AsyncStorage.removeItem("token");
// navigate to login screen
},
retry: 2,
enableLogging: __DEV__,
});React / Next.js (localStorage)
import { createApiEngine } from "@yunush/api-engine";
export const api = createApiEngine({
baseURL: process.env.NEXT_PUBLIC_API_URL!,
getToken: () => localStorage.getItem("token"),
refreshToken: async () => {
const res = await fetch("/api/auth/refresh");
const { token } = await res.json();
localStorage.setItem("token", token);
return token;
},
onLogout: () => {
localStorage.removeItem("token");
window.location.href = "/login";
},
enableLogging: process.env.NODE_ENV === "development",
});Node.js
import { createApiEngine } from "@yunush/api-engine";
const api = createApiEngine({
baseURL: "https://api.partner.com",
getToken: () => process.env.API_TOKEN ?? null,
retry: 3,
enableLogging: true,
});Error Handling
All errors are normalised into ApiError — a typed subclass of Error:
import { ApiError } from "@yunush/api-engine";
try {
const data = await api.get("/users");
} catch (err) {
if (err instanceof ApiError) {
console.log(err.status); // e.g. 404
console.log(err.message); // e.g. "Not Found"
console.log(err.data); // response body (if any)
}
}| Field | Type | Description |
|---|---|---|
| status | number | HTTP status code (0 = network error, -1 = unknown) |
| message | string | Human-readable error message |
| data | unknown | Raw response body from the server |
Token Refresh Flow
Request → 401 response
↓
Call refreshToken()
↓
Success?
Yes → update Authorization header → retry original request
No → call onLogout() → throw ApiErrorConcurrent requests that arrive while a refresh is in-flight are queued and replayed automatically once the new token is available.
Logging
When enableLogging: true:
[API Engine] → GET /users
[API Engine] ← GET /users [200] 142ms
[API Engine] Token expired — refreshing...
[API Engine] ← GET /users [200] 98msDisable in production:
enableLogging: process.env.NODE_ENV !== "production"Best Practices
Create a single shared instance
// lib/api.ts
export const api = createApiEngine({ ... });Fetch backend config on app startup
// app entry point
const config = await api.get("/app-config");
setResponseDelay(config.delayEnabled, config.delayMs);Don't create multiple instances — multiple interceptor stacks lead to unpredictable behaviour.
License
MIT © yunush
