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

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,588 @@
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 headRequest = create.head(); // HEAD
113
+ const patchRequest = create.patch(); // PATCH
114
+ const deleteRequest = create.del(); // DELETE
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
+ })
105
146
 
106
- ### 3. Request Retries Made Simple
147
+ // Authentication methods
148
+ .withBearerToken("your-token") // Adds Authorization: Bearer your-token
149
+ .withBasicAuth("username", "password") // HTTP Basic Authentication
150
+ .withAuthorization("auth-scheme value") // Custom authorization header
107
151
 
108
- ```typescript
109
- // Without create-request: Pages of manual retry logic with delay calculations
110
- // With create-request: One line
152
+ // Add a single cookie
153
+ .withCookie("language", "en-US")
111
154
 
112
- const data = await create.get()
113
- .withRetries(3)
114
- .onRetry(({ retryCount, error }) => {
115
- console.log(`Retry ${retryCount} after ${error.message}`);
155
+ // Add multiple cookies
156
+ .withCookies({
157
+ sessionId: "abc123",
158
+ preferences: { value: "dark-mode", secure: true },
159
+ tracking: { value: "enabled", sameSite: SameSitePolicy.STRICT },
116
160
  })
117
- .sendTo('https://api.example.com/flaky-endpoint')
118
- .json();
161
+
162
+ // URL parameters
163
+ .withQueryParams({ search: "term", page: 1, limit: 20 })
164
+ .withQueryParam("filter", "active") // Add a single query parameter
165
+
166
+ // Request body configuration (for POST/PUT/PATCH)
167
+ .withContentType("application/json") // Set specific content type
168
+
169
+ // Fetch API options
170
+ .withCredentials(CredentialsPolicy.INCLUDE) // Includes cookies with cross-origin requests
171
+ .withMode(RequestMode.CORS) // Controls CORS behavior
172
+ .withRedirect(RedirectMode.FOLLOW) // Controls redirect behavior (follow, error, manual)
173
+ .withReferrer("https://example.com") // Sets request referrer
174
+ .withReferrerPolicy(ReferrerPolicy.NO_REFERRER_WHEN_DOWNGRADE) // Controls referrer policy
175
+ .withPriority(RequestPriority.HIGH) // Sets request priority
176
+ .withKeepAlive(true); // Keeps connection alive after the page is unloaded
119
177
  ```
120
178
 
121
- ## Basic Usage
179
+ 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
180
 
123
181
  ```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();
182
+ // Simple example with just what's needed
183
+ const searchUsers = create
184
+ .get()
185
+ .withBearerToken(userToken)
186
+ .withQueryParams({ q: searchTerm, limit: 20 })
187
+ .withTimeout(3000);
188
+
189
+ // Now execute the request
190
+ const users = await searchUsers.sendTo("https://api.example.com/users").getData();
138
191
  ```
139
192
 
140
- ## API Reference
141
-
142
- ### Request Creation
143
-
144
- Create requests using the appropriate factory method:
193
+ You can also create reusable base requests with common settings:
145
194
 
146
195
  ```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
196
+ // Create base authenticated request
197
+ const apiBase = create
198
+ .get()
199
+ .withHeaders({
200
+ "X-API-Version": "1.2",
201
+ "Accept-Language": "en-US",
202
+ })
203
+ .withBearerToken(authToken)
204
+ .withTimeout(5000)
205
+ .withRetries(2);
160
206
 
161
- All requests support these configuration methods:
207
+ // Use the base request for different endpoints
208
+ const users = await apiBase.sendTo("https://api.example.com/users").getData();
209
+ const products = await apiBase.sendTo("https://api.example.com/products").getData();
210
+ ```
162
211
 
163
- #### Common Settings
212
+ ### Request Bodies (POST/PUT/PATCH)
164
213
 
