npm package: @sachin-acharya-projects/resource-client
Description: A flexible, minimal, and extendable TypeScript REST API client for any HTTP backend, compatible with Axios, Fetch, and similar clients.
Author: Sachin Acharya
Latest version: 1.0.1

# Resource Client

A flexible, minimal, and extendable TypeScript REST API client for any HTTP backend. Compatible with Axios, Fetch, and similar HTTP clients.

## Features

- Supports all standard REST operations: GET, POST, PUT, PATCH, DELETE
- Works seamlessly with Axios, Fetch, or any client following the Axios request signature
- Global default headers with per-request overrides and extend options
- Configurable trailing slash behavior
- Written in TypeScript with types included
- Minimal boilerplate for easy integration

## Installation

```bash
npm install resource-client
# or
yarn add resource-client
```

## Usage

```ts
import ResourceClient from 'resource-client';
import axios from 'axios';

const api = new ResourceClient('users', {
    client: axios,
    baseURL: 'https://api.example.com',
    defaultHeaders: {
        'Content-Type': 'application/json',
        Authorization: 'Bearer YOUR_TOKEN',
    },
    extendHeaders: true,
    trailingSlash: true,
});

// List users
const users = await api.list({ params: { page: 1 } });

// Get a user by ID
const user = await api.get('123');

// Create a user
await api.store({ name: 'John Doe' });

// Update a user
await api.update('123', { name: 'Jane Doe' });

// Delete a user
await api.destroy('123');
```

### 1. Minimal fetch adapter (to fit ResourceClient’s expected interface)

```ts
// fetchAdapter.ts
export async function fetchAdapter(config: {
    url: string;
    method: string;
    headers?: Record<string, string>;
    params?: Record<string, any>;
    data?: any;
}) {
    // Build query string from params
    const queryString = config.params ? '?' + new URLSearchParams(config.params).toString() : '';

    const response = await fetch(config.url + queryString, {
        method: config.method.toUpperCase(),
        headers: config.headers,
        body: ['GET', 'HEAD'].includes(config.method.toUpperCase())
            ? undefined
            : JSON.stringify(config.data),
    });

    if (!response.ok) {
        throw new Error(`HTTP error! status: ${response.status}`);
    }

    // Assuming JSON response
    return response.json();
}
```

---

### 2. Using ResourceClient with fetchAdapter

```ts
import ResourceClient from 'resource-client';
import { fetchAdapter } from './fetchAdapter';

const api = new ResourceClient('posts', {
    client: fetchAdapter,
    baseURL: 'https://api.example.com',
    defaultHeaders: {
        'Content-Type': 'application/json',
    },
    extendHeaders: true,
    trailingSlash: true,
});

async function run() {
    // List posts (with optional query)
    const posts = await api.list({ params: { page: 1, limit: 10 } });
    console.log(posts);

    // Get a post by ID
    const post = await api.get('42');
    console.log(post);

    // Create a new post
    const newPost = await api.store({
        title: 'Hello world',
        body: 'This is a test.',
    });
    console.log(newPost);

    // Update a post fully
    const updatedPost = await api.update('42', {
        title: 'Updated title',
        body: 'Updated body',
    });
    console.log(updatedPost);

    // Patch a post partially
    const patchedPost = await api.patch('42', {
        title: 'Partially updated title',
    });
    console.log(patchedPost);

    // Delete a post
    await api.destroy('42');
    console.log('Deleted post 42');
}

run().catch(console.error);
```

---

### Notes:

- `fetchAdapter` converts `params` into query string automatically.
- It serializes the body as JSON for non-GET/HEAD requests.
- Throws an error on non-2xx responses.
- You can customize `fetchAdapter` if your API expects different formats (e.g., form data).

## API

| Method                       | Description                        | Params                                   |
| ---------------------------- | ---------------------------------- | ---------------------------------------- |
| `list(query?, options?)`     | Fetch a list of resources          | Query params and request options         |
| `get(id, options?)`          | Fetch a single resource by ID      | ID and request options                   |
| `store(data, options?)`      | Create a new resource              | Payload and request options              |
| `update(id, data, options?)` | Replace an existing resource by ID | ID, payload, and request options         |
| `patch(id, data, options?)`  | Partially update a resource by ID  | ID, partial payload, and request options |
| `destroy(id, options?)`      | Delete a resource by ID            | ID and request options                   |

## Configuration Options

| Option           | Type        | Default | Description                                              |
| ---------------- | ----------- | ------- | -------------------------------------------------------- |
| `client`         | HTTP client | —       | HTTP client instance (e.g., axios)                       |
| `baseURL`        | string      | —       | Base URL for all requests                                |
| `defaultHeaders` | Record      | `{}`    | Headers to include in every request                      |
| `extendHeaders`  | boolean     | `true`  | Whether to merge per-request headers with defaultHeaders |
| `trailingSlash`  | boolean     | `true`  | Whether URLs should end with a slash                     |

## License

MIT © Sachin Acharya