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