165
214
  ```typescript
166
- import { RequestPriority, CredentialsPolicy, RequestMode, RedirectMode } from 'create-request';
215
+ // JSON body (Content-Type automatically set to application/json)
216
+ const jsonRequest = create.post().withBody({ name: "John", age: 30 });
167
217
 
168
- // Set request headers
169
- .withHeaders({ 'X-API-Key': 'abc123' })
218
+ // String body (Content-Type automatically set to text/plain)
219
+ const textRequest = create.post().withBody("Plain text content");
170
220
 
171
- // Set cookies
172
- .withCookies({
173
- sessionId: 'abc123',
174
- preference: 'darkMode',
175
- region: 'us-east'
176
- })
221
+ // Form data
222
+ const formData = new FormData();
223
+ formData.append("name", "John");
224
+ formData.append("file", fileBlob);
177
225
 
178
- // Set a single content type header
179
- .withContentType('application/json')
226
+ const formRequest = create.post().withBody(formData);
180
227
 
181
- // Set request timeout in milliseconds
182
- .withTimeout(5000)
228
+ // URLSearchParams (typically used for application/x-www-form-urlencoded)
229
+ const params = new URLSearchParams();
230
+ params.append("username", "john");
231
+ params.append("password", "secret");
183
232
 
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)
233
+ const formUrlEncodedRequest = create.post().withBody(params);
234
+ ```
195
235
 
196
- // Set redirect behavior
197
- .withRedirect(RedirectMode.FOLLOW)
236
+ ### Executing Requests
198
237
 
199
- // Set request mode
200
- .withMode(RequestMode.CORS)
238
+ ```typescript
239
+ // Simple execution
240
+ const response = await request.sendTo("https://api.example.com/endpoint");
241
+
242
+ // With direct data extraction
243
+ const jsonData = await request.sendTo("https://api.example.com/endpoint").getJson();
244
+ const textData = await request.sendTo("https://api.example.com/endpoint").getText();
245
+ const blobData = await request.sendTo("https://api.example.com/endpoint").getBlob();
246
+ const bodyStream = await request.sendTo("https://api.example.com/endpoint").getBody();
247
+ const arrayBuffer = await request.sendTo("https://api.example.com/endpoint").getArrayBuffer();
248
+
249
+ // Using the data selector API to extract specific data
250
+ const userData = await request
251
+ .sendTo("https://api.example.com/users")
252
+ .getData(data => data.results.users);
253
+
254
+ // Using the data selector without a selector function just returns the full JSON response
255
+ const fullData = await request.sendTo("https://api.example.com/data").getData();
256
+ ```
201
257
 
202
- // Set referrer
203
- .withReferrer('https://example.com')
258
+ ### Error Handling
204
259
 
205
- // Set keepalive flag (allows request to outlive the page)
206
- .withKeepAlive(true)
260
+ All errors from requests are instances of `RequestError` with detailed information:
207
261
 
208
- // Set request priority
209
- .withPriority(RequestPriority.HIGH)
262
+ ```typescript
263
+ try {
264
+ const data = await request.sendTo("https://api.example.com/data").getJson();
265
+ } catch (error) {
266
+ // error will always be a RequestError
267
+ console.log(error.message); // Error message
268
+ console.log(error.status); // HTTP status code (if available)
269
+ console.log(error.url); // Request URL
270
+ console.log(error.method); // HTTP method
271
+ console.log(error.timeoutError); // Whether it was a timeout
272
+
273
+ // Access the original response if available
274
+ if (error.response) {
275
+ // Raw Response object is available
276
+ console.log(error.response.status);
277
+ }
278
+ }
210
279
  ```
211
280
 
212
- #### Query Parameters
281
+ ### Caching Requests
213
282
 
214
283
  ```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
-
284
+ import create, {
285
+ createMemoryStorage,
286
+ createLocalStorageStorage,
287
+ createSessionStorageStorage,
288
+ } from "create-request";
289
+
290
+ // Simple in-memory caching
291
+ const request = create
292
+ .get()
293
+ .withCache({
294
+ storage: createMemoryStorage(),
295
+ ttl: 60000, // 1 minute in milliseconds
296
+ })
297
+ .sendTo("https://api.example.com/data");
298
+
299
+ // Using localStorage storage
300
+ const request = create
301
+ .get()
302
+ .withCache({
303
+ storage: createLocalStorageStorage(),
304
+ ttl: 5 * 60 * 1000, // 5 minutes
305
+ maxSize: "1MB",
306
+ keyPrefix: "user-data",
307
+ })
308
+ .sendTo("https://api.example.com/users");
309
+
310
+ // Using sessionStorage for per-session caching
311
+ const request = create
312
+ .get()
313
+ .withCache({
314
+ storage: createSessionStorageStorage(),
315
+ maxEntries: 50,
316
+ varyByHeaders: ["x-api-version"],
317
+ })
318
+ .sendTo("https://api.example.com/data");
225
319
  ```
