@suamnkayal/reqnest
v1.0.7
Published
A middleware-driven HTTP client for JavaScript (Node.js + Browser)
Maintainers
Readme
Build, control, and extend HTTP requests like a backend engineer.
Reqnest gives you full ownership over the request lifecycle — nothing is hidden, everything is composable.
Getting Started · Plugins · API Docs · vs Axios · Roadmap
🤔 Why Reqnest?
Most HTTP clients treat the request lifecycle as an implementation detail. You get a black box that usually works — until you need something custom, and then you're monkey-patching interceptors and praying nothing breaks.
Reqnest is different. It exposes the full pipeline through a clean middleware system, so you can add caching, retries, deduplication, and rate limiting as composable layers — without touching the core.
Your App → [dedupe] → [cache] → [rateLimit] → [dispatch] → Network
← [dedupe] ← [cache] ← [rateLimit] ← [dispatch] ←✨ Features
📦 Installation
npm install @suamnkayal/reqnest
# or
yarn add @suamnkayal/reqnest
# or
pnpm add @suamnkayal/reqnestRequires Node.js 18+ or a modern browser with native
fetchsupport.
🚀 Quick Start
import reqnest from "@suamnkayal/reqnest";
const res = await reqnest.get("https://jsonplaceholder.typicode.com/posts");
console.log(res.data); // parsed JSON
console.log(res.status); // 200⚙️ Custom Instance
Create isolated client instances with their own base URL, headers, and middleware stack.
import { create, dispatch } from "@suamnkayal/reqnest";
const api = create({
baseURL: "https://api.example.com",
headers: {
"Content-Type": "application/json",
Authorization: "Bearer YOUR_TOKEN",
},
timeout: 8000,
});
// dispatch must always be the LAST middleware — it sends the actual request
api.use(dispatch);
const res = await api.get("/users");⚠️ Always register
dispatchlast. It is the terminal handler that performs the real HTTP call.
🔌 Plugins
Plugins are the superpower of Reqnest. Chain them in any order — each one is a focused, reusable middleware.
import { create, dispatch, cachePlugin, dedupe, rateLimit } from "reqnest";
const api = create({ baseURL: "https://api.example.com" });
api.use(dedupe); // 🔄 collapse identical concurrent requests into one
api.use(cachePlugin); // 🧠 cache responses, skip repeat network calls
api.use(rateLimit(3)); // 🚦 max 3 concurrent requests at any time
api.use(dispatch); // 📡 send the request (always last)Plugin Execution Order
Middleware runs top-to-bottom on the way out, and bottom-to-top on the way back:
Request → dedupe → cache → rateLimit → dispatch → 🌐
Response ← dedupe ← cache ← rateLimit ← dispatch ← 🌐📡 Request Methods
// GET
await api.get("/posts");
// POST
await api.post("/posts", {
title: "Hello Reqnest",
body: "Middleware is everything.",
});
// PUT — full replacement
await api.put("/posts/1", { id: 1, title: "New Title", body: "New body" });
// PATCH — partial update
await api.patch("/posts/1", { title: "Just the title" });
// DELETE
await api.delete("/posts/1");📘 API Reference
Request Config
api.get("/users", {
params: { page: 1, limit: 10 }, // → appended as ?page=1&limit=10
headers: { Authorization: "Bearer token" },
timeout: 5000, // abort after 5 seconds
retry: 3, // retry up to 3 times on failure
cache: true, // use cachePlugin if registered
signal: controller.signal, // manual AbortSignal
transformRequest: (data) => data, // transform outgoing body
transformResponse: (data) => data, // transform incoming response
});Full Config Type
interface ReqnestConfig {
url: string;
method?: "GET" | "POST" | "PUT" | "PATCH" | "DELETE" | "HEAD" | "OPTIONS";
baseURL?: string;
headers?: Record<string, string>;
params?: Record<string, any>;
data?: any;
timeout?: number;
signal?: AbortSignal;
retry?: number;
cache?: boolean;
transformRequest?: (data: any) => any;
transformResponse?: (data: any) => any;
}Response Format
interface ReqnestResponse<T = any> {
data: T; // parsed JSON body
status: number; // e.g. 200
statusText: string; // e.g. "OK"
headers: Record<string, string>; // response headers
raw: Response; // original fetch Response object
}❌ Error Handling
Reqnest throws structured errors on all non-2xx responses. No more manually checking response.ok.
try {
await api.get("/not-found");
} catch (err) {
console.log(err.message); // "Not Found"
console.log(err.status); // 404
console.log(err.data); // server error body (if any)
}🧩 Middleware System
Write your own middleware to hook into any part of the request lifecycle.
// Signature
type Middleware = (ctx: Context, next: () => Promise<void>) => Promise<void>;
interface Context {
config: ReqnestConfig; // outgoing request config (mutate to modify)
response?: ReqnestResponse; // set after dispatch runs
}Auth Middleware
api.use(async (ctx, next) => {
const token = localStorage.getItem("token");
if (token) {
ctx.config.headers = {
...ctx.config.headers,
Authorization: `Bearer ${token}`,
};
}
await next();
});Logging Middleware
api.use(async (ctx, next) => {
const start = Date.now();
console.log(`→ ${ctx.config.method} ${ctx.config.url}`);
await next();
const ms = Date.now() - start;
console.log(`← ${ctx.response.status} in ${ms}ms`);
});Error Reporting Middleware
api.use(async (ctx, next) => {
try {
await next();
} catch (err) {
myErrorTracker.capture({ url: ctx.config.url, status: err.status });
throw err; // always re-throw so callers still receive the error
}
});🔬 Plugin Deep Dives
🔁 Retry
Auto-retries on network error or 5xx. Won't retry 4xx client errors.
// Retry up to 3 times before throwing
await api.get("/flaky-endpoint", { retry: 3 });🧠 Cache
Caches responses in-memory by URL + query params. Second identical request is instant — zero network.
api.use(cachePlugin);
const a = await api.get("/posts"); // → network call
const b = await api.get("/posts"); // → cache hit ⚡ (no network)🔄 Deduplication
Collapses simultaneous identical requests into a single network call. All callers resolve with the same data.
api.use(dedupe);
// 3 requests fire at the same time
const [a, b, c] = await Promise.all([
api.get("/posts"),
api.get("/posts"),
api.get("/posts"),
]);
// ☝️ Only ONE network request was made🚦 Rate Limiting
Queues requests and enforces a concurrency cap to protect your server or stay within API limits.
api.use(rateLimit(2)); // max 2 in-flight at a time
await Promise.all([
api.get("/item/1"), // ← starts immediately
api.get("/item/2"), // ← starts immediately
api.get("/item/3"), // ← queued
api.get("/item/4"), // ← queued
]);⚖️ Reqnest vs Axios
| Feature | Reqnest | Axios | |---|:---:|:---:| | Middleware pipeline | ✅ | ❌ | | Plugin system | ✅ | ❌ | | Full lifecycle control | ✅ | ❌ | | Built on Fetch API | ✅ | ❌ (XHR) | | Request deduplication | ✅ | ❌ | | Smart caching | ✅ | ❌ | | Rate limiting | ✅ | ❌ | | TypeScript support | ✅ | ✅ | | Extensibility | 🔥 High | ⚠️ Limited |
🏗 Architecture
Reqnest is inspired by three ideas:
| Inspiration | What it contributes |
|---|---|
| Koa.js | Async middleware composition with next() |
| Fetch API | Native, modern transport layer |
| Redux middleware | Predictable, composable pipeline |
The core engine is intentionally tiny. Every advanced feature lives in its own isolated plugin — you only pay the cost of what you register.
📂 Project Structure
reqnest/
├── src/
│ ├── core/
│ │ ├── client.ts # create() factory and instance logic
│ │ ├── compose.ts # middleware composition engine
│ │ ├── dispatch.ts # fetch-based terminal middleware
│ │ └── context.ts # Context type definitions
│ │
│ ├── plugins/
│ │ ├── cache.ts # in-memory caching
│ │ ├── dedupe.ts # request deduplication
│ │ ├── rateLimit.ts # concurrency limiting
│ │ └── retry.ts # auto-retry logic
│ │
│ ├── types/
│ │ └── index.ts # shared TypeScript interfaces
│ │
│ └── utils/
│ ├── buildURL.ts # URL + query param serialization
│ └── parseResponse.ts # response parsing helpers
│
├── tests/
├── package.json
├── tsconfig.json
└── README.md🗺 Roadmap
- [ ] 🔥 Interceptors — request/response hooks (cleaner than Axios)
- [ ] 🔥 GraphQL support — first-class
gql()helper - [ ] 🔥 SSR support — cookie forwarding for server-side rendering
- [ ] 🔥 Devtools — browser extension for request inspection
- [ ] 🔥 Persistent cache — IndexedDB / localStorage adapter
- [ ] 🔥 npm publish — official release on the npm registry
👨💻 Author
Suman Kayal · Sky
Full-Stack Developer · MERN Stack · Real-time Systems · AI Tooling
Built in Kolkata, India 🇮🇳
⭐ Support
If Reqnest saved you time or taught you something:
- ⭐ Star this repo
- 🍴 Fork it and build something cool
- 📣 Share it with your dev community
- 🐛 Open issues for bugs or feature ideas
- 🤝 Contribute — PRs are always welcome
📄 License
MIT © Suman Kayal
Built with obsession. Designed for control. Made to be extended.
