npm package: @philiprehberger/http-client
Description: Type-safe fetch wrapper with interceptors, retries, and base URL support
Author: Philip Rehberger
Latest version: 0.4.6

# @philiprehberger/http-client

[![CI](https://github.com/philiprehberger/ts-http-client/actions/workflows/ci.yml/badge.svg)](https://github.com/philiprehberger/ts-http-client/actions/workflows/ci.yml)
[![npm version](https://img.shields.io/npm/v/@philiprehberger/http-client.svg)](https://www.npmjs.com/package/@philiprehberger/http-client)
[![Last updated](https://img.shields.io/github/last-commit/philiprehberger/ts-http-client)](https://github.com/philiprehberger/ts-http-client/commits/main)

Type-safe fetch wrapper with interceptors, retries, and base URL support

## Installation

```bash
npm install @philiprehberger/http-client
```

## Usage

### Basic

```ts
import { createClient } from '@philiprehberger/http-client';

const api = createClient({
  baseURL: 'https://api.example.com',
  timeout: 5000,
});

const user = await api.get<User>('/users/1');
const created = await api.post<User>('/users', { body: { name: 'Alice' } });
await api.put<User>('/users/1', { body: { name: 'Bob' } });
await api.patch<User>('/users/1', { body: { name: 'Bob' } });
await api.delete('/users/1');
await api.head('/users/1');
await api.options('/users');
```

### Query Parameters

```ts
const users = await api.get<User[]>('/users', {
  params: { page: 2, limit: 10, active: true },
});
// GET /users?page=2&limit=10&active=true
```

### Auth

```ts
const api = createClient({
  baseURL: 'https://api.example.com',
  auth: { type: 'bearer', token: 'my-token' },
});

// Or basic auth:
const api = createClient({
  baseURL: 'https://api.example.com',
  auth: { type: 'basic', username: 'user', password: 'pass' },
});
```

### Retry

```ts
const api = createClient({
  baseURL: 'https://api.example.com',
  retry: {
    maxAttempts: 3,
    retryOn: [500, 502, 503],
    backoff: 1000,
    backoffStrategy: 'exponential', // default, or 'linear'
    jitter: true,                   // add randomized variation
    maxBackoff: 30000,              // cap maximum wait
  },
});
```

### Interceptors

```ts
// Request interceptor
api.onRequest((req) => {
  req.headers.set('X-Request-Id', crypto.randomUUID());
  return req;
});

// Response interceptor
api.onResponse((res) => {
  console.log(`${res.url} → ${res.status} (${res.duration}ms)`);
  return res;
});
```

### Timeout

```ts
// Default timeout for all requests
const api = createClient({ timeout: 5000 });

// Per-request override
await api.get('/slow-endpoint', { timeout: 30000 });
```

### Response Type

```ts
// Force response parsing regardless of Content-Type header
const html = await api.get<string>('/page', { responseType: 'text' });
const data = await api.get<Blob>('/file', { responseType: 'blob' });
const buf = await api.get<ArrayBuffer>('/binary', { responseType: 'arrayBuffer' });
const json = await api.get<MyType>('/data', { responseType: 'json' });
```

### Abort Signal

```ts
const controller = new AbortController();
await api.get('/data', { signal: controller.signal });
controller.abort();
```

### Error Handling

```ts
import { HttpError } from '@philiprehberger/http-client';

try {
  await api.get('/not-found');
} catch (error) {
  if (error instanceof HttpError) {
    error.status;     // 404
    error.statusText; // 'Not Found'
    error.url;        // full URL
    error.body;       // parsed response body
  }
}
```

## API

### `createClient(options?: ClientOptions)`

Returns an HTTP client with the following methods:

| Method | Signature | Description |
|--------|-----------|-------------|
| `get` | `<T>(path, opts?) => Promise<T>` | GET request. |
| `post` | `<T>(path, opts?) => Promise<T>` | POST request. |
| `put` | `<T>(path, opts?) => Promise<T>` | PUT request. |
| `patch` | `<T>(path, opts?) => Promise<T>` | PATCH request. |
| `delete` | `<T>(path, opts?) => Promise<T>` | DELETE request. |
| `head` | `<T>(path, opts?) => Promise<T>` | HEAD request. |
| `options` | `<T>(path, opts?) => Promise<T>` | OPTIONS request. |
| `onRequest` | `(interceptor) => void` | Add a request interceptor. |
| `onResponse` | `(interceptor) => void` | Add a response interceptor. |

### `ClientOptions`

| Property | Type | Description |
|----------|------|-------------|
| `baseURL` | `string` | Prepended to all request paths. |
| `headers` | `Record<string, string>` | Default headers for all requests. |
| `timeout` | `number` | Default timeout in ms. |
| `retry` | `RetryConfig` | Retry configuration. |
| `auth` | `AuthConfig` | Auth configuration (bearer or basic). |

### `RetryConfig`

| Property | Type | Default | Description |
|----------|------|---------|-------------|
| `maxAttempts` | `number` | `1` | Total attempts (1 = no retry). |
| `retryOn` | `number[] \| (status) => boolean` | None | HTTP status codes to retry on. |
| `backoff` | `number` | `1000` | Base backoff in ms. |
| `backoffStrategy` | `'linear' \| 'exponential'` | `'exponential'` | Backoff growth strategy. |
| `jitter` | `boolean` | `false` | Add random variation to backoff. |
| `maxBackoff` | `number` | `30000` | Maximum backoff cap in ms. |

### `RequestOptions`

| Property | Type | Description |
|----------|------|-------------|
| `headers` | `Record<string, string>` | Per-request headers (merged with defaults). |
| `params` | `Record<string, string \| number \| boolean>` | Query parameters. |
| `body` | `unknown` | Request body (auto-serialized to JSON). |
| `timeout` | `number` | Per-request timeout override. |
| `signal` | `AbortSignal` | Abort signal for cancellation. |
| `responseType` | `'json' \| 'text' \| 'blob' \| 'arrayBuffer'` | Force response parsing method (overrides Content-Type auto-detect). |

### `HttpError`

| Property | Type | Description |
|----------|------|-------------|
| `status` | `number` | HTTP status code. |
| `statusText` | `string` | HTTP status text. |
| `url` | `string` | Request URL. |
| `body` | `unknown` | Parsed response body. |

## Development

```bash
npm install
npm run build
npm test
```

## Support

If you find this project useful:

⭐ [Star the repo](https://github.com/philiprehberger/ts-http-client)

🐛 [Report issues](https://github.com/philiprehberger/ts-http-client/issues?q=is%3Aissue+is%3Aopen+label%3Abug)

💡 [Suggest features](https://github.com/philiprehberger/ts-http-client/issues?q=is%3Aissue+is%3Aopen+label%3Aenhancement)

❤️ [Sponsor development](https://github.com/sponsors/philiprehberger)

🌐 [All Open Source Projects](https://philiprehberger.com/open-source-packages)

💻 [GitHub Profile](https://github.com/philiprehberger)

🔗 [LinkedIn Profile](https://www.linkedin.com/in/philiprehberger)

## License

[MIT](LICENSE)