226
320
 
227
- #### Authentication
228
-
229
- ```typescript
230
- // Basic authentication
231
- .withBasicAuth('username', 'password')
321
+ ## Advanced Usage
232
322
 
233
- // Bearer token
234
- .withBearerToken('your-jwt-token')
323
+ ### Request Reuse
235
324
 
236
- // Custom authorization header
237
- .withAuthorization('Custom scheme-and-token')
238
- ```
325
+ ```typescript
326
+ // Create a base authenticated request
327
+ const authRequest = create.get().withBearerToken("token123").withTimeout(5000).withRetries(2);
239
328
 
240
- ### Requests with Body
329
+ // Reuse for different endpoints
330
+ const users = await authRequest.sendTo("https://api.example.com/users").getJson();
331
+ const products = await authRequest.sendTo("https://api.example.com/products").getJson();
241
332
 
242
- For POST, PUT, and PATCH requests, you can set a body:
333
+ // You can also create application-wide base requests
334
+ const apiBase = () =>
335
+ create
336
+ .get()
337
+ .withHeaders({
338
+ "X-API-Version": "1.2",
339
+ "Accept-Language": "en-US",
340
+ })
341
+ .withBearerToken(getAuthToken()) // Get fresh token each time
342
+ .withTimeout(5000);
243
343
 
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');
344
+ // Use throughout your application
345
+ function getUsers() {
346
+ return apiBase().sendTo("https://api.example.com/users").getData();
347
+ }
273
348
 
349
+ function getProducts() {
350
+ return apiBase().sendTo("https://api.example.com/products").getData();
351
+ }
274
352
  ```
275
353
 
276
- ### Response Handling
277
-
278
- The library provides convenient methods for handling responses that can be chained directly after `sendTo()`:
354
+ ### Request Cancellation
279
355
 
280
356
  ```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
- ```
357
+ const controller = new AbortController();
312
358
 
313
- ### Error Handling
359
+ const request = create.get().withTimeout(10000).withAbortController(controller);
314
360
 
315
- The library provides a `RequestError` class with details about failed requests:
361
+ // Later, cancel the request if needed
362
+ setTimeout(() => controller.abort(), 2000);
316
363
 
317
- ```typescript
318
364
  try {
319
- const data = await create.get()
320
- .sendTo('https://api.example.com/data')
321
- .json();
365
+ const data = await request.sendTo("https://api.example.com/slow-endpoint").getJson();
322
366
  } 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)
367
+ if (error.name === "AbortError") {
368
+ console.log("Request was cancelled by user");
369
+ } else if (error.timeoutError) {
370
+ console.log("Request timed out");
371
+ } else {
372
+ console.log("Other error:", error.message);
330
373
  }
331
374
  }
332
375
  ```
333
376
 
334
- ## Advanced Usage
377
+ ### Data Selection
335
378
 
336
- ### Handling File Downloads
379
+ The `getData` method provides a powerful way to extract and transform specific data from API responses:
337
380
 
338
381
  ```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);
382
+ // Extract specific properties from nested structures
383
+ const posts = await request
384
+ .sendTo("https://api.example.com/feed")
385
+ .getData(data => data.feed.posts);
386
+
387
+ // Transform data in the selector function
388
+ const usernames = await request
389
+ .sendTo("https://api.example.com/users")
390
+ .getData(data => data.users.map(user => user.username));
391
+
392
+ // Apply filtering in the selector
393
+ const activeUsers = await request
394
+ .sendTo("https://api.example.com/users")
395
+ .getData(data => data.users.filter(user => user.isActive));
396
+
397
+ // Combine data from complex nested structures
398
+ const combinedData = await request
399
+ .sendTo("https://api.example.com/dashboard")
400
+ .getData(data => ({
401
+ userCount: data.stats.users.total,
402
+ recentPosts: data.content.recent.slice(0, 5),
403
+ notifications: data.user.notifications.unread,
404
+ }));
354
405
  ```
