@rutansh0101/fetchify 1.0.0 → 1.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +119 -1091
- package/fetchify.js +7 -0
- package/package.json +1 -1
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Rutansh Chawla
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,907 +1,191 @@
|
|
|
1
1
|
# Fetchify - A Lightweight HTTP Client Library
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
[](https://www.npmjs.com/package/@rutansh0101/fetchify)
|
|
4
|
+
[](https://www.npmjs.com/package/@rutansh0101/fetchify)
|
|
5
|
+
[](https://github.com/Rutansh0101/Fetchify-Rutansh-Chawla/blob/master/LICENSE)
|
|
4
6
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
## Table of Contents
|
|
8
|
-
|
|
9
|
-
1. [Installation & Setup](#installation--setup)
|
|
10
|
-
2. [Basic Usage](#basic-usage)
|
|
11
|
-
3. [Features Overview](#features-overview)
|
|
12
|
-
4. [Detailed Feature Explanation](#detailed-feature-explanation)
|
|
13
|
-
- [Instance Creation](#1-instance-creation)
|
|
14
|
-
- [HTTP Methods](#2-http-methods)
|
|
15
|
-
- [Configuration Management](#3-configuration-management)
|
|
16
|
-
- [Timeout Handling](#4-timeout-handling)
|
|
17
|
-
- [Request Interceptors](#5-request-interceptors)
|
|
18
|
-
- [Response Interceptors](#6-response-interceptors)
|
|
19
|
-
- [Interceptor Chain Execution](#7-interceptor-chain-execution)
|
|
20
|
-
5. [Architecture & Design Decisions](#architecture--design-decisions)
|
|
21
|
-
6. [Code Examples](#code-examples)
|
|
22
|
-
7. [Error Handling](#error-handling)
|
|
23
|
-
8. [API Reference](#api-reference)
|
|
7
|
+
A modern, lightweight HTTP client library built on top of the native Fetch API. It provides a clean, axios-like interface with support for interceptors, request/response transformation, and timeout handling.
|
|
24
8
|
|
|
25
9
|
---
|
|
26
10
|
|
|
27
|
-
## Installation
|
|
28
|
-
|
|
29
|
-
### Prerequisites
|
|
30
|
-
- Node.js environment with ES6+ module support
|
|
31
|
-
- Modern browser with Fetch API support
|
|
32
|
-
|
|
33
|
-
### Setup
|
|
34
|
-
1. Clone or download the project
|
|
35
|
-
2. Ensure your project supports ES6 modules (add `"type": "module"` in package.json)
|
|
36
|
-
3. Import Fetchify in your code:
|
|
11
|
+
## Installation
|
|
37
12
|
|
|
38
|
-
```
|
|
39
|
-
|
|
13
|
+
```bash
|
|
14
|
+
npm install @rutansh0101/fetchify
|
|
40
15
|
```
|
|
41
16
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
## Basic Usage
|
|
45
|
-
|
|
46
|
-
### Creating an Instance
|
|
17
|
+
## Quick Start
|
|
47
18
|
|
|
48
19
|
```javascript
|
|
49
|
-
import fetchify from
|
|
20
|
+
import fetchify from '@rutansh0101/fetchify';
|
|
50
21
|
|
|
51
|
-
// Create an instance
|
|
22
|
+
// Create an instance
|
|
52
23
|
const api = fetchify.create({
|
|
53
24
|
baseURL: 'https://api.example.com',
|
|
54
25
|
timeout: 5000,
|
|
55
26
|
headers: {
|
|
56
|
-
'Content-Type': 'application/json'
|
|
57
|
-
'Authorization': 'Bearer YOUR_TOKEN'
|
|
27
|
+
'Content-Type': 'application/json'
|
|
58
28
|
}
|
|
59
29
|
});
|
|
60
|
-
```
|
|
61
30
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
```javascript
|
|
65
|
-
// GET request
|
|
31
|
+
// Make requests
|
|
66
32
|
const response = await api.get('/users');
|
|
67
33
|
const users = await response.json();
|
|
68
|
-
|
|
69
|
-
// POST request
|
|
70
|
-
const newUser = await api.post('/users', {
|
|
71
|
-
body: JSON.stringify({ name: 'John', email: 'john@example.com' })
|
|
72
|
-
});
|
|
73
|
-
|
|
74
|
-
// PUT request
|
|
75
|
-
const updated = await api.put('/users/1', {
|
|
76
|
-
body: JSON.stringify({ name: 'John Updated' })
|
|
77
|
-
});
|
|
78
|
-
|
|
79
|
-
// PATCH request
|
|
80
|
-
const patched = await api.patch('/users/1', {
|
|
81
|
-
body: JSON.stringify({ email: 'newemail@example.com' })
|
|
82
|
-
});
|
|
83
|
-
|
|
84
|
-
// DELETE request
|
|
85
|
-
await api.delete('/users/1');
|
|
86
34
|
```
|
|
87
35
|
|
|
88
36
|
---
|
|
89
37
|
|
|
90
|
-
## Features
|
|
38
|
+
## Features
|
|
91
39
|
|
|
92
|
-
✅ **Axios-like API** - Familiar
|
|
93
|
-
✅ **Request/Response Interceptors** - Transform requests and responses
|
|
40
|
+
✅ **Axios-like API** - Familiar and easy to use
|
|
41
|
+
✅ **Request/Response Interceptors** - Transform requests and responses
|
|
94
42
|
✅ **Timeout Support** - Abort requests after specified duration
|
|
95
43
|
✅ **Configuration Merging** - Instance, request-level, and default configs
|
|
96
|
-
✅ **All HTTP Methods** - GET, POST, PUT, PATCH, DELETE
|
|
97
|
-
✅ **
|
|
98
|
-
✅ **Promise Chain Architecture** - Clean async operation handling
|
|
99
|
-
✅ **Error Transformation** - Custom error messages for timeouts and failures
|
|
44
|
+
✅ **All HTTP Methods** - GET, POST, PUT, PATCH, DELETE
|
|
45
|
+
✅ **Lightweight** - ~5KB with zero dependencies
|
|
100
46
|
|
|
101
47
|
---
|
|
102
48
|
|
|
103
|
-
##
|
|
104
|
-
|
|
105
|
-
### 1. Instance Creation
|
|
106
|
-
|
|
107
|
-
**What it does:**
|
|
108
|
-
Creates a reusable HTTP client instance with shared configuration.
|
|
109
|
-
|
|
110
|
-
**Why it's needed:**
|
|
111
|
-
- Avoid repeating baseURL, headers, and timeout in every request
|
|
112
|
-
- Create multiple instances for different APIs (e.g., one for auth, one for data)
|
|
113
|
-
- Centralize common configuration
|
|
114
|
-
|
|
115
|
-
**How it works:**
|
|
116
|
-
|
|
117
|
-
```javascript
|
|
118
|
-
const create = (config) => {
|
|
119
|
-
return new Fetchify(config);
|
|
120
|
-
}
|
|
121
|
-
```
|
|
122
|
-
|
|
123
|
-
The `create` method:
|
|
124
|
-
1. Accepts a configuration object
|
|
125
|
-
2. Instantiates the Fetchify class
|
|
126
|
-
3. Merges user config with default config
|
|
127
|
-
4. Returns the configured instance
|
|
128
|
-
|
|
129
|
-
**Implementation Details:**
|
|
130
|
-
|
|
131
|
-
```javascript
|
|
132
|
-
constructor(newConfig) {
|
|
133
|
-
this.config = this.#mergeConfig(newConfig);
|
|
134
|
-
}
|
|
135
|
-
```
|
|
136
|
-
|
|
137
|
-
The constructor:
|
|
138
|
-
- Calls `#mergeConfig` to combine default config with user-provided config
|
|
139
|
-
- Stores the merged config in `this.config`
|
|
140
|
-
- This config becomes the base for all future requests
|
|
141
|
-
|
|
142
|
-
**Default Configuration:**
|
|
143
|
-
|
|
144
|
-
```javascript
|
|
145
|
-
config = {
|
|
146
|
-
headers: {
|
|
147
|
-
'Content-Type': 'application/json'
|
|
148
|
-
},
|
|
149
|
-
timeout: 1000 // 1 second default timeout
|
|
150
|
-
};
|
|
151
|
-
```
|
|
152
|
-
|
|
153
|
-
---
|
|
154
|
-
|
|
155
|
-
### 2. HTTP Methods
|
|
156
|
-
|
|
157
|
-
**What it does:**
|
|
158
|
-
Provides convenient methods for different HTTP verbs (GET, POST, PUT, PATCH, DELETE).
|
|
159
|
-
|
|
160
|
-
**Why it's needed:**
|
|
161
|
-
- Simplifies request syntax
|
|
162
|
-
- Makes code more readable (`api.get()` vs `api.request({ method: 'GET' })`)
|
|
163
|
-
- Follows REST conventions
|
|
49
|
+
## Basic Usage
|
|
164
50
|
|
|
165
|
-
|
|
51
|
+
### Making Requests
|
|
166
52
|
|
|
167
53
|
```javascript
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
config: { ...tempConfig, method: 'GET' }
|
|
172
|
-
});
|
|
173
|
-
}
|
|
174
|
-
```
|
|
175
|
-
|
|
176
|
-
Each method:
|
|
177
|
-
1. Takes an endpoint and optional temporary config
|
|
178
|
-
2. Merges the HTTP method into the config
|
|
179
|
-
3. Calls the private `#request` method
|
|
180
|
-
4. Returns a Promise that resolves to the fetch Response object
|
|
181
|
-
|
|
182
|
-
**All Methods:**
|
|
183
|
-
|
|
184
|
-
- **GET**: Retrieve data (no body allowed by HTTP spec)
|
|
185
|
-
- **POST**: Create new resources (requires body)
|
|
186
|
-
- **PUT**: Update entire resources (requires body)
|
|
187
|
-
- **PATCH**: Partially update resources (requires body)
|
|
188
|
-
- **DELETE**: Remove resources (usually no body)
|
|
189
|
-
|
|
190
|
-
**Example with Body:**
|
|
54
|
+
// GET request
|
|
55
|
+
const response = await api.get('/users');
|
|
56
|
+
const data = await response.json();
|
|
191
57
|
|
|
192
|
-
|
|
58
|
+
// POST request
|
|
193
59
|
await api.post('/users', {
|
|
194
|
-
body: JSON.stringify({ name: 'John' })
|
|
195
|
-
headers: { 'Custom-Header': 'value' }
|
|
60
|
+
body: JSON.stringify({ name: 'John', email: 'john@example.com' })
|
|
196
61
|
});
|
|
197
|
-
```
|
|
198
|
-
|
|
199
|
-
---
|
|
200
|
-
|
|
201
|
-
### 3. Configuration Management
|
|
202
|
-
|
|
203
|
-
**What it does:**
|
|
204
|
-
Handles merging of configurations at three levels: default, instance, and request.
|
|
205
|
-
|
|
206
|
-
**Why it's needed:**
|
|
207
|
-
- Allow global defaults that can be overridden
|
|
208
|
-
- Support instance-specific configs (different baseURLs)
|
|
209
|
-
- Enable request-specific overrides (custom timeout for one request)
|
|
210
|
-
|
|
211
|
-
**Configuration Priority (highest to lowest):**
|
|
212
|
-
1. Request-level config (passed to `get()`, `post()`, etc.)
|
|
213
|
-
2. Instance-level config (passed to `create()`)
|
|
214
|
-
3. Default config (hardcoded in the class)
|
|
215
|
-
|
|
216
|
-
**How it works:**
|
|
217
|
-
|
|
218
|
-
```javascript
|
|
219
|
-
#mergeConfig(newConfig) {
|
|
220
|
-
return {
|
|
221
|
-
...this.config, // Existing config
|
|
222
|
-
...newConfig, // New config (overrides existing)
|
|
223
|
-
headers: { // Deep merge for headers
|
|
224
|
-
...this.config.headers,
|
|
225
|
-
...newConfig?.headers
|
|
226
|
-
}
|
|
227
|
-
};
|
|
228
|
-
}
|
|
229
|
-
```
|
|
230
|
-
|
|
231
|
-
**Why Deep Merge for Headers?**
|
|
232
|
-
|
|
233
|
-
Without deep merge:
|
|
234
|
-
```javascript
|
|
235
|
-
// Instance config
|
|
236
|
-
headers: { 'Authorization': 'Bearer token', 'Content-Type': 'application/json' }
|
|
237
|
-
|
|
238
|
-
// Request config
|
|
239
|
-
headers: { 'X-Custom': 'value' }
|
|
240
|
-
|
|
241
|
-
// Result WITHOUT deep merge (WRONG):
|
|
242
|
-
headers: { 'X-Custom': 'value' } // Lost Authorization!
|
|
243
62
|
|
|
244
|
-
//
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
'X-Custom': 'value'
|
|
249
|
-
}
|
|
250
|
-
```
|
|
251
|
-
|
|
252
|
-
**Two Merge Methods:**
|
|
253
|
-
|
|
254
|
-
1. `#mergeConfig(newConfig)` - Merges with instance config (`this.config`)
|
|
255
|
-
2. `#mergeConfigs(config1, config2)` - Merges two arbitrary configs
|
|
256
|
-
|
|
257
|
-
---
|
|
258
|
-
|
|
259
|
-
### 4. Timeout Handling
|
|
260
|
-
|
|
261
|
-
**What it does:**
|
|
262
|
-
Automatically aborts requests that take longer than specified duration.
|
|
263
|
-
|
|
264
|
-
**Why it's needed:**
|
|
265
|
-
- Prevent requests from hanging indefinitely
|
|
266
|
-
- Improve user experience with faster feedback
|
|
267
|
-
- Handle slow/unresponsive servers gracefully
|
|
268
|
-
- Free up resources from stalled connections
|
|
269
|
-
|
|
270
|
-
**How it works:**
|
|
271
|
-
|
|
272
|
-
```javascript
|
|
273
|
-
// 1. Create AbortController
|
|
274
|
-
const abortController = new AbortController();
|
|
275
|
-
const timeout = config.timeout || this.config.timeout || 0;
|
|
276
|
-
|
|
277
|
-
// 2. Set timer to abort
|
|
278
|
-
let timeOutId;
|
|
279
|
-
if (timeout) {
|
|
280
|
-
timeOutId = setTimeout(() => {
|
|
281
|
-
abortController.abort(); // Trigger abort after timeout
|
|
282
|
-
}, timeout);
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
// 3. Link signal to fetch
|
|
286
|
-
config.signal = abortController.signal;
|
|
287
|
-
|
|
288
|
-
// 4. Execute fetch
|
|
289
|
-
const response = await fetch(url, config);
|
|
290
|
-
|
|
291
|
-
// 5. Clear timer in finally block
|
|
292
|
-
finally {
|
|
293
|
-
if (timeOutId) {
|
|
294
|
-
clearTimeout(timeOutId); // Prevent memory leak
|
|
295
|
-
}
|
|
296
|
-
}
|
|
297
|
-
```
|
|
298
|
-
|
|
299
|
-
**Why Use AbortController?**
|
|
300
|
-
|
|
301
|
-
- Native browser API for cancelling fetch requests
|
|
302
|
-
- Cleaner than old XMLHttpRequest cancellation
|
|
303
|
-
- Works across all modern browsers
|
|
304
|
-
- Can abort multiple operations with one controller
|
|
63
|
+
// PUT request
|
|
64
|
+
await api.put('/users/1', {
|
|
65
|
+
body: JSON.stringify({ name: 'Jane Doe' })
|
|
66
|
+
});
|
|
305
67
|
|
|
306
|
-
|
|
68
|
+
// PATCH request
|
|
69
|
+
await api.patch('/users/1', {
|
|
70
|
+
body: JSON.stringify({ email: 'jane@example.com' })
|
|
71
|
+
});
|
|
307
72
|
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
↓
|
|
311
|
-
Set Timeout Timer (e.g., 5000ms)
|
|
312
|
-
↓
|
|
313
|
-
Start Fetch Request
|
|
314
|
-
↓
|
|
315
|
-
├─→ Response arrives in 2000ms → Clear Timer → Return Response ✓
|
|
316
|
-
│
|
|
317
|
-
└─→ 5000ms passes → Timer fires → Abort Signal → Fetch throws AbortError ✗
|
|
73
|
+
// DELETE request
|
|
74
|
+
await api.delete('/users/1');
|
|
318
75
|
```
|
|
319
76
|
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
```javascript
|
|
323
|
-
// Without clearing:
|
|
324
|
-
setTimeout(() => abortController.abort(), 5000);
|
|
325
|
-
// If request finishes in 2s, timer still exists in memory until 5s
|
|
326
|
-
// With 1000 requests, you have 1000 timers lingering!
|
|
327
|
-
|
|
328
|
-
// With clearing:
|
|
329
|
-
finally {
|
|
330
|
-
clearTimeout(timeOutId); // Immediately free memory when done
|
|
331
|
-
}
|
|
332
|
-
```
|
|
77
|
+
### Request Configuration
|
|
333
78
|
|
|
334
|
-
|
|
79
|
+
Override default settings per request:
|
|
335
80
|
|
|
336
81
|
```javascript
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
throw error;
|
|
82
|
+
const response = await api.get('/users', {
|
|
83
|
+
timeout: 10000,
|
|
84
|
+
headers: {
|
|
85
|
+
'Authorization': 'Bearer token123'
|
|
342
86
|
}
|
|
343
|
-
}
|
|
87
|
+
});
|
|
344
88
|
```
|
|
345
89
|
|
|
346
|
-
The code checks `error.name === 'AbortError'` to distinguish timeout aborts from other errors.
|
|
347
|
-
|
|
348
90
|
---
|
|
349
91
|
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
**What it does:**
|
|
353
|
-
Intercepts and modifies requests before they are sent to the server.
|
|
92
|
+
## Interceptors
|
|
354
93
|
|
|
355
|
-
|
|
356
|
-
- Add authentication tokens to all requests
|
|
357
|
-
- Log outgoing requests for debugging
|
|
358
|
-
- Modify request data (transform, encrypt, compress)
|
|
359
|
-
- Add timestamps or request IDs
|
|
360
|
-
- Implement retry logic
|
|
94
|
+
### Request Interceptors
|
|
361
95
|
|
|
362
|
-
|
|
96
|
+
Modify requests before they are sent:
|
|
363
97
|
|
|
364
98
|
```javascript
|
|
365
|
-
// Adding an interceptor
|
|
366
99
|
api.addRequestInterceptor(
|
|
367
100
|
(config) => {
|
|
368
|
-
//
|
|
369
|
-
|
|
370
|
-
config.config.headers['
|
|
371
|
-
return config;
|
|
372
|
-
},
|
|
373
|
-
(error) => {
|
|
374
|
-
// Error handler: runs if previous interceptor failed
|
|
375
|
-
console.error('Request interceptor error:', error);
|
|
376
|
-
return Promise.reject(error);
|
|
377
|
-
}
|
|
378
|
-
);
|
|
379
|
-
```
|
|
380
|
-
|
|
381
|
-
**Storage:**
|
|
382
|
-
|
|
383
|
-
```javascript
|
|
384
|
-
requestInterceptors = []; // Array of { successHandler, errorHandler }
|
|
385
|
-
|
|
386
|
-
addRequestInterceptor(successHandler, errorHandler) {
|
|
387
|
-
this.requestInterceptors.push({ successHandler, errorHandler });
|
|
388
|
-
}
|
|
389
|
-
```
|
|
390
|
-
|
|
391
|
-
**Execution in Chain:**
|
|
392
|
-
|
|
393
|
-
```javascript
|
|
394
|
-
const chain = [
|
|
395
|
-
...this.requestInterceptors, // All request interceptors
|
|
396
|
-
{ successHandler: this.#dispatchRequest.bind(this) }, // Actual fetch
|
|
397
|
-
...this.responseInterceptors // All response interceptors
|
|
398
|
-
];
|
|
399
|
-
```
|
|
400
|
-
|
|
401
|
-
Request interceptors run **before** the actual fetch call.
|
|
402
|
-
|
|
403
|
-
**Input/Output Format:**
|
|
404
|
-
|
|
405
|
-
- **Input**: `{ endPoint: '/users', config: { method: 'GET', ... } }`
|
|
406
|
-
- **Output**: Must return same format (modified or not)
|
|
407
|
-
|
|
408
|
-
**Common Use Cases:**
|
|
409
|
-
|
|
410
|
-
```javascript
|
|
411
|
-
// 1. Add authentication
|
|
412
|
-
api.addRequestInterceptor((config) => {
|
|
413
|
-
const token = localStorage.getItem('token');
|
|
414
|
-
config.config.headers['Authorization'] = `Bearer ${token}`;
|
|
415
|
-
return config;
|
|
416
|
-
});
|
|
417
|
-
|
|
418
|
-
// 2. Log requests
|
|
419
|
-
api.addRequestInterceptor((config) => {
|
|
420
|
-
console.log(`[${config.config.method}] ${config.endPoint}`);
|
|
421
|
-
return config;
|
|
422
|
-
});
|
|
423
|
-
|
|
424
|
-
// 3. Transform request body
|
|
425
|
-
api.addRequestInterceptor((config) => {
|
|
426
|
-
if (config.config.body) {
|
|
427
|
-
// Add timestamp to all POST requests
|
|
428
|
-
const body = JSON.parse(config.config.body);
|
|
429
|
-
body.timestamp = Date.now();
|
|
430
|
-
config.config.body = JSON.stringify(body);
|
|
431
|
-
}
|
|
432
|
-
return config;
|
|
433
|
-
});
|
|
434
|
-
```
|
|
435
|
-
|
|
436
|
-
---
|
|
437
|
-
|
|
438
|
-
### 6. Response Interceptors
|
|
439
|
-
|
|
440
|
-
**What it does:**
|
|
441
|
-
Intercepts and modifies responses before they reach your application code.
|
|
442
|
-
|
|
443
|
-
**Why it's needed:**
|
|
444
|
-
- Transform response data globally
|
|
445
|
-
- Handle errors consistently (e.g., redirect on 401)
|
|
446
|
-
- Log responses for debugging
|
|
447
|
-
- Extract data from nested response structures
|
|
448
|
-
- Implement global retry logic
|
|
449
|
-
|
|
450
|
-
**How it works:**
|
|
451
|
-
|
|
452
|
-
```javascript
|
|
453
|
-
// Adding an interceptor
|
|
454
|
-
api.addResponseInterceptor(
|
|
455
|
-
(response) => {
|
|
456
|
-
// Success handler: runs for successful responses
|
|
457
|
-
console.log('Response status:', response.status);
|
|
458
|
-
|
|
459
|
-
if (!response.ok) {
|
|
460
|
-
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
461
|
-
}
|
|
462
|
-
|
|
463
|
-
return response; // Must return response
|
|
101
|
+
// Add authentication token
|
|
102
|
+
const token = localStorage.getItem('authToken');
|
|
103
|
+
config.config.headers['Authorization'] = `Bearer ${token}`;
|
|
104
|
+
return config;
|
|
464
105
|
},
|
|
465
106
|
(error) => {
|
|
466
|
-
// Error handler: runs on fetch errors or if previous interceptor threw
|
|
467
|
-
console.error('Response error:', error);
|
|
468
|
-
|
|
469
|
-
if (error.message.includes('timeout')) {
|
|
470
|
-
alert('Request timed out. Please try again.');
|
|
471
|
-
}
|
|
472
|
-
|
|
473
107
|
return Promise.reject(error);
|
|
474
108
|
}
|
|
475
109
|
);
|
|
476
110
|
```
|
|
477
111
|
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
```javascript
|
|
481
|
-
responseInterceptors = []; // Array of { successHandler, errorHandler }
|
|
482
|
-
|
|
483
|
-
addResponseInterceptor(successHandler, errorHandler) {
|
|
484
|
-
this.responseInterceptors.push({ successHandler, errorHandler });
|
|
485
|
-
}
|
|
486
|
-
```
|
|
487
|
-
|
|
488
|
-
**Execution in Chain:**
|
|
112
|
+
### Response Interceptors
|
|
489
113
|
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
**Input/Output Format:**
|
|
493
|
-
|
|
494
|
-
- **Input**: Native fetch `Response` object
|
|
495
|
-
- **Output**: Must return `Response` object (or modified version)
|
|
496
|
-
|
|
497
|
-
**Common Use Cases:**
|
|
114
|
+
Handle responses globally:
|
|
498
115
|
|
|
499
116
|
```javascript
|
|
500
|
-
// 1. Auto-logout on 401
|
|
501
117
|
api.addResponseInterceptor(
|
|
502
118
|
(response) => {
|
|
119
|
+
// Handle unauthorized responses
|
|
503
120
|
if (response.status === 401) {
|
|
504
|
-
localStorage.removeItem('token');
|
|
505
121
|
window.location.href = '/login';
|
|
506
122
|
}
|
|
507
123
|
return response;
|
|
508
|
-
}
|
|
509
|
-
)
|
|
510
|
-
|
|
511
|
-
// 2. Parse all responses as JSON
|
|
512
|
-
api.addResponseInterceptor(
|
|
513
|
-
async (response) => {
|
|
514
|
-
const data = await response.json();
|
|
515
|
-
// Return modified response with parsed data
|
|
516
|
-
response.data = data;
|
|
517
|
-
return response;
|
|
518
|
-
}
|
|
519
|
-
);
|
|
520
|
-
|
|
521
|
-
// 3. Handle rate limiting
|
|
522
|
-
api.addResponseInterceptor(
|
|
523
|
-
(response) => {
|
|
524
|
-
if (response.status === 429) {
|
|
525
|
-
const retryAfter = response.headers.get('Retry-After');
|
|
526
|
-
throw new Error(`Rate limited. Retry after ${retryAfter} seconds`);
|
|
527
|
-
}
|
|
528
|
-
return response;
|
|
529
|
-
}
|
|
530
|
-
);
|
|
531
|
-
|
|
532
|
-
// 4. Log all responses
|
|
533
|
-
api.addResponseInterceptor(
|
|
534
|
-
(response) => {
|
|
535
|
-
console.log(`[${response.status}] Response received`);
|
|
536
|
-
return response;
|
|
537
|
-
}
|
|
538
|
-
);
|
|
539
|
-
```
|
|
540
|
-
|
|
541
|
-
---
|
|
542
|
-
|
|
543
|
-
### 7. Interceptor Chain Execution
|
|
544
|
-
|
|
545
|
-
**What it does:**
|
|
546
|
-
Executes all interceptors and the fetch request in a sequential promise chain.
|
|
547
|
-
|
|
548
|
-
**Why it's needed:**
|
|
549
|
-
- Ensure interceptors run in order (request → fetch → response)
|
|
550
|
-
- Handle errors at any stage
|
|
551
|
-
- Allow each interceptor to modify data for the next
|
|
552
|
-
- Maintain clean async flow
|
|
553
|
-
|
|
554
|
-
**The Chain Structure:**
|
|
555
|
-
|
|
556
|
-
```javascript
|
|
557
|
-
const chain = [
|
|
558
|
-
...this.requestInterceptors, // [interceptor1, interceptor2, ...]
|
|
559
|
-
{ successHandler: this.#dispatchRequest.bind(this) }, // The actual fetch
|
|
560
|
-
...this.responseInterceptors // [interceptor3, interceptor4, ...]
|
|
561
|
-
];
|
|
562
|
-
```
|
|
563
|
-
|
|
564
|
-
**Visual Representation:**
|
|
565
|
-
|
|
566
|
-
```
|
|
567
|
-
Initial Promise.resolve({ endPoint, config })
|
|
568
|
-
↓
|
|
569
|
-
Request Interceptor 1 (success/error)
|
|
570
|
-
↓
|
|
571
|
-
Request Interceptor 2 (success/error)
|
|
572
|
-
↓
|
|
573
|
-
#dispatchRequest (actual fetch)
|
|
574
|
-
↓
|
|
575
|
-
Response Interceptor 1 (success/error)
|
|
576
|
-
↓
|
|
577
|
-
Response Interceptor 2 (success/error)
|
|
578
|
-
↓
|
|
579
|
-
Final Result returned to user
|
|
580
|
-
```
|
|
581
|
-
|
|
582
|
-
**Chain Building Code:**
|
|
583
|
-
|
|
584
|
-
```javascript
|
|
585
|
-
let promise = Promise.resolve({ endPoint, config: finalConfig });
|
|
586
|
-
|
|
587
|
-
for (const { successHandler, errorHandler } of chain) {
|
|
588
|
-
promise = promise.then(
|
|
589
|
-
(responseOfPrevPromise) => {
|
|
590
|
-
try {
|
|
591
|
-
return successHandler(responseOfPrevPromise);
|
|
592
|
-
} catch (error) {
|
|
593
|
-
if (errorHandler) {
|
|
594
|
-
return errorHandler(error);
|
|
595
|
-
} else {
|
|
596
|
-
return Promise.reject(error);
|
|
597
|
-
}
|
|
598
|
-
}
|
|
599
|
-
},
|
|
600
|
-
(error) => {
|
|
601
|
-
if (errorHandler) {
|
|
602
|
-
return errorHandler(error);
|
|
603
|
-
} else {
|
|
604
|
-
return Promise.reject(error);
|
|
605
|
-
}
|
|
606
|
-
}
|
|
607
|
-
);
|
|
608
|
-
}
|
|
609
|
-
|
|
610
|
-
return promise;
|
|
611
|
-
```
|
|
612
|
-
|
|
613
|
-
**How It Works Step-by-Step:**
|
|
614
|
-
|
|
615
|
-
1. **Initial Promise**: Starts with `{ endPoint, config }`
|
|
616
|
-
|
|
617
|
-
2. **Iteration**: For each interceptor in the chain:
|
|
618
|
-
- Attach `.then()` to the promise
|
|
619
|
-
- Pass output of previous step as input to next
|
|
620
|
-
|
|
621
|
-
3. **Success Path**: `then(successHandler, errorHandler)`
|
|
622
|
-
- If previous promise resolved → `successHandler` runs
|
|
623
|
-
- Output becomes input for next interceptor
|
|
624
|
-
|
|
625
|
-
4. **Error Path**: Second parameter of `.then()`
|
|
626
|
-
- If previous promise rejected → `errorHandler` runs
|
|
627
|
-
- Can recover (return value) or propagate (reject)
|
|
628
|
-
|
|
629
|
-
5. **Try-Catch in Success Handler**:
|
|
630
|
-
- If `successHandler` throws synchronous error
|
|
631
|
-
- Catch it and pass to `errorHandler`
|
|
632
|
-
- This handles errors that don't return rejected promises
|
|
633
|
-
|
|
634
|
-
**Example Flow:**
|
|
635
|
-
|
|
636
|
-
```javascript
|
|
637
|
-
// User code
|
|
638
|
-
const response = await api.get('/users');
|
|
639
|
-
|
|
640
|
-
// What happens:
|
|
641
|
-
|
|
642
|
-
// Step 1: Initial promise
|
|
643
|
-
Promise.resolve({ endPoint: '/users', config: { method: 'GET' } })
|
|
644
|
-
|
|
645
|
-
// Step 2: Request Interceptor 1
|
|
646
|
-
.then(config => {
|
|
647
|
-
config.config.headers['Auth'] = 'token';
|
|
648
|
-
return config; // { endPoint: '/users', config: { method: 'GET', headers: {...} } }
|
|
649
|
-
})
|
|
650
|
-
|
|
651
|
-
// Step 3: Request Interceptor 2
|
|
652
|
-
.then(config => {
|
|
653
|
-
console.log('Logging request');
|
|
654
|
-
return config; // Same object passed through
|
|
655
|
-
})
|
|
656
|
-
|
|
657
|
-
// Step 4: Dispatch Request (actual fetch)
|
|
658
|
-
.then(async config => {
|
|
659
|
-
const response = await fetch(config.config.baseURL + config.endPoint, config.config);
|
|
660
|
-
return response; // Native Response object
|
|
661
|
-
})
|
|
662
|
-
|
|
663
|
-
// Step 5: Response Interceptor 1
|
|
664
|
-
.then(response => {
|
|
665
|
-
if (!response.ok) throw new Error('Bad response');
|
|
666
|
-
return response; // Response object passed through
|
|
667
|
-
})
|
|
668
|
-
|
|
669
|
-
// Step 6: Response Interceptor 2
|
|
670
|
-
.then(response => {
|
|
671
|
-
console.log('Response received:', response.status);
|
|
672
|
-
return response; // Final Response object
|
|
673
|
-
})
|
|
674
|
-
|
|
675
|
-
// Result: User gets the Response object
|
|
676
|
-
```
|
|
677
|
-
|
|
678
|
-
**Error Handling Example:**
|
|
679
|
-
|
|
680
|
-
```javascript
|
|
681
|
-
// If Request Interceptor throws:
|
|
682
|
-
.then(config => {
|
|
683
|
-
throw new Error('Invalid token'); // Sync error
|
|
684
|
-
})
|
|
685
|
-
|
|
686
|
-
// Caught by try-catch, passed to errorHandler:
|
|
687
|
-
catch (error) {
|
|
688
|
-
if (errorHandler) {
|
|
689
|
-
return errorHandler(error); // Can recover
|
|
690
|
-
} else {
|
|
691
|
-
return Promise.reject(error); // Propagate
|
|
692
|
-
}
|
|
693
|
-
}
|
|
694
|
-
|
|
695
|
-
// If fetch fails (network error, timeout):
|
|
696
|
-
.then(async config => {
|
|
697
|
-
const response = await fetch(...); // Throws on timeout
|
|
698
|
-
return response;
|
|
699
|
-
})
|
|
700
|
-
|
|
701
|
-
// Second parameter of .then() catches it:
|
|
702
|
-
.then(successHandler, (error) => {
|
|
703
|
-
if (errorHandler) {
|
|
704
|
-
return errorHandler(error);
|
|
705
|
-
} else {
|
|
124
|
+
},
|
|
125
|
+
(error) => {
|
|
126
|
+
console.error('Request failed:', error);
|
|
706
127
|
return Promise.reject(error);
|
|
707
128
|
}
|
|
708
|
-
|
|
709
|
-
```
|
|
710
|
-
|
|
711
|
-
**Why Use This Pattern?**
|
|
712
|
-
|
|
713
|
-
1. **Sequential Execution**: Each interceptor waits for previous one
|
|
714
|
-
2. **Error Recovery**: Interceptors can catch and fix errors
|
|
715
|
-
3. **Immutable Chain**: Original promise isn't modified
|
|
716
|
-
4. **Type Safety**: Each step expects specific input/output format
|
|
717
|
-
5. **Debugging**: Easy to add console.logs at each step
|
|
718
|
-
|
|
719
|
-
**Key Points:**
|
|
720
|
-
|
|
721
|
-
- Interceptors must return a value (config or response)
|
|
722
|
-
- Errors can be caught and handled at any point
|
|
723
|
-
- The chain is built once per request
|
|
724
|
-
- All interceptors share the same promise chain
|
|
725
|
-
- `bind(this)` ensures `#dispatchRequest` maintains class context
|
|
726
|
-
|
|
727
|
-
---
|
|
728
|
-
|
|
729
|
-
## Architecture & Design Decisions
|
|
730
|
-
|
|
731
|
-
### Why Classes?
|
|
732
|
-
|
|
733
|
-
- Encapsulation of state (config, interceptors)
|
|
734
|
-
- Private methods (#) for internal logic
|
|
735
|
-
- Instance creation for multiple API clients
|
|
736
|
-
- Clear separation of concerns
|
|
737
|
-
|
|
738
|
-
### Why Private Methods (#)?
|
|
739
|
-
|
|
740
|
-
```javascript
|
|
741
|
-
#request()
|
|
742
|
-
#dispatchRequest()
|
|
743
|
-
#mergeConfig()
|
|
744
|
-
#mergeConfigs()
|
|
745
|
-
```
|
|
746
|
-
|
|
747
|
-
- Hide implementation details
|
|
748
|
-
- Prevent external modification
|
|
749
|
-
- Clear API surface (only public methods exposed)
|
|
750
|
-
- Follows encapsulation principles
|
|
751
|
-
|
|
752
|
-
### Why Async/Await in #dispatchRequest?
|
|
753
|
-
|
|
754
|
-
```javascript
|
|
755
|
-
// Way 1: Promise chain
|
|
756
|
-
return fetch(url, config).finally(() => clearTimeout(timeOutId));
|
|
757
|
-
|
|
758
|
-
// Way 2: Async/Await (chosen)
|
|
759
|
-
try {
|
|
760
|
-
const response = await fetch(url, config);
|
|
761
|
-
return response;
|
|
762
|
-
} finally {
|
|
763
|
-
clearTimeout(timeOutId);
|
|
764
|
-
}
|
|
765
|
-
```
|
|
766
|
-
|
|
767
|
-
**Chosen Way 2 because:**
|
|
768
|
-
- More readable for error handling
|
|
769
|
-
- Finally block guaranteed to run
|
|
770
|
-
- Easier to debug with stack traces
|
|
771
|
-
- Synchronous-looking async code
|
|
772
|
-
|
|
773
|
-
### Why Bind Context?
|
|
774
|
-
|
|
775
|
-
```javascript
|
|
776
|
-
successHandler: this.#dispatchRequest.bind(this)
|
|
777
|
-
```
|
|
778
|
-
|
|
779
|
-
Without `bind(this)`:
|
|
780
|
-
```javascript
|
|
781
|
-
// Inside interceptor chain loop
|
|
782
|
-
successHandler(config); // 'this' is undefined in #dispatchRequest!
|
|
783
|
-
```
|
|
784
|
-
|
|
785
|
-
With `bind(this)`:
|
|
786
|
-
```javascript
|
|
787
|
-
// 'this' correctly refers to Fetchify instance
|
|
788
|
-
this.config, this.requestInterceptors, etc. are accessible
|
|
129
|
+
);
|
|
789
130
|
```
|
|
790
131
|
|
|
791
|
-
### Why Two Merge Methods?
|
|
792
|
-
|
|
793
|
-
1. **#mergeConfig(newConfig)**: Merges with `this.config`
|
|
794
|
-
- Used in constructor and #request
|
|
795
|
-
- Instance-specific merging
|
|
796
|
-
|
|
797
|
-
2. **#mergeConfigs(config1, config2)**: Merges two arbitrary configs
|
|
798
|
-
- Used in #dispatchRequest
|
|
799
|
-
- Doesn't depend on instance state
|
|
800
|
-
|
|
801
|
-
Separation provides flexibility and reusability.
|
|
802
|
-
|
|
803
132
|
---
|
|
804
133
|
|
|
805
|
-
##
|
|
134
|
+
## Advanced Examples
|
|
806
135
|
|
|
807
|
-
### Complete
|
|
136
|
+
### Complete Setup with Authentication
|
|
808
137
|
|
|
809
138
|
```javascript
|
|
810
|
-
import fetchify from
|
|
139
|
+
import fetchify from '@rutansh0101/fetchify';
|
|
811
140
|
|
|
812
|
-
// 1. Create instance with base config
|
|
813
141
|
const api = fetchify.create({
|
|
814
142
|
baseURL: 'https://api.example.com',
|
|
815
143
|
timeout: 5000,
|
|
816
144
|
headers: {
|
|
817
|
-
'Content-Type': 'application/json'
|
|
818
|
-
'X-API-Version': 'v1'
|
|
145
|
+
'Content-Type': 'application/json'
|
|
819
146
|
}
|
|
820
147
|
});
|
|
821
148
|
|
|
822
|
-
//
|
|
823
|
-
api.addRequestInterceptor(
|
|
824
|
-
(
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
config.config.headers['Authorization'] = `Bearer ${token}`;
|
|
828
|
-
}
|
|
829
|
-
console.log('→ Request:', config.config.method, config.endPoint);
|
|
830
|
-
return config;
|
|
831
|
-
},
|
|
832
|
-
(error) => {
|
|
833
|
-
console.error('Request interceptor failed:', error);
|
|
834
|
-
return Promise.reject(error);
|
|
149
|
+
// Add auth token to all requests
|
|
150
|
+
api.addRequestInterceptor((config) => {
|
|
151
|
+
const token = localStorage.getItem('token');
|
|
152
|
+
if (token) {
|
|
153
|
+
config.config.headers['Authorization'] = `Bearer ${token}`;
|
|
835
154
|
}
|
|
836
|
-
|
|
155
|
+
return config;
|
|
156
|
+
});
|
|
837
157
|
|
|
838
|
-
//
|
|
158
|
+
// Handle errors globally
|
|
839
159
|
api.addResponseInterceptor(
|
|
840
160
|
(response) => {
|
|
841
|
-
console.log('← Response:', response.status, response.statusText);
|
|
842
|
-
|
|
843
161
|
if (!response.ok) {
|
|
844
|
-
if (response.status === 401) {
|
|
845
|
-
localStorage.removeItem('authToken');
|
|
846
|
-
window.location.href = '/login';
|
|
847
|
-
}
|
|
848
162
|
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
849
163
|
}
|
|
850
|
-
|
|
851
164
|
return response;
|
|
852
165
|
},
|
|
853
166
|
(error) => {
|
|
854
167
|
if (error.message.includes('timeout')) {
|
|
855
|
-
alert('Request timed out. Please
|
|
168
|
+
alert('Request timed out. Please try again.');
|
|
856
169
|
}
|
|
857
170
|
return Promise.reject(error);
|
|
858
171
|
}
|
|
859
172
|
);
|
|
860
173
|
|
|
861
|
-
//
|
|
862
|
-
async function
|
|
174
|
+
// Use the API
|
|
175
|
+
async function fetchUsers() {
|
|
863
176
|
try {
|
|
864
|
-
|
|
865
|
-
const
|
|
866
|
-
|
|
867
|
-
console.log('Users:', users);
|
|
868
|
-
|
|
869
|
-
// POST request with custom timeout
|
|
870
|
-
const newUser = await api.post('/users', {
|
|
871
|
-
body: JSON.stringify({
|
|
872
|
-
name: 'John Doe',
|
|
873
|
-
email: 'john@example.com'
|
|
874
|
-
}),
|
|
875
|
-
timeout: 10000 // Override default timeout
|
|
876
|
-
});
|
|
877
|
-
const created = await newUser.json();
|
|
878
|
-
console.log('Created user:', created);
|
|
879
|
-
|
|
880
|
-
// PUT request
|
|
881
|
-
const updateResponse = await api.put('/users/1', {
|
|
882
|
-
body: JSON.stringify({ name: 'Jane Doe' })
|
|
883
|
-
});
|
|
884
|
-
|
|
885
|
-
// PATCH request
|
|
886
|
-
const patchResponse = await api.patch('/users/1', {
|
|
887
|
-
body: JSON.stringify({ email: 'jane@example.com' })
|
|
888
|
-
});
|
|
889
|
-
|
|
890
|
-
// DELETE request
|
|
891
|
-
await api.delete('/users/1');
|
|
892
|
-
|
|
177
|
+
const response = await api.get('/users');
|
|
178
|
+
const users = await response.json();
|
|
179
|
+
console.log(users);
|
|
893
180
|
} catch (error) {
|
|
894
|
-
console.error('
|
|
181
|
+
console.error('Failed to fetch users:', error);
|
|
895
182
|
}
|
|
896
183
|
}
|
|
897
|
-
|
|
898
|
-
example();
|
|
899
184
|
```
|
|
900
185
|
|
|
901
186
|
### Multiple API Instances
|
|
902
187
|
|
|
903
188
|
```javascript
|
|
904
|
-
// Different APIs with different configs
|
|
905
189
|
const mainAPI = fetchify.create({
|
|
906
190
|
baseURL: 'https://api.example.com',
|
|
907
191
|
timeout: 5000
|
|
@@ -912,349 +196,93 @@ const authAPI = fetchify.create({
|
|
|
912
196
|
timeout: 10000
|
|
913
197
|
});
|
|
914
198
|
|
|
915
|
-
|
|
916
|
-
baseURL: 'https://analytics.example.com',
|
|
917
|
-
timeout: 3000,
|
|
918
|
-
headers: {
|
|
919
|
-
'X-Analytics-Key': 'secret123'
|
|
920
|
-
}
|
|
921
|
-
});
|
|
922
|
-
|
|
923
|
-
// Use them independently
|
|
199
|
+
// Use independently
|
|
924
200
|
await mainAPI.get('/users');
|
|
925
201
|
await authAPI.post('/login', { body: credentials });
|
|
926
|
-
await analyticsAPI.post('/track', { body: event });
|
|
927
202
|
```
|
|
928
203
|
|
|
929
204
|
---
|
|
930
205
|
|
|
931
206
|
## Error Handling
|
|
932
207
|
|
|
933
|
-
### Types of Errors
|
|
934
|
-
|
|
935
|
-
1. **Network Errors**: No internet, DNS failure, etc.
|
|
936
|
-
2. **Timeout Errors**: Request exceeded timeout duration
|
|
937
|
-
3. **HTTP Errors**: 4xx, 5xx status codes
|
|
938
|
-
4. **Interceptor Errors**: Thrown by custom interceptor logic
|
|
939
|
-
|
|
940
|
-
### Error Handling Pattern
|
|
941
|
-
|
|
942
208
|
```javascript
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
if (!response.ok) {
|
|
949
|
-
throw new Error(`HTTP ${response.status}`);
|
|
950
|
-
}
|
|
951
|
-
|
|
952
|
-
const data = await response.json();
|
|
953
|
-
return data;
|
|
954
|
-
|
|
955
|
-
} catch (error) {
|
|
956
|
-
// Timeout error
|
|
957
|
-
if (error.message.includes('timeout')) {
|
|
958
|
-
console.error('Request timed out');
|
|
959
|
-
return null;
|
|
960
|
-
}
|
|
961
|
-
|
|
962
|
-
// Network error
|
|
963
|
-
if (error.message.includes('fetch')) {
|
|
964
|
-
console.error('Network error');
|
|
965
|
-
return null;
|
|
966
|
-
}
|
|
967
|
-
|
|
968
|
-
// Other errors
|
|
969
|
-
console.error('Unknown error:', error);
|
|
970
|
-
throw error;
|
|
209
|
+
try {
|
|
210
|
+
const response = await api.get('/users', { timeout: 3000 });
|
|
211
|
+
|
|
212
|
+
if (!response.ok) {
|
|
213
|
+
throw new Error(`HTTP ${response.status}`);
|
|
971
214
|
}
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
switch(response.status) {
|
|
982
|
-
case 401:
|
|
983
|
-
console.error('Unauthorized - redirecting to login');
|
|
984
|
-
window.location.href = '/login';
|
|
985
|
-
break;
|
|
986
|
-
case 403:
|
|
987
|
-
console.error('Forbidden - insufficient permissions');
|
|
988
|
-
break;
|
|
989
|
-
case 404:
|
|
990
|
-
console.error('Resource not found');
|
|
991
|
-
break;
|
|
992
|
-
case 500:
|
|
993
|
-
console.error('Server error');
|
|
994
|
-
break;
|
|
995
|
-
}
|
|
996
|
-
return response;
|
|
997
|
-
},
|
|
998
|
-
(error) => {
|
|
999
|
-
// Log all errors globally
|
|
1000
|
-
console.error('[Global Error Handler]', error);
|
|
1001
|
-
|
|
1002
|
-
// Send error to monitoring service
|
|
1003
|
-
// sendToSentry(error);
|
|
1004
|
-
|
|
1005
|
-
return Promise.reject(error);
|
|
215
|
+
|
|
216
|
+
const data = await response.json();
|
|
217
|
+
return data;
|
|
218
|
+
|
|
219
|
+
} catch (error) {
|
|
220
|
+
if (error.message.includes('timeout')) {
|
|
221
|
+
console.error('Request timed out');
|
|
222
|
+
} else {
|
|
223
|
+
console.error('Request failed:', error);
|
|
1006
224
|
}
|
|
1007
|
-
|
|
225
|
+
}
|
|
1008
226
|
```
|
|
1009
227
|
|
|
1010
228
|
---
|
|
1011
229
|
|
|
1012
230
|
## API Reference
|
|
1013
231
|
|
|
1014
|
-
### fetchify.create(config)
|
|
232
|
+
### `fetchify.create(config)`
|
|
1015
233
|
|
|
1016
234
|
Creates a new Fetchify instance.
|
|
1017
235
|
|
|
1018
|
-
**
|
|
1019
|
-
- `
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
- `headers` (Object): Default headers for all requests
|
|
1023
|
-
|
|
1024
|
-
**Returns:** Fetchify instance
|
|
1025
|
-
|
|
1026
|
-
**Example:**
|
|
1027
|
-
```javascript
|
|
1028
|
-
const api = fetchify.create({
|
|
1029
|
-
baseURL: 'https://api.example.com',
|
|
1030
|
-
timeout: 5000,
|
|
1031
|
-
headers: { 'Authorization': 'Bearer token' }
|
|
1032
|
-
});
|
|
1033
|
-
```
|
|
1034
|
-
|
|
1035
|
-
---
|
|
236
|
+
**Config Options:**
|
|
237
|
+
- `baseURL` (String) - Base URL for all requests
|
|
238
|
+
- `timeout` (Number) - Default timeout in milliseconds (default: 1000)
|
|
239
|
+
- `headers` (Object) - Default headers
|
|
1036
240
|
|
|
1037
|
-
###
|
|
241
|
+
### HTTP Methods
|
|
1038
242
|
|
|
1039
|
-
|
|
243
|
+
All methods return a Promise that resolves to the fetch Response object.
|
|
1040
244
|
|
|
1041
|
-
|
|
1042
|
-
- `endpoint
|
|
1043
|
-
- `
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
- Any other fetch options
|
|
245
|
+
- `instance.get(endpoint, config)` - GET request
|
|
246
|
+
- `instance.post(endpoint, config)` - POST request
|
|
247
|
+
- `instance.put(endpoint, config)` - PUT request
|
|
248
|
+
- `instance.patch(endpoint, config)` - PATCH request
|
|
249
|
+
- `instance.delete(endpoint, config)` - DELETE request
|
|
1047
250
|
|
|
1048
|
-
|
|
251
|
+
### Interceptors
|
|
1049
252
|
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
const response = await api.get('/users', { timeout: 3000 });
|
|
1053
|
-
const users = await response.json();
|
|
1054
|
-
```
|
|
253
|
+
- `instance.addRequestInterceptor(successHandler, errorHandler)` - Intercept requests
|
|
254
|
+
- `instance.addResponseInterceptor(successHandler, errorHandler)` - Intercept responses
|
|
1055
255
|
|
|
1056
256
|
---
|
|
1057
257
|
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
Performs a POST request.
|
|
1061
|
-
|
|
1062
|
-
**Parameters:**
|
|
1063
|
-
- `endpoint` (String): API endpoint
|
|
1064
|
-
- `config` (Object, optional): Request-specific config
|
|
1065
|
-
- `body` (String): Request body (usually JSON.stringify)
|
|
1066
|
-
- `timeout` (Number): Override default timeout
|
|
1067
|
-
- `headers` (Object): Additional headers
|
|
1068
|
-
|
|
1069
|
-
**Returns:** Promise<Response>
|
|
258
|
+
## Quick Reference
|
|
1070
259
|
|
|
1071
|
-
**Example:**
|
|
1072
260
|
```javascript
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
timeout: 10000
|
|
1076
|
-
});
|
|
1077
|
-
```
|
|
1078
|
-
|
|
1079
|
-
---
|
|
1080
|
-
|
|
1081
|
-
### instance.put(endpoint, config)
|
|
1082
|
-
|
|
1083
|
-
Performs a PUT request (full update).
|
|
1084
|
-
|
|
1085
|
-
**Parameters:** Same as POST
|
|
1086
|
-
|
|
1087
|
-
**Example:**
|
|
1088
|
-
```javascript
|
|
1089
|
-
await api.put('/users/1', {
|
|
1090
|
-
body: JSON.stringify({ name: 'John', email: 'john@example.com' })
|
|
1091
|
-
});
|
|
1092
|
-
```
|
|
1093
|
-
|
|
1094
|
-
---
|
|
1095
|
-
|
|
1096
|
-
### instance.patch(endpoint, config)
|
|
1097
|
-
|
|
1098
|
-
Performs a PATCH request (partial update).
|
|
1099
|
-
|
|
1100
|
-
**Parameters:** Same as POST
|
|
1101
|
-
|
|
1102
|
-
**Example:**
|
|
1103
|
-
```javascript
|
|
1104
|
-
await api.patch('/users/1', {
|
|
1105
|
-
body: JSON.stringify({ email: 'newemail@example.com' })
|
|
1106
|
-
});
|
|
1107
|
-
```
|
|
1108
|
-
|
|
1109
|
-
---
|
|
1110
|
-
|
|
1111
|
-
### instance.delete(endpoint, config)
|
|
1112
|
-
|
|
1113
|
-
Performs a DELETE request.
|
|
1114
|
-
|
|
1115
|
-
**Parameters:**
|
|
1116
|
-
- `endpoint` (String): API endpoint
|
|
1117
|
-
- `config` (Object, optional): Request-specific config
|
|
1118
|
-
|
|
1119
|
-
**Returns:** Promise<Response>
|
|
1120
|
-
|
|
1121
|
-
**Example:**
|
|
1122
|
-
```javascript
|
|
1123
|
-
await api.delete('/users/1');
|
|
1124
|
-
```
|
|
1125
|
-
|
|
1126
|
-
---
|
|
1127
|
-
|
|
1128
|
-
### instance.addRequestInterceptor(successHandler, errorHandler)
|
|
1129
|
-
|
|
1130
|
-
Adds a request interceptor.
|
|
1131
|
-
|
|
1132
|
-
**Parameters:**
|
|
1133
|
-
- `successHandler` (Function): Called before request
|
|
1134
|
-
- Receives: `{ endPoint, config }`
|
|
1135
|
-
- Must return: Modified `{ endPoint, config }`
|
|
1136
|
-
- `errorHandler` (Function, optional): Called if previous interceptor failed
|
|
1137
|
-
- Receives: Error object
|
|
1138
|
-
- Must return: Rejected promise or recovery value
|
|
1139
|
-
|
|
1140
|
-
**Example:**
|
|
1141
|
-
```javascript
|
|
1142
|
-
api.addRequestInterceptor(
|
|
1143
|
-
(config) => {
|
|
1144
|
-
config.config.headers['X-Timestamp'] = Date.now();
|
|
1145
|
-
return config;
|
|
1146
|
-
},
|
|
1147
|
-
(error) => {
|
|
1148
|
-
console.error('Request error:', error);
|
|
1149
|
-
return Promise.reject(error);
|
|
1150
|
-
}
|
|
1151
|
-
);
|
|
1152
|
-
```
|
|
1153
|
-
|
|
1154
|
-
---
|
|
1155
|
-
|
|
1156
|
-
### instance.addResponseInterceptor(successHandler, errorHandler)
|
|
1157
|
-
|
|
1158
|
-
Adds a response interceptor.
|
|
261
|
+
// Create
|
|
262
|
+
const api = fetchify.create({ baseURL: '...', timeout: 5000 });
|
|
1159
263
|
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
- Must return: Rejected promise or recovery value
|
|
264
|
+
// Requests
|
|
265
|
+
await api.get('/path');
|
|
266
|
+
await api.post('/path', { body: JSON.stringify(data) });
|
|
267
|
+
await api.put('/path', { body: JSON.stringify(data) });
|
|
268
|
+
await api.patch('/path', { body: JSON.stringify(data) });
|
|
269
|
+
await api.delete('/path');
|
|
1167
270
|
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
api.addResponseInterceptor(
|
|
1171
|
-
(response) => {
|
|
1172
|
-
console.log('Status:', response.status);
|
|
1173
|
-
return response;
|
|
1174
|
-
},
|
|
1175
|
-
(error) => {
|
|
1176
|
-
if (error.message.includes('timeout')) {
|
|
1177
|
-
alert('Request timed out');
|
|
1178
|
-
}
|
|
1179
|
-
return Promise.reject(error);
|
|
1180
|
-
}
|
|
1181
|
-
);
|
|
271
|
+
// Interceptors
|
|
272
|
+
api.addRequestInterceptor((config) => { return config; });
|
|
273
|
+
api.addResponseInterceptor((response) => { return response; });
|
|
1182
274
|
```
|
|
1183
275
|
|
|
1184
276
|
---
|
|
1185
277
|
|
|
1186
|
-
## Comparison with Axios
|
|
1187
|
-
|
|
1188
|
-
| Feature | Fetchify | Axios |
|
|
1189
|
-
|---------|----------|-------|
|
|
1190
|
-
| Size | ~5KB | ~20KB |
|
|
1191
|
-
| Dependencies | None (native fetch) | Standalone library |
|
|
1192
|
-
| Browser Support | Modern browsers | All browsers (polyfills) |
|
|
1193
|
-
| Interceptors | ✅ | ✅ |
|
|
1194
|
-
| Timeout | ✅ | ✅ |
|
|
1195
|
-
| Request Cancellation | ✅ (AbortController) | ✅ (CancelToken) |
|
|
1196
|
-
| Automatic JSON Transform | ❌ (manual) | ✅ |
|
|
1197
|
-
| Progress Events | ❌ | ✅ |
|
|
1198
|
-
| TypeScript | ❌ | ✅ |
|
|
1199
|
-
|
|
1200
|
-
---
|
|
1201
|
-
|
|
1202
|
-
## Future Enhancements
|
|
1203
|
-
|
|
1204
|
-
Potential features to add:
|
|
1205
|
-
- Retry logic with exponential backoff
|
|
1206
|
-
- Request caching
|
|
1207
|
-
- Automatic JSON parsing
|
|
1208
|
-
- Progress events for uploads
|
|
1209
|
-
- TypeScript definitions
|
|
1210
|
-
- Request/response transformation helpers
|
|
1211
|
-
- CSRF token handling
|
|
1212
|
-
- File upload helpers
|
|
1213
|
-
|
|
1214
|
-
---
|
|
1215
|
-
|
|
1216
278
|
## License
|
|
1217
279
|
|
|
1218
280
|
MIT License - Free to use and modify
|
|
1219
281
|
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
## Contributing
|
|
282
|
+
## Author
|
|
1223
283
|
|
|
1224
|
-
|
|
284
|
+
Rutansh Chawla
|
|
1225
285
|
|
|
1226
286
|
---
|
|
1227
287
|
|
|
1228
|
-
**Last Updated:** January 2026
|
|
1229
288
|
**Version:** 1.0.0
|
|
1230
|
-
**Author:** Rutansh Chawla
|
|
1231
|
-
|
|
1232
|
-
---
|
|
1233
|
-
|
|
1234
|
-
## Quick Reference Card
|
|
1235
|
-
|
|
1236
|
-
```javascript
|
|
1237
|
-
// Create instance
|
|
1238
|
-
const api = fetchify.create({ baseURL: '...', timeout: 5000 });
|
|
1239
|
-
|
|
1240
|
-
// Requests
|
|
1241
|
-
await api.get('/path', { timeout: 3000 });
|
|
1242
|
-
await api.post('/path', { body: JSON.stringify(data) });
|
|
1243
|
-
await api.put('/path', { body: JSON.stringify(data) });
|
|
1244
|
-
await api.patch('/path', { body: JSON.stringify(data) });
|
|
1245
|
-
await api.delete('/path');
|
|
1246
|
-
|
|
1247
|
-
// Interceptors
|
|
1248
|
-
api.addRequestInterceptor((config) => { /* modify */ return config; });
|
|
1249
|
-
api.addResponseInterceptor((response) => { /* handle */ return response; });
|
|
1250
|
-
|
|
1251
|
-
// Error handling
|
|
1252
|
-
try {
|
|
1253
|
-
const res = await api.get('/path');
|
|
1254
|
-
const data = await res.json();
|
|
1255
|
-
} catch (error) {
|
|
1256
|
-
console.error(error.message);
|
|
1257
|
-
}
|
|
1258
|
-
```
|
|
1259
|
-
|
|
1260
|
-
---
|
package/fetchify.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rutansh0101/fetchify",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.3",
|
|
4
4
|
"description": "A modern, lightweight HTTP client library built on top of the native Fetch API with axios-like interface, interceptors, and timeout support",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Rutansh Chawla",
|