|
3 | 3 |
|
4 | 4 | FetchClient is a library that makes it easier to use the fetch API for JSON APIs. It provides the following features:
|
5 | 5 |
|
6 |
| - - [Typed Response](#typed-response) |
7 |
| - - [Functional Typed Response](#functional-typed-response) |
8 |
| - - [Model Validator](#model-validator) |
9 |
| - - [Caching](#caching) |
10 |
| - - [Middleware](#middleware) |
11 |
| - - [Rate Limiting](#rate-limiting) |
| 6 | +- [Typed Response](#typed-response) - Full TypeScript support with strongly typed responses |
| 7 | +- [Functional](#functional) - Standalone functions for simple usage |
| 8 | +- [Model Validator](#model-validator) - Built-in validation with Problem Details support |
| 9 | +- [Caching](#caching) - Response caching with TTL and programmatic invalidation |
| 10 | +- [Middleware](#middleware) - Extensible middleware pipeline for request/response handling |
| 11 | +- [Rate Limiting](#rate-limiting) - Built-in rate limiting with per-domain support |
| 12 | +- [Request Timeout](#request-timeout) - Configurable timeouts with AbortSignal support |
| 13 | +- [Error Handling](#error-handling) - Comprehensive error handling with Problem Details |
| 14 | +- [Authentication](#authentication) - Built-in Bearer token support |
| 15 | +- [Base URL](#base-url) - Global base URL configuration |
| 16 | +- [Loading State](#loading-state) - Track request loading state with events |
12 | 17 |
|
13 | 18 | ## Install
|
14 | 19 |
|
@@ -39,20 +44,24 @@ const response = await client.getJSON<Products>(
|
39 | 44 | const products = response.data;
|
40 | 45 | ```
|
41 | 46 |
|
42 |
| -### Functional Typed Response |
| 47 | +### Functional |
43 | 48 |
|
44 | 49 | ```ts
|
45 |
| -import { getJSON } from '@exceptionless/fetchclient'; |
| 50 | +import { postJSON, getJSON } from '@exceptionless/fetchclient'; |
46 | 51 |
|
47 |
| -type Products = { |
48 |
| - products: Array<{ id: number; name: string }>; |
49 |
| -}; |
| 52 | +type Product = { id: number; title: string }; |
| 53 | +type Products = { products: Product[] }; |
50 | 54 |
|
51 |
| -const response = await getJSON<Products>( |
52 |
| - `https://dummyjson.com/products/search?q=iphone&limit=10`, |
| 55 | +const response = await postJSON<Product>( |
| 56 | + "https://dummyjson.com/products/add", |
| 57 | + { |
| 58 | + name: "iPhone 13", |
| 59 | + }, |
53 | 60 | );
|
54 | 61 |
|
55 |
| -const products = response.data; |
| 62 | +const product = await getJSON<Product>( |
| 63 | + `https://dummyjson.com/products/${response.data!.id}`, |
| 64 | +); |
56 | 65 | ```
|
57 | 66 |
|
58 | 67 | ### Model Validator
|
@@ -147,6 +156,104 @@ const response = await client.getJSON(
|
147 | 156 | );
|
148 | 157 | ```
|
149 | 158 |
|
| 159 | +### Request Timeout |
| 160 | + |
| 161 | +```ts |
| 162 | +import { FetchClient } from '@exceptionless/fetchclient'; |
| 163 | + |
| 164 | +const client = new FetchClient(); |
| 165 | + |
| 166 | +// Set timeout for individual requests |
| 167 | +const response = await client.getJSON( |
| 168 | + `https://api.example.com/data`, |
| 169 | + { timeout: 5000 } // 5 seconds |
| 170 | +); |
| 171 | + |
| 172 | +// Use AbortSignal for cancellation |
| 173 | +const controller = new AbortController(); |
| 174 | +setTimeout(() => controller.abort(), 1000); |
| 175 | + |
| 176 | +const response2 = await client.getJSON( |
| 177 | + `https://api.example.com/data`, |
| 178 | + { signal: controller.signal } |
| 179 | +); |
| 180 | +``` |
| 181 | + |
| 182 | +### Error Handling |
| 183 | + |
| 184 | +```ts |
| 185 | +import { FetchClient } from '@exceptionless/fetchclient'; |
| 186 | + |
| 187 | +const client = new FetchClient(); |
| 188 | + |
| 189 | +try { |
| 190 | + const response = await client.getJSON(`https://api.example.com/data`); |
| 191 | +} catch (error) { |
| 192 | + // Handle HTTP errors (4xx, 5xx) |
| 193 | + if (error.problem) { |
| 194 | + console.log(error.problem.title); |
| 195 | + console.log(error.problem.detail); |
| 196 | + } |
| 197 | +} |
| 198 | + |
| 199 | +// Or handle specific status codes |
| 200 | +const response = await client.getJSON( |
| 201 | + `https://api.example.com/data`, |
| 202 | + { |
| 203 | + expectedStatusCodes: [404, 500], |
| 204 | + errorCallback: (response) => { |
| 205 | + if (response.status === 404) { |
| 206 | + console.log('Resource not found'); |
| 207 | + return true; // Don't throw |
| 208 | + } |
| 209 | + } |
| 210 | + } |
| 211 | +); |
| 212 | +``` |
| 213 | + |
| 214 | +### Authentication |
| 215 | + |
| 216 | +```ts |
| 217 | +import { FetchClient, setAccessTokenFunc } from '@exceptionless/fetchclient'; |
| 218 | + |
| 219 | +// Set global access token function |
| 220 | +setAccessTokenFunc(() => localStorage.getItem('token')); |
| 221 | + |
| 222 | +const client = new FetchClient(); |
| 223 | +const response = await client.getJSON(`https://api.example.com/data`); |
| 224 | +// Automatically adds Authorization: Bearer <token> header |
| 225 | +``` |
| 226 | + |
| 227 | +### Base URL |
| 228 | + |
| 229 | +```ts |
| 230 | +import { FetchClient, setBaseUrl } from '@exceptionless/fetchclient'; |
| 231 | + |
| 232 | +// Set global base URL |
| 233 | +setBaseUrl('https://api.example.com'); |
| 234 | + |
| 235 | +const client = new FetchClient(); |
| 236 | +const response = await client.getJSON(`/users/123`); |
| 237 | +// Requests to https://api.example.com/users/123 |
| 238 | +``` |
| 239 | + |
| 240 | +### Loading State |
| 241 | + |
| 242 | +```ts |
| 243 | +import { FetchClient } from '@exceptionless/fetchclient'; |
| 244 | + |
| 245 | +const client = new FetchClient(); |
| 246 | + |
| 247 | +// Track loading state |
| 248 | +client.loading.on((isLoading) => { |
| 249 | + console.log(`Loading: ${isLoading}`); |
| 250 | +}); |
| 251 | + |
| 252 | +// Check current loading state |
| 253 | +console.log(client.isLoading); |
| 254 | +console.log(client.requestCount); |
| 255 | +``` |
| 256 | + |
150 | 257 | Also, take a look at the tests:
|
151 | 258 |
|
152 | 259 | [FetchClient Tests](src/FetchClient.test.ts)
|
|
0 commit comments