355
406
 
356
- ### Working with AbortController
407
+ When a selector fails, the error message will contain the original response data to help diagnose the issue:
357
408
 
358
409
  ```typescript
359
- const controller = new AbortController();
360
-
361
- // Pass the controller to the request
362
410
  try {
363
- const data = await create.get()
364
- .withAbortController(controller)
365
- .sendTo('https://api.example.com/data')
366
- .json();
411
+ // This will fail if the response structure doesn't match expectations
412
+ const result = await request
413
+ .sendTo("https://api.example.com/data")
414
+ .getData(data => data.results.items);
367
415
  } catch (error) {
368
- // Handle the aborted request
416
+ console.error(error);
417
+ // Error message includes the original response data for debugging
369
418
  }
370
-
371
- // Cancel the request after 2 seconds
372
- setTimeout(() => controller.abort(), 2000);
373
419
  ```
374
420
 
375
- ### Request Priority
421
+ ### Implementing Custom Storage Providers
422
+
423
+ You can create your own storage provider by implementing the `StorageProvider` interface:
376
424
 
377
425
  ```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
- ```
426
+ import { StorageProvider } from "create-request";
392
427
 
393
- ### Creating Reusable Request Configurations
428
+ // Create a namespaced storage provider
429
+ export function createNamespacedStorage(namespace: string): StorageProvider {
430
+ return {
431
+ get(key: string): string | null {
432
+ return localStorage.getItem(`${namespace}:${key}`);
433
+ },
394
434
 
395
- ```typescript
396
- import { RequestPriority, CredentialsPolicy } from 'create-request';
435
+ set(key: string, value: string): void {
436
+ try {
437
+ localStorage.setItem(`${namespace}:${key}`, value);
438
+ } catch (e) {
439
+ // Handle quota exceeded errors
440
+ console.warn("Storage quota exceeded, clearing cache");
441
+ this.clear();
442
+ try {
443
+ localStorage.setItem(`${namespace}:${key}`, value);
444
+ } catch (e) {
445
+ console.error("Failed to set cache item even after clearing cache");
446
+ }
447
+ }
448
+ },
397
449
 
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);
450
+ has(key: string): boolean {
451
+ return localStorage.getItem(`${namespace}:${key}`) !== null;
452
+ },
453
+
454
+ delete(key: string): void {
455
+ localStorage.removeItem(`${namespace}:${key}`);
456
+ },
457
+
458
+ clear(): void {
459
+ // Only clear items in our namespace
460
+ Object.keys(localStorage)
461
+ .filter(key => key.startsWith(`${namespace}:`))
462
+ .forEach(key => localStorage.removeItem(key));
463
+ },
464
+ };
408
465
  }
409
466
 
410
- // Later use the configured request
411
- const users = await createAuthenticatedRequest(myToken)
412
- .sendTo('https://api.example.com/users')
413
- .json();
467
+ // Usage:
468
+ const request = create
469
+ .get()
470
+ .withCache({
471
+ storage: createNamespacedStorage("api-cache"),
472
+ ttl: 24 * 60 * 60 * 1000, // 24 hours
473
+ })
474
+ .sendTo("https://api.example.com/data");
414
475
  ```
415
476
 
416
- ### Cookie Management
477
+ ## TypeScript Support
417
478
 
418
479
  ```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');
480
+ interface User {
481
+ id: number;
482
+ name: string;
483
+ email: string;
484
+ isActive: boolean;
485
+ }
428
486
 
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');
487
+ interface ApiResponse<T> {
488
+ data: T;
489
+ meta: {
490
+ total: number;
491
+ page: number;
492
+ };
493
+ }
494
+
495
+ // Type the full response
496
+ const response = await create
497
+ .get()
498
+ .sendTo("https://api.example.com/users")
499
+ .getJson<ApiResponse<User[]>>();
500
+
501
+ // Or use getData with type parameters
502
+ const users = await create
503
+ .get()
504
+ .sendTo("https://api.example.com/users")
505
+ .getData<ApiResponse<User[]>, User[]>(data => data.data);
506
+
507
+ // TypeScript knows the types
508
+ users.forEach(user => {
509
+ console.log(`${user.name} (${user.email}): ${user.isActive ? "Active" : "Inactive"}`);
510
+ });
511
+
512
+ // Function with proper types
513
+ async function getUserById(id: number): Promise<User> {
514
+ return create
515
+ .get()
516
+ .withQueryParam("id", id)
517
+ .sendTo("https://api.example.com/users")
518
+ .getData<ApiResponse<User[]>, User>(data => {
519
+ const user = data.data[0];
520
+ if (!user) throw new Error(`User with ID ${id} not found`);
521
+ return user;
522
+ });
523
+ }
434
524
  ```
435
525
 
436
- ## Environment-Specific Limitations
526
+ ## CSRF Protection
527
+
528
+ 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
529
 
438
- ### Browser Limitations
530
+ ### How CSRF Protection Works
439
531
 
440
- 1. **CORS Restrictions** - Browsers enforce Same-Origin Policy and CORS. Requests to different origins must have proper CORS headers set up server-side.
532
+ The library employs multiple strategies to protect against CSRF attacks:
441
533
 
442
- 2. **Cookies and Authentication** - Third-party cookies might be blocked depending on browser settings. Use `withCredentials(CredentialsPolicy.INCLUDE)` for cross-origin authenticated requests.
534
+ 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
535
 
444
- 3. **Mixed Content** - Browsers block HTTP requests from HTTPS pages. Always use HTTPS endpoints in production.
536
+ 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
537
 
446
- 4. **Content Security Policy (CSP)** - If your application uses CSP, you must configure it to allow connections to your API endpoints.
538
+ 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
539
 
448
- 5. **Priority Hints** - The `withPriority` feature is only supported in some modern browsers and may be ignored in others.
540
+ ### Global CSRF Configuration
449
541
 
450
- 6. **KeepAlive** - The `withKeepAlive` feature allows requests to outlive the page but has varying support across browsers.
542
+ You can configure CSRF settings globally for all requests:
451
543
 
452
- ### Server-Side (Node.js) Limitations
544
+ ```typescript
545
+ // Configure CSRF settings for all requests
546
+ create.config.setCsrfToken("your-csrf-token");
547
+ create.config.setCsrfHeaderName("X-CSRF-Token"); // Default header name
548
+ create.config.setXsrfCookieName("XSRF-TOKEN"); // Default cookie name
549
+ create.config.setEnableAntiCsrf(true); // Enable/disable X-Requested-With header
550
+ create.config.setEnableAutoXsrf(true); // Enable/disable automatic cookie-to-header token
551
+ ```
453
552
 
454
- 1. **Missing Node.js Fetch** - Prior to Node 18, the Fetch API isn't available natively.
553
+ ### Per-Request CSRF Settings
455
554
 
456
- 2. **Self-signed Certificates** - Node.js rejects self-signed certificates by default. You may need additional configuration for development environments.
555
+ You can also configure CSRF protection on individual requests:
457
556
 
458
- 3. **Memory Management** - When downloading large files or processing large responses, be mindful of memory consumption, especially when using `arrayBuffer()` or `blob()`.
557
+ ```typescript
558
+ // Configure CSRF for a specific request
559
+ const request = create
560
+ .post()
561
+ .withCsrfToken("request-specific-token") // Set a specific token
562
+ .withAntiCsrfHeaders() // Explicitly add X-Requested-With header
563
+ .withoutCsrfProtection(); // Or disable all automatic CSRF protection
564
+ ```
459
565
 
460
- 4. **Proxy Support** - Additional configuration may be needed to work with HTTP proxies in Node.js.
566
+ ### Integrating with Backend Frameworks
461
567
 
462
- 5. **Limited Feature Support** - Features like `withKeepAlive` and `withPriority` might not be relevant or fully supported in Node.js environments.
568
+ Most modern frameworks support CSRF protection out of the box. The library works seamlessly with:
463
569
 
464
- ## Browser Compatibility
570
+ - **Laravel**: Automatically reads XSRF-TOKEN cookie and sends X-XSRF-TOKEN header
571
+ - **Rails**: Works with the Rails CSRF token system
572
+ - **Django**: Compatible with Django's CSRF middleware
573
+ - **Express.js + csurf**: Works with the csurf middleware token pattern
574
+
575
+ ## Performance Considerations
576
+
577
+ create-request is designed to be lightweight and efficient:
578
+
579
+ - **Zero Dependencies**: No extra libraries to load
580
+ - **Tree-Shakable**: Only import what you need
581
+ - **Minimal Overhead**: Thin wrapper around the native Fetch API
582
+ - **Memory Efficient**: Doesn't create unnecessary objects
583
+ - **Cache Management**: Configurable caching to reduce network requests
584
+
585
+ ## Browser Support
465
586
 
466
587
  This library works with all browsers that support the Fetch API:
467
588
 
@@ -471,104 +592,33 @@ This library works with all browsers that support the Fetch API:
471
592
  - Edge 14+
472
593
  - Opera 29+
473
594
 
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
- ```
595
+ ## Comparison of JavaScript HTTP Client Libraries
596
+
597
+ | Feature | create-request | Fetch | Axios | SuperAgent | Got | Ky | node-fetch | Redaxios |
598
+ | ------------------- | -------------- | ------ | ------- | ---------- | ------- | ------ | ---------- | -------- |
599
+ | **Size (min+gzip)** | ~5.1KB | Native | ~13.6KB | ~17.8KB | ~17.8KB | ~3.4KB | ~7.7KB | ~1KB |
600
+ | **Browser** | Modern | Modern | IE11+ | IE9+ | ❌ No | Modern | ❌ No | Modern |
601
+ | **Node.js** | βœ… | βœ… | βœ… | βœ… | βœ… | βœ… | βœ… | βœ… |
602
+ | **HTTP/2** | βœ… | βœ… | βœ… | βœ… | βœ… | βœ… | ❌ | ❌ |
603
+ | **Auto Retries** | βœ… | ❌ | πŸ› οΈ | βœ… | βœ… | βœ… | ❌ | ❌ |
604
+ | **Cancellation** | βœ… | βœ… | βœ… | βœ… | βœ… | βœ… | βœ… | βœ… |
605
+ | **Caching** | βœ… | ❌ | ❌ | βœ… | βœ… | ❌ | ❌ | ❌ |
606
+ | **Auto JSON** | βœ… | ❌ | βœ… | βœ… | βœ… | βœ… | ❌ | βœ… |
607
+ | **Timeout** | βœ… | ❌ | βœ… | βœ… | βœ… | βœ… | βœ… | βœ… |
608
+ | **TypeScript** | βœ… | βœ… | βœ… | βœ… | βœ… | βœ… | βœ… | βœ… |
609
+ | **Streaming** | βœ… | βœ… | βœ… | βœ… | βœ… | βœ… | βœ… | ❌ |
610
+ | **Progress** | ❌ | ❌ | βœ… | βœ… | βœ… | βœ… | ❌ | ❌ |
611
+ | **Middleware** | ❌ | ❌ | βœ… | βœ… | βœ… | βœ… | ❌ | ❌ |
612
+ | **Cookies** | βœ… | βœ… | πŸ› οΈ | βœ… | βœ… | ❌ | ❌ | ❌ |
613
+ | **Pagination API** | ❌ | ❌ | ❌ | ❌ | βœ… | ❌ | ❌ | ❌ |
614
+ | **Zero Deps** | βœ… | βœ… | ❌ | ❌ | ❌ | βœ… | βœ… | βœ… |
615
+ | **Chainable API** | βœ… | ❌ | ❌ | βœ… | βœ… | βœ… | ❌ | ❌ |
616
+ | **CSRF Protection** | βœ… | ❌ | βœ… | ❌ | ❌ | ❌ | ❌ | ❌ |
617
+
618
+ **Notes:**
619
+
620
+ - "Modern" browser support: Chrome 42+, Firefox 39+, Safari 10.1+, Edge 14+, Opera 29+
621
+ - πŸ› οΈ Feature requires additional plugins or adapters (not available out-of-the-box)
572
622
 
573
623
  ## License
574
624