Amu is a Fetch-first HTTP client for modern JavaScript and TypeScript apps.
It keeps native Fetch behavior while removing the boilerplate that slows teams down in real-world systems.
Safer URL handling by default: strict URL parsing (syntax-level validation only) rejects malformed absolute URLs instead of silently normalizing them.
npm install amu-http// Native Fetch
const res = await fetch('/users');
if (!res.ok) throw new Error('Request failed');
const users = await res.json();
// Axios
const res2 = await axios.get('/users');
const users2 = res2.data;
// Amu
const users3 = await amu.get('/users');- Direct data access (no
res.data) - Deterministic retries (network errors by default; configurable status-code retries)
- Structured errors (HTTP + network)
- URL safety (strict URL parsing, syntax-level validation only)
- Schema validation support
- Tiny footprint (~1.6KB gzip)
Amu is a thin, opinionated layer over Fetch with explicit, testable behavior:
- Throws on non-2xx responses (
AmuError) instead of returningok: falseresponses. - Retries only idempotent methods (
GET,HEAD) by default. - Rejects malformed absolute URLs early (
AmuUrlError).
It also has two standout capabilities:
- Structured Network Errors (
AmuNetworkError) for reliable retry/debug logic - Built-in Schema Validation for runtime-safe API parsing
| Feature | Amu | Axios |
|---|---|---|
| Data access | Direct (await get()) |
res.data |
| Fetch-native | ✅ | ❌ (adapters) |
| Retry semantics | HTTP-aware | Manual |
| Error structure | Typed & structured | Less structured |
| URL validation | Strict | Lenient |
| Bundle size | ~1.6KB (gzip) | ~14KB (gzip) |
Amu provides a dedicated AmuNetworkError with:
kind(network | timeout | abort | unknown)isRetryablecause
This gives you predictable retry and debugging behavior without guessing from generic "Network Error" strings.
Amu supports validator-driven parsing at the request layer:
- Zod-style schema support (via
.parseinterface) + custom validators - custom validation functions
You get runtime data-shape guarantees at the boundary where APIs enter your app.
- Auto JSON / text parsing
- Built-in timeout support
- Retry policies with full control
- Query params support
- Schema validation (Zod + custom)
- Instance-based client factory
- Tiny footprint for browser & server
import amu from 'amu-http';
const users = await amu.get('https://jsonplaceholder.typicode.com/users');If you prefer Axios-like response objects, pass raw: true.
import amu from 'amu-http';
const res = await amu.get('/users', { raw: true });
console.log(res.data); // parsed payload
console.log(res.status); // HTTP status code
console.log(res.statusText); // HTTP status text
console.log(res.headers); // plain header object
console.log(res.config); // resolved request config
console.log(res.request); // native Fetch ResponseWith schema validation, res.data is still validated:
const res = await amu.get('/user/1', {
raw: true,
schema: UserSchema,
});import amu from 'amu-http';
await amu.post('/posts', {
title: 'hello',
body: 'from amu',
});import amu from 'amu-http';
await amu.put('/users/1', { name: 'Updated Name' });
await amu.patch('/users/1', { role: 'admin' });
await amu.delete('/users/1');import amu from 'amu-http';
await amu.get('/users', {
params: { page: 1, limit: 10 },
});You can also pass query params directly in the URL:
const users = await amu.get('/users?page=1&limit=10');Mixing URL query + params also works:
await amu.get('/users?page=1', {
params: { limit: 10 },
});
// Final URL: /users?page=1&limit=10import amu from 'amu-http';
await amu.get('/me', {
headers: {
Authorization: `Bearer ${token}`,
},
});import amu from 'amu-http';
const controller = new AbortController();
const promise = amu.get('/users', { signal: controller.signal });
controller.abort();
await promise; // throws AmuNetworkError with kind: 'abort'signal works together with timeout: whichever aborts first cancels the request.
import amu from 'amu-http';
await amu.get('/stats', {
timeout: 5000,
retries: 2,
});Advanced retry:
await amu.get('/stats', {
retries: {
attempts: 3,
delay: (attempt) => 2 ** attempt * 100,
retryOn: ['network-error', 429, 500, 502, 503, 504],
},
});Retry lifecycle hooks (instance-level + request override):
import amu from 'amu-http';
const api = amu('https://api.example.com', {
retries: {
attempts: 3,
delay: (attempt) => 2 ** attempt * 100,
retryOn: ['network-error', 429, 500, 502, 503, 504],
},
hooks: {
onRetry(ctx) {
console.log('retry', ctx.attempt, ctx.reason, ctx.delay);
},
onRetryComplete(ctx) {
console.log('retry-complete', ctx.success, ctx.totalRetries, ctx.totalDuration);
},
},
});
await api.get('/stats', {
hooks: {
onRetry(ctx) {
// Request-level hooks override same-named instance hooks.
console.log('request retry', ctx.attempt);
},
},
});Hook behavior:
onRetryfires each time a retry is scheduled (before backoff sleep).onRetryCompletefires once when retry workflow finishes (final success or final failure).onRetryCompletefires only if at least one retry happened.
Amu performs strict URL parsing (syntax-level validation only) and rejects malformed absolute URLs.
await amu.get('https:google.com'); // throws AmuUrlError
await amu.get('https://google.com'); // validimport amu, { AmuError } from 'amu-http';
try {
await amu.get('/404');
} catch (err) {
if (err instanceof AmuError) {
console.log(err.status);
console.log(err.data);
console.log(err.headers);
}
}import amu, { AmuNetworkError } from 'amu-http';
try {
await amu.get('/users');
} catch (err) {
if (err instanceof AmuNetworkError) {
console.log(err.kind); // network | timeout | abort | unknown
console.log(err.isRetryable);
console.log(err.cause);
}
}Amu has explicit failure semantics:
-
HTTP 4xx/5xx (e.g. 404, 500)
ThrowsAmuErrorwith:status: HTTP status codedata: parsed response body (JSON/text/null)headers: response headers
-
Network failure (DNS/offline/unreachable transport)
ThrowsAmuNetworkErrorwith:kind: 'network'isRetryablebased on retry policycause: original underlying error
-
Timeout
ThrowsAmuNetworkErrorwith:kind: 'timeout'isRetryable: falseby default
-
Abort
ThrowsAmuNetworkErrorwith:kind: 'abort'isRetryable: falseby default
-
Schema validation failure
ThrowsAmuValidationErrorwith:data: unvalidated response payloadissues: validator-provided issues (if available)
-
Malformed absolute URL (e.g.
https:google.com)
ThrowsAmuUrlErrorbefore request execution.
Retry defaults:
- Network errors are retryable when configured via
retries. - HTTP status retries happen only when status codes are listed in
retryOn. - Retries are idempotent-method-only by default (
GET,HEAD) unlessallowNonIdempotent: true.
import amu from 'amu-http';
import { z } from 'zod';
const User = z.object({
id: z.number(),
name: z.string(),
});
const user = await amu.get('/user/1', {
schema: User,
});import amu from 'amu-http';
const api = amu('https://api.example.com', {
timeout: 8000,
retries: 1,
headers: {
'X-App': 'dashboard',
},
});
await api.get('/me');- Amu (gzip): ~1.6 KB
- Axios (gzip): ~14 KB
Amu is ~9x smaller while keeping essential features for modern runtimes.
- Minimal abstraction over Fetch
- Predictable behavior over magic
- Correct defaults over configuration
- Small surface area over feature bloat
- Production-safe by design
npm run lint
npm run test
npm run test:watch
npm run test:coverage
npm run build
npm run dev