create-request 0.0.1-beta.2 β†’ 0.0.1-beta.20

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/README.md CHANGED
@@ -1,467 +1,590 @@
1
1
  # create-request
2
2
 
3
3
  [![npm version](https://img.shields.io/npm/v/create-request.svg)](https://www.npmjs.com/package/create-request)
4
- [![License](https://img.shields.io/npm/l/create-request.svg)](https://github.com/yourusername/create-request/blob/main/LICENSE)
4
+ [![License](https://img.shields.io/npm/l/create-request.svg)](https://github.com/DanielAmenou/create-request/blob/main/LICENSE)
5
5
  [![Bundle Size](https://img.shields.io/bundlephobia/minzip/create-request)](https://bundlephobia.com/package/create-request)
6
+ [![TypeScript](https://img.shields.io/badge/TypeScript-4.7%2B-blue)](https://www.typescriptlang.org/)
6
7
 
7
8
  `create-request` is a modern TypeScript library that transforms how you make API calls. Built as an elegant wrapper around the native Fetch API, it provides a chainable, fluent interface that dramatically reduces boilerplate while adding powerful features like automatic retries, timeout handling, and comprehensive error management.
8
9
 
9
- With first-class TypeScript support, built-in response parsing, and intuitive authentication helpers, `create-request` makes your API code more readable, more reliable, and fully type-safeβ€”all with zero dependencies and a tiny footprint.
10
+ ## Table of Contents
11
+
12
+ - [Core Features](#core-features)
13
+ - [Why create-request](#why-create-request)
14
+ - [Installation](#installation)
15
+ - [Basic Usage](#basic-usage)
16
+ - [Advanced Usage](#advanced-usage)
17
+ - [TypeScript Support](#typescript-support)
18
+ - [CSRF Protection](#csrf-protection)
19
+ - [Performance Considerations](#performance-considerations)
20
+ - [Browser Support](#browser-support)
21
+ - [Comparison of JavaScript HTTP Client Libraries](#comparison-of-javascript-http-client-libraries)
22
+ - [License](#license)
23
+
24
+ ## Core Features
25
+
26
+ - πŸš€ **Performance** - Tiny bundle size with zero dependencies
27
+ - 🚧 **Error Handling** - Detailed error info with custom error class
28
+ - ⛓️ **Chainable API** - Build and execute requests with a fluent interface
29
+ - ⏱️ **Timeout Support** - Set timeouts for requests with automatic aborts
30
+ - πŸ›‘οΈ **Type Safety** - Full TypeScript support with intelligent type inference
31
+ - πŸ” **Auth Helpers** - Simple methods for common authentication patterns
32
+ - πŸ” **Data Selection** - Extract and transform specific data from responses
33
+ - πŸ” **Automatic Retries** - Retry failed requests with customizable settings
34
+ - πŸ“‰ **Reduced Boilerplate** - Write 60% less code for common API operations
35
+ - πŸ”’ **CSRF Protection** - Built-in safeguards against cross-site request forgery
36
+ - πŸ“ **Response Caching** - Flexible caching system with multiple storage options
37
+ - πŸ›‘ **Request Cancellation** - Abort requests on demand with AbortController integration
10
38
 
11
39
  ## Why create-request?
12
40
 
13
- **You've probably been here before:** writing boilerplate fetch code, handling retries manually, struggling with timeouts, or wrestling with TypeScript types for your API responses. `create-request` solves all of that with an elegant, chainable API:
41
+ **API interactions often require repetitive code patterns** - handling HTTP status checks, parsing responses, managing errors, and dealing with TypeScript types. `create-request` provides a clean, efficient solution with an elegant API that separates request building from execution:
42
+
43
+ ### With Regular Fetch
14
44
 
15
45
  ```typescript
16
- // Instead of verbose fetch code with manual JSON parsing, error handling, etc.
17
- // You get a clean, intuitive API that just works
18
- const users = await create.get()
19
- .withTimeout(5000)
20
- .withRetries(3)
21
- .withBearerToken('your-token')
22
- .sendTo('https://api.example.com/users')
23
- .json<User[]>(); // Fully typed response!
46
+ async function createUser(userData) {
47
+ try {
48
+ const response = await fetch("https://api.example.com/users", {
49
+ method: "POST",
50
+ headers: {
51
+ "Content-Type": "application/json",
52
+ Authorization: "Basic " + btoa("username:password"),
53
+ },
54
+ body: JSON.stringify(userData),
55
+ });
56
+
57
+ if (!response.ok) {
58
+ throw new Error(`HTTP error! status: ${response.status}`);
59
+ }
60
+ const data = await response.json();
61
+ return data;
62
+ } catch (error) {
63
+ console.error("Fetch error:", error);
64
+ throw error;
65
+ }
66
+ }
24
67
  ```
25
68
 
26
- ## πŸš€ Designed for Developer Happiness
27
-
28
- - **Write Less Code** - Accomplish in one line what might take dozens with raw fetch
29
- - **Fewer Bugs** - Strong typing and smart defaults prevent common API bugs
30
- - **Self-Documenting** - Chainable API clearly expresses intent for easier code reviews
31
- - **Universal** - Works the same in browser and Node.js environments
32
-
33
- ## Overview
69
+ ### With create-request
34
70
 
35
- `create-request` provides a chainable, fluent API for making HTTP requests with built-in solutions for the most common API challenges. It's designed for developers who value clean code, type safety, and robust error handling.
36
-
37
- ## Features
38
-
39
- - πŸ”„ **Fully Typed** - Complete TypeScript support with accurate type definitions
40
- - πŸ”— **Chainable API** - Fluent interface for building requests
41
- - ⏱️ **Timeout Support** - Set timeouts for any request (no more hanging requests!)
42
- - πŸ” **Automatic Retries** - Smart retry mechanism with customizable strategies
43
- - πŸ”’ **Auth Helpers** - One-liner methods for common auth patterns
44
- - 🧩 **Query Parameter Management** - Easy query parameter handling with arrays support
45
- - πŸ“¦ **Response Transformations** - Parse responses as JSON, text, blob, etc.
46
- - 🚫 **Robust Error Handling** - Detailed error information with custom error class
47
- - πŸ”Œ **Zero Dependencies** - Built on the native Fetch API with no external baggage
48
- - 🌐 **Universal** - Works exactly the same in all modern browsers and Node.js
49
- - 🧠 **Smart Content-Type** - Automatic content type detection for request bodies
50
- - 🏷️ **Type Safety** - Enums for all string constants to prevent typos
71
+ ```typescript
72
+ import create from "create-request";
73
+
74
+ function createUser(userData) {
75
+ return create
76
+ .post()
77
+ .withBasicAuth("username", "password")
78
+ .withBody(userData) // Content-Type automatically set to application/json
79
+ .sendTo("https://api.example.com/users")
80
+ .getData<User>() // Type-safe response handling
81
+ .catch(error => {
82
+ console.error("Fetch error:", error);
83
+ throw error;
84
+ });
85
+ }
86
+ ```
51
87
 
52
88
  ## Installation
53
89
 
54
90
  ```bash
91
+ # npm
55
92
  npm install create-request
56
- ```
57
93
 
58
- Or with yarn:
59
-
60
- ```bash
94
+ # yarn
61
95
  yarn add create-request
96
+
97
+ # pnpm
98
+ pnpm add create-request
62
99
  ```
63
100
 
64
- ## See the Difference: How create-request Transforms API Calls
101
+ ## Basic Usage
65
102
 
66
- ### 1. Simplified Error Handling
103
+ ### Creating Requests
67
104
 
68
105
  ```typescript
69
- create.get()
70
- .sendTo('https://api.example.com/data')
71
- .json()
72
- .then(data => {
73
- // Process successful response
74
- console.log(data);
75
- })
76
- .catch(error => {
77
- if (error instanceof RequestError) {
78
- // Rich error details that fetch doesn't provide:
79
- console.log(`${error.status} error from ${error.method} ${error.url}`);
80
- console.log(`Was it a timeout? ${error.timeoutError}`);
81
-
82
- // Original error data still available:
83
- console.log(error.response);
84
- }
85
- });
106
+ import create from "create-request";
107
+
108
+ // Create different request types
109
+ const getRequest = create.get(); // GET
110
+ const putRequest = create.put(); // PUT
111
+ const postRequest = create.post(); // POST
112
+ const patchRequest = create.patch(); // PATCH
113
+ const deleteRequest = create.del(); // DELETE
114
+ const headRequest = create.head(); // HEAD
115
+ const optionsRequest = create.options(); // OPTIONS
86
116
  ```
87
117
 
88
- ### 2. Type-Safe API Responses
89
-
90
- ```typescript
91
- interface User {
92
- id: number;
93
- name: string;
94
- email: string;
95
- }
118
+ ### Request Configuration
96
119
 
97
- // TypeScript knows exactly what you're getting back!
98
- const users = await create.get()
99
- .sendTo('https://api.example.com/users')
100
- .json<User[]>();
120
+ The library provides a comprehensive set of configuration methods that can be chained together to customize your requests:
101
121
 
102
- // No more type casting or guessing - your IDE autocomplete works perfectly
103
- users.forEach(user => console.log(user.name));
104
- ```
122
+ ```typescript
123
+ import create, {
124
+ RequestPriority,
125
+ CredentialsPolicy,
126
+ RequestMode,
127
+ RedirectMode,
128
+ ReferrerPolicy,
129
+ } from "create-request";
130
+
131
+ // Configure request options
132
+ const request = create
133
+ .get()
134
+ // Basic headers
135
+ .withHeaders({ "X-API-Key": "abc123", "Accept-Language": "en-US" })
136
+ .withHeader("Custom-Header", "value") // Add a single header
137
+
138
+ // Timeout settings
139
+ .withTimeout(5000) // Request will abort after 5 seconds
140
+
141
+ // Automatic retry configuration
142
+ .withRetries(3) // Retry up to 3 times on failure
143
+ .onRetry(({ attempt, error }) => {
144
+ console.log(`Attempt ${attempt} failed: ${error.message}. Retrying...`);
145
+ // You can implement backoff strategy here
146
+ return new Promise(resolve => setTimeout(resolve, attempt * 1000));
147
+ })
105
148
 
106
- ### 3. Request Retries Made Simple
149
+ // Authentication methods
150
+ .withBearerToken("your-token") // Adds Authorization: Bearer your-token
151
+ .withBasicAuth("username", "password") // HTTP Basic Authentication
152
+ .withAuthorization("auth-scheme value") // Custom authorization header
107
153
 
108
- ```typescript
109
- // Without create-request: Pages of manual retry logic with delay calculations
110
- // With create-request: One line
154
+ // Add a single cookie
155
+ .withCookie("language", "en-US")
111
156
 
112
- const data = await create.get()
113
- .withRetries(3)
114
- .onRetry(({ retryCount, error }) => {
115
- console.log(`Retry ${retryCount} after ${error.message}`);
157
+ // Add multiple cookies
158
+ .withCookies({
159
+ sessionId: "abc123",
160
+ preferences: { value: "dark-mode", secure: true },
161
+ tracking: { value: "enabled", sameSite: SameSitePolicy.STRICT },
116
162
  })
117
- .sendTo('https://api.example.com/flaky-endpoint')
118
- .json();
163
+
164
+ // URL parameters
165
+ .withQueryParams({ search: "term", page: 1, limit: 20 })
166
+ .withQueryParam("filter", "active") // Add a single query parameter
167
+
168
+ // Request body configuration (for POST/PUT/PATCH)
169
+ .withContentType("application/json") // Set specific content type
170
+
171
+ // Fetch API options
172
+ .withCredentials(CredentialsPolicy.INCLUDE) // Includes cookies with cross-origin requests
173
+ .withMode(RequestMode.CORS) // Controls CORS behavior
174
+ .withRedirect(RedirectMode.FOLLOW) // Controls redirect behavior (follow, error, manual)
175
+ .withReferrer("https://example.com") // Sets request referrer
176
+ .withReferrerPolicy(ReferrerPolicy.NO_REFERRER_WHEN_DOWNGRADE) // Controls referrer policy
177
+ .withPriority(RequestPriority.HIGH) // Sets request priority
178
+ .withKeepAlive(true); // Keeps connection alive after the page is unloaded
119
179
  ```
120
180
 
121
- ## Basic Usage
181
+ Each configuration method returns the request object, allowing for a fluent interface where methods can be chained together. You can configure only what you need for a specific request:
122
182
 
123
183
  ```typescript
124
- import create from 'create-request';
125
-
126
- // Simple GET request with chained response parsing
127
- const users = await create.get()
128
- .withTimeout(5000)
129
- .sendTo('https://api.example.com/users')
130
- .json(); // Parse response directly
131
-
132
- // POST request with JSON body
133
- const newUser = await create.post()
134
- .withBody({ name: 'Daniel Amenou', email: 'daniel@example.com' })
135
- .withBearerToken('your-token-here')
136
- .sendTo('https://api.example.com/users')
137
- .json();
184
+ // Simple example with just what's needed
185
+ const searchUsers = create
186
+ .get()
187
+ .withBearerToken(userToken)
188
+ .withQueryParams({ q: searchTerm, limit: 20 })
189
+ .withTimeout(3000);
190
+
191
+ // Now execute the request
192
+ const users = await searchUsers.sendTo("https://api.example.com/users").getData();
138
193
  ```
139
194
 
140
- ## API Reference
141
-
142
- ### Request Creation
143
-
144
- Create requests using the appropriate factory method:
195
+ You can also create reusable base requests with common settings:
145
196
 
146
197
  ```typescript
147
- // Methods without body
148
- create.get()
149
- create.del()
150
- create.head()
151
- create.options()
152
-
153
- // Methods with body
154
- create.put()
155
- create.post()
156
- create.patch()
157
- ```
158
-
159
- ### Request Configuration
198
+ // Create base authenticated request
199
+ const apiBase = create
200
+ .get()
201
+ .withHeaders({
202
+ "X-API-Version": "1.2",
203
+ "Accept-Language": "en-US",
204
+ })
205
+ .withBearerToken(authToken)
206
+ .withTimeout(5000)
207
+ .withRetries(2);
160
208
 
161
- All requests support these configuration methods:
209
+ // Use the base request for different endpoints
210
+ const users = await apiBase.sendTo("https://api.example.com/users").getData();
211
+ const products = await apiBase.sendTo("https://api.example.com/products").getData();
212
+ ```
162
213
 
163
- #### Common Settings
214
+ ### Request Bodies (POST/PUT/PATCH)
164
215
 
165
216
  ```typescript
166
- import { RequestPriority, CredentialsPolicy, RequestMode, RedirectMode } from 'create-request';
217
+ // JSON body (Content-Type automatically set to application/json)
218
+ const jsonRequest = create.post().withBody({ name: "John", age: 30 });
167
219
 
168
- // Set request headers
169
- .withHeaders({ 'X-API-Key': 'abc123' })
220
+ // String body (Content-Type automatically set to text/plain)
221
+ const textRequest = create.post().withBody("Plain text content");
170
222
 
171
- // Set cookies
172
- .withCookies({
173
- sessionId: 'abc123',
174
- preference: 'darkMode',
175
- region: 'us-east'
176
- })
223
+ // Form data
224
+ const formData = new FormData();
225
+ formData.append("name", "John");
226
+ formData.append("file", fileBlob);
177
227
 
178
- // Set a single content type header
179
- .withContentType('application/json')
228
+ const formRequest = create.post().withBody(formData);
180
229
 
181
- // Set request timeout in milliseconds
182
- .withTimeout(5000)
230
+ // URLSearchParams (typically used for application/x-www-form-urlencoded)
231
+ const params = new URLSearchParams();
232
+ params.append("username", "john");
233
+ params.append("password", "secret");
183
234
 
184
- // Configure retry behavior
185
- .withRetries(3)
186
- .onRetry(({ retryCount, error }) => {
187
- console.log(`Retry ${retryCount} after error: ${error.message}`);
188
- })
189
-
190
- // Use an external abort controller
191
- .withAbortController(myAbortController)
192
-
193
- // Set credentials policy - uses same-origin by default
194
- .withCredentials(CredentialsPolicy.INCLUDE)
235
+ const formUrlEncodedRequest = create.post().withBody(params);
236
+ ```
195
237
 
196
- // Set redirect behavior
197
- .withRedirect(RedirectMode.FOLLOW)
238
+ ### Executing Requests
198
239
 
199
- // Set request mode
200
- .withMode(RequestMode.CORS)
240
+ ```typescript
241
+ // Simple execution
242
+ const response = await request.sendTo("https://api.example.com/endpoint");
243
+
244
+ // With direct data extraction
245
+ const jsonData = await request.sendTo("https://api.example.com/endpoint").getJson();
246
+ const textData = await request.sendTo("https://api.example.com/endpoint").getText();
247
+ const blobData = await request.sendTo("https://api.example.com/endpoint").getBlob();
248
+ const bodyStream = await request.sendTo("https://api.example.com/endpoint").getBody();
249
+ const arrayBuffer = await request.sendTo("https://api.example.com/endpoint").getArrayBuffer();
250
+
251
+ // Using the data selector API to extract specific data
252
+ const userData = await request
253
+ .sendTo("https://api.example.com/users")
254
+ .getData(data => data.results.users);
255
+
256
+ // Using the data selector without a selector function just returns the full JSON response
257
+ const fullData = await request.sendTo("https://api.example.com/data").getData();
258
+ ```
201
259
 
202
- // Set referrer
203
- .withReferrer('https://example.com')
260
+ ### Error Handling
204
261
 
205
- // Set keepalive flag (allows request to outlive the page)
206
- .withKeepAlive(true)
262
+ All errors from requests are instances of `RequestError` with detailed information:
207
263
 
208
- // Set request priority
209
- .withPriority(RequestPriority.HIGH)
264
+ ```typescript
265
+ try {
266
+ const data = await request.sendTo("https://api.example.com/data").getJson();
267
+ } catch (error) {
268
+ // error will always be a RequestError
269
+ console.log(error.message); // Error message
270
+ console.log(error.status); // HTTP status code (if available)
271
+ console.log(error.url); // Request URL
272
+ console.log(error.method); // HTTP method
273
+ console.log(error.timeoutError); // Whether it was a timeout
274
+
275
+ // Access the original response if available
276
+ if (error.response) {
277
+ // Raw Response object is available
278
+ console.log(error.response.status);
279
+ }
280
+ }
210
281
  ```
211
282
 
212
- #### Query Parameters
283
+ ### Caching Requests
213
284
 
214
285
  ```typescript
215
- // Add multiple query parameters
216
- .withQueryParams({
217
- search: 'keyword',
218
- page: 1,
219
- filters: ['active', 'verified']
220
- })
221
-
222
- // Add a single query parameter
223
- .withQueryParam('sort', 'desc')
224
-
286
+ import create, {
287
+ createMemoryStorage,
288
+ createLocalStorageStorage,
289
+ createSessionStorageStorage,
290
+ } from "create-request";
291
+
292
+ // Simple in-memory caching
293
+ const request = create
294
+ .get()
295
+ .withCache({
296
+ storage: createMemoryStorage(),
297
+ ttl: 60000, // 1 minute in milliseconds
298
+ })
299
+ .sendTo("https://api.example.com/data");
300
+
301
+ // Using localStorage storage
302
+ const request = create
303
+ .get()
304
+ .withCache({
305
+ storage: createLocalStorageStorage(),
306
+ ttl: 5 * 60 * 1000, // 5 minutes
307
+ maxSize: "1MB",
308
+ keyPrefix: "user-data",
309
+ })
310
+ .sendTo("https://api.example.com/users");
311
+
312
+ // Using sessionStorage for per-session caching
313
+ const request = create
314
+ .get()
315
+ .withCache({
316
+ storage: createSessionStorageStorage(),
317
+ maxEntries: 50,
318
+ varyByHeaders: ["x-api-version"],
319
+ })
320
+ .sendTo("https://api.example.com/data");
225
321
  ```
226
322
 
227
- #### Authentication
228
-
229
- ```typescript
230
- // Basic authentication
231
- .withBasicAuth('username', 'password')
323
+ ## Advanced Usage
232
324
 
233
- // Bearer token
234
- .withBearerToken('your-jwt-token')
325
+ ### Request Reuse
235
326
 
236
- // Custom authorization header
237
- .withAuthorization('Custom scheme-and-token')
238
- ```
327
+ ```typescript
328
+ // Create a base authenticated request
329
+ const authRequest = create.get().withBearerToken("token123").withTimeout(5000).withRetries(2);
239
330
 
240
- ### Requests with Body
331
+ // Reuse for different endpoints
332
+ const users = await authRequest.sendTo("https://api.example.com/users").getJson();
333
+ const products = await authRequest.sendTo("https://api.example.com/products").getJson();
241
334
 
242
- For POST, PUT, and PATCH requests, you can set a body:
335
+ // You can also create application-wide base requests
336
+ const apiBase = () =>
337
+ create
338
+ .get()
339
+ .withHeaders({
340
+ "X-API-Version": "1.2",
341
+ "Accept-Language": "en-US",
342
+ })
343
+ .withBearerToken(getAuthToken()) // Get fresh token each time
344
+ .withTimeout(5000);
243
345
 
244
- ```typescript
245
- // JSON body (automatically sets Content-Type: application/json)
246
- .withBody({ name: 'John', age: 30 })
247
-
248
- // String body
249
- .withBody('Plain text content')
250
-
251
- // FormData
252
- const form = new FormData();
253
- form.append('file', fileBlob);
254
- .withBody(form)
255
-
256
- // Blob body
257
- const blob = new Blob(['Hello, world!'], { type: 'text/plain' });
258
- const request = create.post()
259
- .withBody(blob)
260
- .sendTo('https://api.example.com/upload');
261
-
262
- // ArrayBuffer body
263
- const buffer = new ArrayBuffer(8);
264
- const request = create.post()
265
- .withBody(buffer)
266
- .sendTo('https://api.example.com/upload');
267
-
268
- // ReadableStream body
269
- const stream = new ReadableStream();
270
- const request = create.post()
271
- .withBody(stream)
272
- .sendTo('https://api.example.com/upload');
346
+ // Use throughout your application
347
+ function getUsers() {
348
+ return apiBase().sendTo("https://api.example.com/users").getData();
349
+ }
273
350
 
351
+ function getProducts() {
352
+ return apiBase().sendTo("https://api.example.com/products").getData();
353
+ }
274
354
  ```
275
355
 
276
- ### Response Handling
277
-
278
- The library provides convenient methods for handling responses that can be chained directly after `sendTo()`:
356
+ ### Request Cancellation
279
357
 
280
358
  ```typescript
281
- // Parse as JSON with type inference
282
- const jsonData = await create.get()
283
- .sendTo('https://api.example.com/data')
284
- .json<MyDataType>();
285
-
286
- // Get as text
287
- const text = await create.get()
288
- .sendTo('https://api.example.com/text')
289
- .text();
290
-
291
- // Get as blob
292
- const blob = await create.get()
293
- .sendTo('https://api.example.com/file')
294
- .blob();
295
-
296
- // Get as ArrayBuffer
297
- const buffer = await create.get()
298
- .sendTo('https://api.example.com/binary')
299
- .arrayBuffer();
300
-
301
- // Get as ReadableStream
302
- const stream = await create.get()
303
- .sendTo('https://api.example.com/stream')
304
- .body();
305
-
306
- // For more control, you can access the raw response first
307
- const response = await create.get().sendTo('https://api.example.com/data');
308
- const status = response.status;
309
- const headers = response.headers;
310
- const data = await response.json();
311
- ```
359
+ const controller = new AbortController();
312
360
 
313
- ### Error Handling
361
+ const request = create.get().withTimeout(10000).withAbortController(controller);
314
362
 
315
- The library provides a `RequestError` class with details about failed requests:
363
+ // Later, cancel the request if needed
364
+ setTimeout(() => controller.abort(), 2000);
316
365
 
317
- ```typescript
318
366
  try {
319
- const data = await create.get()
320
- .sendTo('https://api.example.com/data')
321
- .json();
367
+ const data = await request.sendTo("https://api.example.com/slow-endpoint").getJson();
322
368
  } catch (error) {
323
- if (error instanceof RequestError) {
324
- console.log(error.message); // Error message
325
- console.log(error.status); // HTTP status code (if available)
326
- console.log(error.url); // Request URL
327
- console.log(error.method); // HTTP method
328
- console.log(error.timeoutError); // Whether it was a timeout
329
- console.log(error.response); // Raw response (if available)
369
+ if (error.name === "AbortError") {
370
+ console.log("Request was cancelled by user");
371
+ } else if (error.timeoutError) {
372
+ console.log("Request timed out");
373
+ } else {
374
+ console.log("Other error:", error.message);
330
375
  }
331
376
  }
332
377
  ```
333
378
 
334
- ## Advanced Usage
379
+ ### Data Selection
335
380
 
336
- ### Handling File Downloads
381
+ The `getData` method provides a powerful way to extract and transform specific data from API responses:
337
382
 
338
383
  ```typescript
339
- const blob = await create.get()
340
- .withHeaders({ Accept: 'application/pdf' })
341
- .sendTo('https://api.example.com/reports/123/download')
342
- .blob();
343
-
344
- const url = window.URL.createObjectURL(blob);
345
-
346
- // Create a download link
347
- const a = document.createElement('a');
348
- a.href = url;
349
- a.download = 'report.pdf';
350
- a.click();
351
-
352
- // Clean up
353
- window.URL.revokeObjectURL(url);
384
+ // Extract specific properties from nested structures
385
+ const posts = await request
386
+ .sendTo("https://api.example.com/feed")
387
+ .getData(data => data.feed.posts);
388
+
389
+ // Transform data in the selector function
390
+ const usernames = await request
391
+ .sendTo("https://api.example.com/users")
392
+ .getData(data => data.users.map(user => user.username));
393
+
394
+ // Apply filtering in the selector
395
+ const activeUsers = await request
396
+ .sendTo("https://api.example.com/users")
397
+ .getData(data => data.users.filter(user => user.isActive));
398
+
399
+ // Combine data from complex nested structures
400
+ const combinedData = await request
401
+ .sendTo("https://api.example.com/dashboard")
402
+ .getData(data => ({
403
+ userCount: data.stats.users.total,
404
+ recentPosts: data.content.recent.slice(0, 5),
405
+ notifications: data.user.notifications.unread,
406
+ }));
354
407
  ```
355
408
 
356
- ### Working with AbortController
409
+ When a selector fails, the error message will contain the original response data to help diagnose the issue:
357
410
 
358
411
  ```typescript
359
- const controller = new AbortController();
360
-
361
- // Pass the controller to the request
362
412
  try {
363
- const data = await create.get()
364
- .withAbortController(controller)
365
- .sendTo('https://api.example.com/data')
366
- .json();
413
+ // This will fail if the response structure doesn't match expectations
414
+ const result = await request
415
+ .sendTo("https://api.example.com/data")
416
+ .getData(data => data.results.items);
367
417
  } catch (error) {
368
- // Handle the aborted request
418
+ console.error(error);
419
+ // Error message includes the original response data for debugging
369
420
  }
370
-
371
- // Cancel the request after 2 seconds
372
- setTimeout(() => controller.abort(), 2000);
373
421
  ```
374
422
 
375
- ### Request Priority
423
+ ### Implementing Custom Storage Providers
424
+
425
+ You can create your own storage provider by implementing the `StorageProvider` interface:
376
426
 
377
427
  ```typescript
378
- import { RequestPriority } from 'create-request';
379
-
380
- // High priority for critical resources
381
- const userProfile = await create.get()
382
- .withPriority(RequestPriority.HIGH)
383
- .sendTo('https://api.example.com/user/profile')
384
- .json();
385
-
386
- // Low priority for non-critical resources
387
- const recommendations = await create.get()
388
- .withPriority(RequestPriority.LOW)
389
- .sendTo('https://api.example.com/recommendations')
390
- .json();
391
- ```
428
+ import { StorageProvider } from "create-request";
392
429
 
393
- ### Creating Reusable Request Configurations
430
+ // Create a namespaced storage provider
431
+ export function createNamespacedStorage(namespace: string): StorageProvider {
432
+ return {
433
+ get(key: string): string | null {
434
+ return localStorage.getItem(`${namespace}:${key}`);
435
+ },
394
436
 
395
- ```typescript
396
- import { RequestPriority, CredentialsPolicy } from 'create-request';
437
+ set(key: string, value: string): void {
438
+ try {
439
+ localStorage.setItem(`${namespace}:${key}`, value);
440
+ } catch (e) {
441
+ // Handle quota exceeded errors
442
+ console.warn("Storage quota exceeded, clearing cache");
443
+ this.clear();
444
+ try {
445
+ localStorage.setItem(`${namespace}:${key}`, value);
446
+ } catch (e) {
447
+ console.error("Failed to set cache item even after clearing cache");
448
+ }
449
+ }
450
+ },
397
451
 
398
- function createAuthenticatedRequest(token) {
399
- return create.get()
400
- .withBearerToken(token)
401
- .withHeaders({
402
- 'X-App-Version': '1.0.0',
403
- })
404
- .withTimeout(10000)
405
- .withRetries(2)
406
- .withCredentials(CredentialsPolicy.INCLUDE)
407
- .withPriority(RequestPriority.HIGH);
452
+ has(key: string): boolean {
453
+ return localStorage.getItem(`${namespace}:${key}`) !== null;
454
+ },
455
+
456
+ delete(key: string): void {
457
+ localStorage.removeItem(`${namespace}:${key}`);
458
+ },
459
+
460
+ clear(): void {
461
+ // Only clear items in our namespace
462
+ Object.keys(localStorage)
463
+ .filter(key => key.startsWith(`${namespace}:`))
464
+ .forEach(key => localStorage.removeItem(key));
465
+ },
466
+ };
408
467
  }
409
468
 
410
- // Later use the configured request
411
- const users = await createAuthenticatedRequest(myToken)
412
- .sendTo('https://api.example.com/users')
413
- .json();
469
+ // Usage:
470
+ const request = create
471
+ .get()
472
+ .withCache({
473
+ storage: createNamespacedStorage("api-cache"),
474
+ ttl: 24 * 60 * 60 * 1000, // 24 hours
475
+ })
476
+ .sendTo("https://api.example.com/data");
414
477
  ```
415
478
 
416
- ### Cookie Management
479
+ ## TypeScript Support
417
480
 
418
481
  ```typescript
419
- // Set multiple cookies in a single request
420
- const response = await create.get()
421
- .withCookies({
422
- sessionId: 'abc123',
423
- theme: 'dark',
424
- region: 'us-east',
425
- 'analytics-opt-in': 'true'
426
- })
427
- .sendTo('https://api.example.com/dashboard');
482
+ interface User {
483
+ id: number;
484
+ name: string;
485
+ email: string;
486
+ isActive: boolean;
487
+ }
428
488
 
429
- // Combine with other header settings
430
- const response = await create.get()
431
- .withCookies({ sessionId: 'abc123' })
432
- .withBearerToken('your-token-here')
433
- .sendTo('https://api.example.com/protected-resource');
489
+ interface ApiResponse<T> {
490
+ data: T;
491
+ meta: {
492
+ total: number;
493
+ page: number;
494
+ };
495
+ }
496
+
497
+ // Type the full response
498
+ const response = await create
499
+ .get()
500
+ .sendTo("https://api.example.com/users")
501
+ .getJson<ApiResponse<User[]>>();
502
+
503
+ // Or use getData with type parameters
504
+ const users = await create
505
+ .get()
506
+ .sendTo("https://api.example.com/users")
507
+ .getData<ApiResponse<User[]>, User[]>(data => data.data);
508
+
509
+ // TypeScript knows the types
510
+ users.forEach(user => {
511
+ console.log(`${user.name} (${user.email}): ${user.isActive ? "Active" : "Inactive"}`);
512
+ });
513
+
514
+ // Function with proper types
515
+ async function getUserById(id: number): Promise<User> {
516
+ return create
517
+ .get()
518
+ .withQueryParam("id", id)
519
+ .sendTo("https://api.example.com/users")
520
+ .getData<ApiResponse<User[]>, User>(data => {
521
+ const user = data.data[0];
522
+ if (!user) throw new Error(`User with ID ${id} not found`);
523
+ return user;
524
+ });
525
+ }
434
526
  ```
435
527
 
436
- ## Environment-Specific Limitations
528
+ ## CSRF Protection
529
+
530
+ Cross-Site Request Forgery (CSRF) is a type of security vulnerability where unauthorized commands are executed on behalf of an authenticated user. `create-request` provides built-in protection mechanisms to help prevent CSRF attacks.
437
531
 
438
- ### Browser Limitations
532
+ ### How CSRF Protection Works
439
533
 
440
- 1. **CORS Restrictions** - Browsers enforce Same-Origin Policy and CORS. Requests to different origins must have proper CORS headers set up server-side.
534
+ The library employs multiple strategies to protect against CSRF attacks:
441
535
 
442
- 2. **Cookies and Authentication** - Third-party cookies might be blocked depending on browser settings. Use `withCredentials(CredentialsPolicy.INCLUDE)` for cross-origin authenticated requests.
536
+ 1. **Automatic X-Requested-With Header**: By default, all requests include the `X-Requested-With: XMLHttpRequest` header, which helps servers identify legitimate AJAX requests.
443
537
 
444
- 3. **Mixed Content** - Browsers block HTTP requests from HTTPS pages. Always use HTTPS endpoints in production.
538
+ 2. **CSRF Token Support**: The library can automatically include CSRF tokens in request headers, which servers can validate to ensure the request came from your application.
445
539
 
446
- 4. **Content Security Policy (CSP)** - If your application uses CSP, you must configure it to allow connections to your API endpoints.
540
+ 3. **XSRF Cookie Reading**: For frameworks that use the double-submit cookie pattern (like Laravel, Rails, or Django), the library can automatically read XSRF tokens from cookies and include them in request headers.
447
541
 
448
- 5. **Priority Hints** - The `withPriority` feature is only supported in some modern browsers and may be ignored in others.
542
+ ### Global CSRF Configuration
449
543
 
450
- 6. **KeepAlive** - The `withKeepAlive` feature allows requests to outlive the page but has varying support across browsers.
544
+ You can configure CSRF settings globally for all requests:
451
545
 
452
- ### Server-Side (Node.js) Limitations
546
+ ```typescript
547
+ // Configure CSRF settings for all requests
548
+ create.config.setCsrfToken("your-csrf-token");
549
+ create.config.setCsrfHeaderName("X-CSRF-Token"); // Default header name
550
+ create.config.setXsrfCookieName("XSRF-TOKEN"); // Default cookie name
551
+ create.config.setEnableAntiCsrf(true); // Enable/disable X-Requested-With header
552
+ create.config.setEnableAutoXsrf(true); // Enable/disable automatic cookie-to-header token
553
+ ```
453
554
 
454
- 1. **Missing Node.js Fetch** - Prior to Node 18, the Fetch API isn't available natively.
555
+ ### Per-Request CSRF Settings
455
556
 
456
- 2. **Self-signed Certificates** - Node.js rejects self-signed certificates by default. You may need additional configuration for development environments.
557
+ You can also configure CSRF protection on individual requests:
457
558
 
458
- 3. **Memory Management** - When downloading large files or processing large responses, be mindful of memory consumption, especially when using `arrayBuffer()` or `blob()`.
559
+ ```typescript
560
+ // Configure CSRF for a specific request
561
+ const request = create
562
+ .post()
563
+ .withCsrfToken("request-specific-token") // Set a specific token
564
+ .withAntiCsrfHeaders() // Explicitly add X-Requested-With header
565
+ .withoutCsrfProtection(); // Or disable all automatic CSRF protection
566
+ ```
459
567
 
460
- 4. **Proxy Support** - Additional configuration may be needed to work with HTTP proxies in Node.js.
568
+ ### Integrating with Backend Frameworks
461
569
 
462
- 5. **Limited Feature Support** - Features like `withKeepAlive` and `withPriority` might not be relevant or fully supported in Node.js environments.
570
+ Most modern frameworks support CSRF protection out of the box. The library works seamlessly with:
463
571
 
464
- ## Browser Compatibility
572
+ - **Laravel**: Automatically reads XSRF-TOKEN cookie and sends X-XSRF-TOKEN header
573
+ - **Rails**: Works with the Rails CSRF token system
574
+ - **Django**: Compatible with Django's CSRF middleware
575
+ - **Express.js + csurf**: Works with the csurf middleware token pattern
576
+
577
+ ## Performance Considerations
578
+
579
+ create-request is designed to be lightweight and efficient:
580
+
581
+ - **Zero Dependencies**: No extra libraries to load
582
+ - **Tree-Shakable**: Only import what you need
583
+ - **Minimal Overhead**: Thin wrapper around the native Fetch API
584
+ - **Memory Efficient**: Doesn't create unnecessary objects
585
+ - **Cache Management**: Configurable caching to reduce network requests
586
+
587
+ ## Browser Support
465
588
 
466
589
  This library works with all browsers that support the Fetch API:
467
590
 
@@ -471,104 +594,33 @@ This library works with all browsers that support the Fetch API:
471
594
  - Edge 14+
472
595
  - Opera 29+
473
596
 
474
- ## Comparison with Other Libraries
475
-
476
- | Feature | create-request | axios | fetch |
477
- |---------|---------------|-------|-------|
478
- | Bundle Size | Small (~2KB) | Medium (~14KB) | Smallest (built-in) |
479
- | TypeScript Support | Full with type inference | Partial | Manual |
480
- | Type Safety | Enums for constants | Limited | None |
481
- | Chainable API | βœ… Yes | ❌ No | ❌ No |
482
- | Request Cancellation | βœ… Yes (AbortController) | βœ… Yes | βœ… Yes (manual) |
483
- | Auto JSON | βœ… Yes | βœ… Yes | ❌ No |
484
- | Timeout | βœ… Yes | βœ… Yes | ❌ No (manual) |
485
- | Retries | βœ… Yes | ❌ No (needs addon) | ❌ No (manual) |
486
- | Query params with arrays | βœ… Yes | βœ… Yes | ❌ No (manual) |
487
- | Request Priority | βœ… Yes (simple API) | ❌ No | βœ… Yes (via options) |
488
- | KeepAlive | βœ… Yes (simple API) | ❌ No | βœ… Yes (via options) |
489
- | Learning Curve | Low | Medium | Medium |
490
-
491
- ### Why Not Just Use fetch?
492
- like JSON parsing
493
- The native Fetch API is powerful but low-level, requiring you to:ing
494
- - Write boilerplate for common tasks like JSON parsing
495
- - Manually implement timeout handlingeps
496
- - Create your own retry mechanisms- Parse non-2xx responses manually
497
- - Handle errors across multiple steps
498
- - Parse non-2xx responses manually`create-request` gives you all these features with a cleaner API that's designed specifically for modern TypeScript applications.
499
-
500
- `create-request` gives you all these features with a cleaner API that's designed specifically for modern TypeScript applications.### Why Not axios?
501
-
502
- ### Why Not axios?est` offers:
503
-
504
- While axios is popular, `create-request` offers:er)
505
- - Smaller bundle size (~85% smaller)n
506
- - More complete TypeScript integration
507
- - Built-in retries without addons- Modern features like priority hints
508
- - Modern features like priority hintsainable API
509
- - A more intuitive, chainable API
510
-
511
- ## Real-world Examples
512
- t for Authentication
513
- ### API Client for Authentication
514
-
515
- ```typescripta reusable authentication client
516
- // Create a reusable authentication client
517
- function createAuthClient(baseUrl) {
518
- return {{
519
- login: async (email, password) => {
520
- return create.post()
521
- .withBody({ email, password })eout(3000)
522
- .withTimeout(3000) .sendTo(`${baseUrl}/auth/login`)
523
- .sendTo(`${baseUrl}/auth/login`) .json();
524
- .json();
525
- },
526
- en) => {
527
- refreshToken: async (refreshToken) => {t()
528
- return create.post()
529
- .withBody({ refreshToken })ries(2)
530
- .withRetries(2) .sendTo(`${baseUrl}/auth/refresh`)
531
- .sendTo(`${baseUrl}/auth/refresh`) .json();
532
- .json(); }
533
- } };
534
- };
535
- }
536
-
537
- // Use it in your applicationst authClient = createAuthClient('https://api.example.com');
538
- const authClient = createAuthClient('https://api.example.com');const { token, user } = await authClient.login('user@example.com', 'password');
539
- const { token, user } = await authClient.login('user@example.com', 'password');
540
- ```
541
- ad with Progress
542
- ### File Upload with Progress
543
-
544
- ```typescript
545
- // Upload a file with abort capabilityconst uploadFile = async (file, onProgress) => {
546
- const uploadFile = async (file, onProgress) => {Controller();
547
- const controller = new AbortController();
548
- const form = new FormData();
549
- const form = new FormData();append('file', file);
550
- form.append('file', file);
551
-
552
- try {
553
- return await create.post()
554
- .withBody(form)oller(controller)
555
- .withAbortController(controller)or large files
556
- .withTimeout(60000) // 1 minute timeout for large filesries(2)
557
- .withRetries(2)s://api.example.com/upload')
558
- .sendTo('https://api.example.com/upload')
559
- .json();
560
- } catch (error) {f (error instanceof RequestError) {
561
- if (error instanceof RequestError) {ror(`Upload failed: ${error.message}`);
562
- console.error(`Upload failed: ${error.message}`); }
563
- } throw error;
564
- throw error;
565
- }
566
- turn {
567
- return { abort: () => controller.abort()
568
- abort: () => controller.abort();
569
- };};
570
- };
571
- ```
597
+ ## Comparison of JavaScript HTTP Client Libraries
598
+
599
+ | Feature | create-request | Fetch | Axios | SuperAgent | Got | Ky | node-fetch | Redaxios |
600
+ | ------------------- | -------------- | ------ | ------- | ---------- | ------- | ------ | ---------- | -------- |
601
+ | **Size (min+gzip)** | ~5.1KB | Native | ~13.6KB | ~17.8KB | ~17.8KB | ~3.4KB | ~7.7KB | ~1KB |
602
+ | **Browser** | Modern | Modern | IE11+ | IE9+ | ❌ No | Modern | ❌ No | Modern |
603
+ | **Node.js** | βœ… | βœ… | βœ… | βœ… | βœ… | βœ… | βœ… | βœ… |
604
+ | **HTTP/2** | βœ… | βœ… | βœ… | βœ… | βœ… | βœ… | ❌ | ❌ |
605
+ | **Auto Retries** | βœ… | ❌ | πŸ› οΈ | βœ… | βœ… | βœ… | ❌ | ❌ |
606
+ | **Cancellation** | βœ… | βœ… | βœ… | βœ… | βœ… | βœ… | βœ… | βœ… |
607
+ | **Caching** | βœ… | ❌ | ❌ | βœ… | βœ… | ❌ | ❌ | ❌ |
608
+ | **Auto JSON** | βœ… | ❌ | βœ… | βœ… | βœ… | βœ… | ❌ | βœ… |
609
+ | **Timeout** | βœ… | ❌ | βœ… | βœ… | βœ… | βœ… | βœ… | βœ… |
610
+ | **TypeScript** | βœ… | βœ… | βœ… | βœ… | βœ… | βœ… | βœ… | βœ… |
611
+ | **Streaming** | βœ… | βœ… | βœ… | βœ… | βœ… | βœ… | βœ… | ❌ |
612
+ | **Progress** | ❌ | ❌ | βœ… | βœ… | βœ… | βœ… | ❌ | ❌ |
613
+ | **Middleware** | ❌ | ❌ | βœ… | βœ… | βœ… | βœ… | ❌ | ❌ |
614
+ | **Cookies** | βœ… | βœ… | πŸ› οΈ | βœ… | βœ… | ❌ | ❌ | ❌ |
615
+ | **Pagination API** | ❌ | ❌ | ❌ | ❌ | βœ… | ❌ | ❌ | ❌ |
616
+ | **Zero Deps** | βœ… | βœ… | ❌ | ❌ | ❌ | βœ… | βœ… | βœ… |
617
+ | **Chainable API** | βœ… | ❌ | ❌ | βœ… | βœ… | βœ… | ❌ | ❌ |
618
+ | **CSRF Protection** | βœ… | ❌ | βœ… | ❌ | ❌ | ❌ | ❌ | ❌ |
619
+
620
+ **Notes:**
621
+
622
+ - "Modern" browser support: Chrome 42+, Firefox 39+, Safari 10.1+, Edge 14+, Opera 29+
623
+ - πŸ› οΈ Feature requires additional plugins or adapters (not available out-of-the-box)
572
624
 
573
625
  ## License
574
626