@alisdev/axios-kit
v1.0.3
Published
Fully-featured Axios wrapper for React + TypeScript projects with token refresh, progress tracking, and React hooks
Maintainers
Readme
@alisdev/axios-kit
Fully-featured Axios wrapper for React + TypeScript projects — with automatic token refresh, progress tracking, and a powerful React hook.
Features
- 🏗️ Registry Pattern — Multiple named Axios instances, fully independent
- 🔐 Silent Token Refresh — Automatic 401 interception & token refresh with request queuing
- 📦 Typed HTTP Methods —
get,post,put,patch,deletewith full generics - ⬇️ Download with Progress — Speed, ETA, and auto-trigger browser download
- ⬆️ Upload with Progress — File/FormData upload with speed tracking
- ⚛️ React Hook (
useApi) — Auto-cancel on unmount, loading/error/data state management - 🍪 Flexible Token Storage —
localStorage,sessionStorage, orcookie - 🚫 Request Cancellation — Full AbortController support
- 🛡️ Custom
ApiError— Structured error class with status, code, and data
Installation
npm install @alisdev/axios-kit axiosNote:
reactis a peer dependency (optional — only needed if you useuseApi).
Quick Start
1. Register an Instance
// main.ts or api.config.ts
import { AxiosKit } from "@alisdev/axios-kit";
AxiosKit.register("main", {
baseURL: import.meta.env.VITE_API_URL,
tokenStorage: {
type: "localStorage",
accessTokenKey: "access_token",
// refreshTokenKey: "refresh_token", // Optional if httpOnlyRefreshToken is true
httpOnlyRefreshToken: true,
},
refreshTokenPath: "/auth/refresh",
timeout: 15000,
});2. Create a Service
// services/user.service.ts
import { AxiosKit, UploadProgress } from "@alisdev/axios-kit";
const api = AxiosKit.use("main");
interface IUser {
_id: string;
firstName: string;
lastName: string;
email: string;
}
export const UserService = {
getProfile: () =>
api.get<IUser>("/user/profile"),
updateProfile: (data: Partial<IUser>) =>
api.put<IUser>("/user/profile", data),
uploadAvatar: (file: File, onProgress?: (p: UploadProgress) => void) =>
api.upload<{ url: string }>("/user/avatar", file, { onProgress }),
downloadReport: (onProgress?: (p: import("@alisdev/axios-kit").DownloadProgress) => void) =>
api.download("/reports/export", { filename: "report.pdf", onProgress }),
};3. Use in a React Component
// components/ProfilePage.tsx
import { useApi, ApiError } from "@alisdev/axios-kit";
import { UserService } from "@/services/user.service";
import { useState } from "react";
export function ProfilePage() {
const [form, setForm] = useState<Partial<IUser>>({});
// Auto-fetch on mount
const { data: profile, loading, error } = useApi(
() => UserService.getProfile()
);
// Manual trigger
const { execute: updateProfile, loading: updating } = useApi(
() => UserService.updateProfile(form),
{ immediate: false }
);
const handleSubmit = async () => {
const result = await updateProfile();
if (result) console.log("Updated!", result);
};
if (loading) return <p>Loading...</p>;
if (error) return <p>Error: {(error as ApiError).message}</p>;
return (
<div>
<p>{profile?.firstName}</p>
<button onClick={handleSubmit} disabled={updating}>
Save
</button>
</div>
);
}API Reference
AxiosKit
| Method | Description |
|---|---|
| AxiosKit.register(name, config) | Register a new named instance |
| AxiosKit.use(name) | Retrieve a registered instance |
| AxiosKit.unregister(name) | Remove a registered instance |
| AxiosKit.clearAll() | Remove all registered instances |
HTTP Methods
| Method | Signature |
|---|---|
| get<T> | (url, options?) → Promise<T> |
| post<T> | (url, data?, options?) → Promise<T> |
| put<T> | (url, data?, options?) → Promise<T> |
| patch<T> | (url, data?, options?) → Promise<T> |
| delete<T> | (url, options?) → Promise<T> |
Transfer Methods
| Method | Signature |
|---|---|
| download | (url, options?) → Promise<Blob> |
| upload<T> | (url, data: File \| FormData, options?) → Promise<T> |
useApi<T> Hook
const { data, loading, error, execute } = useApi(fn, options?);| Option | Type | Default | Description |
|---|---|---|---|
| immediate | boolean | true | Auto-execute on mount |
ApiError
| Property | Type | Description |
|---|---|---|
| status | number | HTTP status code (0 for network errors) |
| code | string | Error code (e.g. "TOKEN_EXPIRED", "NETWORK_ERROR") |
| data | unknown | Server response body |
| originalError | unknown | Raw Axios error |
AxiosKitConfig
interface AxiosKitConfig {
baseURL: string;
tokenStorage?: {
type: "localStorage" | "sessionStorage" | "cookie";
accessTokenKey: string;
refreshTokenKey?: string;
httpOnlyRefreshToken?: boolean;
cookieOptions?: {
expires?: number; // days
path?: string;
secure?: boolean;
};
};
refreshTokenPath?: string;
timeout?: number; // default: 10000
headers?: Record<string, string>;
}Multiple Instances
AxiosKit.register("main", {
baseURL: "https://api.main.com",
tokenStorage: { type: "localStorage", accessTokenKey: "at", refreshTokenKey: "rt" },
refreshTokenPath: "/auth/refresh",
});
AxiosKit.register("payment", {
baseURL: "https://api.payment.com",
tokenStorage: { type: "sessionStorage", accessTokenKey: "p_at", refreshTokenKey: "p_rt" },
refreshTokenPath: "/auth/refresh",
});
const mainApi = AxiosKit.use("main");
const paymentApi = AxiosKit.use("payment");Token Refresh Flow
- A request gets a 401 response
AxiosKitpauses all in-flight requests- Calls the
refreshTokenPathendpoint with the refresh token - Stores the new access token
- Retries all paused requests with the new token
- If refresh fails → clears tokens and throws
ApiErrorwithcode: "SESSION_EXPIRED"
HTTP-Only Refresh Token
If your server uses httpOnly cookies to store the refresh token, set httpOnlyRefreshToken: true.
When this is enabled:
AxiosKitwill not attempt to read the refresh token using client-side JavaScript.- During a 401 response, it will still trigger the
refreshTokenPathendpoint (without sending a body). - The request will automatically include
withCredentials: true, allowing the browser to send thehttpOnlycookie securely to your server. - The server should respond with
{ accessToken: "..." }.
Error Handling
All errors are converted to ApiError:
try {
const data = await api.get<IUser>("/user");
} catch (err) {
const error = err as ApiError;
switch (error.code) {
case "NETWORK_ERROR": // No internet
case "SESSION_EXPIRED": // Refresh token failed
case "REQUEST_CANCELLED": // AbortController cancelled
case "UNAUTHORIZED": // 401 (without refresh config)
default:
console.log(error.status, error.message, error.data);
}
}License
MIT © alisdev
