npm package: @quarnel/httpclient
Description: Lightweight zero-dependency HTTP client built on top of `fetch` with TypeScript support
Latest version: 0.1.1

# @quarnel/httpclient

Lightweight zero-dependency HTTP client built on top of `fetch` with TypeScript support

[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg?style=flat-square)](https://opensource.org/licenses/MIT)

## Features

- Methods: GET, POST, PUT, DELETE, PATCH
- Automatic `Content-Type: application/json` for object bodies
- Supports `FormData`, `Blob`, `URLSearchParams`
- Request timeout via `AbortController`
- Automatic retries with delay
- Throw on HTTP errors (4xx/5xx), configurable
- External abort via `AbortSignal`
- `Accept: application/json` by default
- Typed headers with autocomplete (CommonHeaders)
- Custom `HttpError` with status, body & headers

## Installation

```bash
npm install @quarnel/httpclient
```

## Usage

```ts
import { HttpClient, HttpError } from '@quarnel/httpclient'

type User = {
  name: string
}

const client = new HttpClient({
  baseUrl: 'https://api.com',
  headers: {
    Authorization: 'Bearer your-token-here',
    'X-Custom-Header': 'value',
  },
  timeout: 15000, // ms
  retry: {
    attempts: 3,
    delay: 1000, // ms
  },
  throwOnHttpError: true, // default `true`
})

async function exampleFetch() {
  try {
    const res = await client.get<User>('/users', {
      query: { profile: 20, name: 'quarnel' }, // https://api.com/users?profile=20&name=quarnel
    })

    console.log(res.status) // HTTP status
    console.log(res.data.name) // typed User
    console.log(res.headers) // headers
  } catch (err) {
    if (err instanceof HttpError) {
      console.error(`HTTP ${err.status}: `, err.body)
    } else {
      console.error('Network/Abort error: ', err)
    }
  }
}
```


### POST FormData

```ts
const form = new FormData()
form.append('avatar', file)
form.append('name', 'Quarnel')

await client.post('/profile/update', form)
```

### Cancel request

```ts
const controller = new AbortController()

client.get('/longfetch', { signal: controller.signal })

controller.abort()
```

## API

- **`get<T>(url: string, config?: RequestConfig)`**
  GET request with generics to type the response

- **`post<T>(url: string, body?: any, config?: RequestConfig)`**
  POST request, automatically sets `Content-Type: application/json`

- **`put<T>`, `delete<T>`, `patch<T>`**  
  Similarly, they support body and config.

**Client configuration (HttpClientConfig):**

- `baseUrl?: string` — prefix for all URLs
- `headers?: CommonHeaders` — default headers (with auto-completion)
- `timeout?: number` — timeout in ms
- `retry?: { attempts: number; delay: number }` — repeated attempts
- `throwOnHttpError?: boolean` — throws an error at 4xx/5xx (true by default)

**Request Configuration (RequestConfig):**

- `query?:  Record<string, string | number | boolean | null>` — query parameters
- `headers?: CommonHeaders` — headers for a specific request
- `signal?: AbortSignal` — to cancel the request
- `timeout?: number` — redefining the timeout

**Response Type (HttpResponse<T>):**
```ts
export interface HttpResponse<T> {
  status: number                  // HTTP status code
  data: T                         // Parsed response body (JSON or text)
  headers: Record<string, string> // Response headers as object
}
```

**type CommonHeaders:**

```ts
export type CommonHeaders = {
  Authorization?: `Bearer ${string}` | `Basic ${string}` | string
  'X-API-Key'?: string
  'Api-Key'?: string

  'Content-Type'?: 'application/json' | 'application/x-www-form-urlencoded' | 'text/plain' | 'application/octet-stream' | string

  Accept?: 'application/json' | 'text/plain' | '*/*' | string
  'Accept-Language'?: string

  'Cache-Control'?: 'no-cache' | 'no-store' | 'max-age=0' | `max-age=${number}` | string

  'If-None-Match'?: string
  'If-Modified-Since'?: string

  Origin?: string
  Referer?: string

  'X-Requested-With'?: 'XMLHttpRequest'
  'X-CSRF-Token'?: string
  'X-Request-ID'?: string
  'X-Client-Version'?: string
  'X-App-Version'?: string

  'User-Agent'?: string
} & Record<string, string | undefined>
```

**Errors:**

- `HttpError` — thrown when `!res.ok` and `throwOnHttpError` is true
Contains:
- `status: number`
- `body: unknown (parsed JSON or text)`
- `headers: Record<string, string>`


Made with ❤️ by Quarnel