npm package: @rxbenefits/mfe-api-client
Description: Generic API client with mapping and React Query hooks for RxBenefits Micro Frontend applications
Author: RxBenefits
Latest version: 1.0.2

# @rxbenefits/mfe-api-client

Generic API client with mapping and React Query hooks for RxBenefits Micro Frontend applications. Provides a type-safe, reusable pattern for API calls with automatic cookie isolation support.

## Installation

```bash
npm install @rxbenefits/mfe-api-client @rxbenefits/mfe-config @tanstack/react-query axios
```

## Features

- **API Configuration Mapping**: Define all API endpoints in one place
- **Type-Safe**: Full TypeScript support with inference
- **React Query Integration**: Built-in hooks for queries and mutations
- **Cookie Isolation**: Automatic 127.0.0.1 origin in integrated mode
- **Authentication**: Automatic token management
- **Interceptors**: Request, response, and error interceptors
- **Flexible**: Works with any backend API

## Quick Start

### 1. Define Your API Configuration

```typescript
// api/apiConfig.ts
import type { ApiConfig } from '@rxbenefits/mfe-api-client';

export const apiConfig: ApiConfig = {
  organizations: {
    api: '/api/ben-admin/organizations',
    method: 'GET',
  },
  organization: {
    api: (id: string) => `/api/ben-admin/organization/${id}`,
    method: 'GET',
  },
  createOrganization: {
    api: '/api/ben-admin/organization',
    method: 'POST',
  },
  updateOrganization: {
    api: (id: string) => `/api/ben-admin/organization/${id}`,
    method: 'PUT',
  },
  deleteOrganization: {
    api: (id: string) => `/api/ben-admin/organization/${id}`,
    method: 'DELETE',
  },
};
```

### 2. Create Your API Client Hook

```typescript
// hooks/useApi.ts
import { createApiHooks } from '@rxbenefits/mfe-api-client';
import { apiConfig } from '../api/apiConfig';
import { useMfeAuth } from '../contexts/MfeAuthContext';

export function useApi() {
  const { getAccessToken } = useMfeAuth();

  const { useApiQuery, useApiMutation } = createApiHooks(apiConfig, {
    mfePort: 3001,
    getAccessToken,
    defaultHeaders: {
      'Content-Type': 'application/json',
    },
    onRequest: async (config) => {
      console.log('API Request:', config.method, config.url);
    },
    onError: async (error) => {
      console.error('API Error:', error);
    },
  });

  return {
    useApiQuery,
    useApiMutation,
  };
}
```

### 3. Use in Your Components

#### Queries (GET requests)

```typescript
import { useApi } from '../hooks/useApi';

function OrganizationsList() {
  const { useApiQuery } = useApi();

  // Single query
  const { organizations, isLoading, isError, refetch } = useApiQuery([
    {
      key: 'organizations',
      queryParams: { limit: 10 },
    },
  ]);

  // Multiple queries in parallel
  const { organizations, organization, isLoading } = useApiQuery([
    { key: 'organizations' },
    {
      key: 'organization',
      pathParams: { id: '123' },
    },
  ]);

  if (isLoading) return <div>Loading...</div>;
  if (isError) return <div>Error loading data</div>;

  return (
    <div>
      {organizations.map((org) => (
        <div key={org.id}>{org.name}</div>
      ))}
    </div>
  );
}
```

#### Mutations (POST, PUT, DELETE, PATCH)

```typescript
import { useApi } from '../hooks/useApi';

function CreateOrganizationForm() {
  const { useApiMutation } = useApi();

  const createMutation = useApiMutation('createOrganization', 'POST', {
    method: 'POST',
    options: {
      onSuccess: (data) => {
        console.log('Organization created:', data);
      },
      onError: (error) => {
        console.error('Failed to create:', error);
      },
    },
  });

  const handleSubmit = (formData) => {
    createMutation.mutate({
      name: formData.name,
      type: formData.type,
    });
  };

  return (
    <form onSubmit={handleSubmit}>
      {/* form fields */}
      <button type="submit" disabled={createMutation.isLoading}>
        Create
      </button>
    </form>
  );
}
```

#### Dynamic Parameters

```typescript
// Path parameters (for dynamic URLs)
const { organization } = useApiQuery([
  {
    key: 'organization',
    pathParams: { id: orgId }, // Used in URL: /api/organization/{id}
  },
]);

// Query parameters (for query strings)
const { organizations } = useApiQuery([
  {
    key: 'organizations',
    queryParams: { limit: 10, offset: 0 }, // Results in: ?limit=10&offset=0
  },
]);

// Both together
const { organization } = useApiQuery([
  {
    key: 'organization',
    pathParams: { id: orgId },
    queryParams: { include: 'contacts' },
  },
]);
// Results in: /api/organization/123?include=contacts
```

## API Reference

### Types

#### `ApiConfig`

Configuration object mapping keys to API endpoints.

```typescript
interface ApiConfig {
  [key: string]: {
    api: string | ((...args: any[]) => string);
    method?: 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH';
    metadata?: Record<string, any>;
  };
}
```

#### `ApiClientConfig`

Configuration for the API client.

```typescript
interface ApiClientConfig {
  mfePort?: number;
  getAccessToken: () => Promise<string | null>;
  defaultHeaders?: Record<string, string>;
  onRequest?: (config: { url: string; method: string; headers: Record<string, string> }) => void | Promise<void>;
  onResponse?: (response: { data: any; status: number }) => void | Promise<void>;
  onError?: (error: Error) => void | Promise<void>;
}
```

### Functions

#### `createApiClient(apiConfig, clientConfig)`

Creates a configured API client with methods for making requests.

```typescript
const client = createApiClient(apiConfig, {
  mfePort: 3001,
  getAccessToken,
});

// Use client methods
const data = await client.get('organizations');
const created = await client.post('createOrganization', { name: 'New Org' });
const updated = await client.put('updateOrganization', { name: 'Updated' }, { id: '123' });
await client.delete('deleteOrganization', { id: '123' });
```

#### `createApiHooks(apiConfig, clientConfig)`

Creates React Query hooks for queries and mutations.

```typescript
const { useApiQuery, useApiMutation } = createApiHooks(apiConfig, clientConfig);
```

### Hooks

#### `useApiQuery(queries)`

Hook for making GET requests using React Query.

```typescript
const { data, isLoading, isFetching, isError, refetch } = useApiQuery([
  {
    key: 'organizations',
    pathParams: {},
    queryParams: {},
    options: {
      enabled: true,
      staleTime: 0,
      cacheTime: 0,
      refetchOnMount: true,
      refetchOnWindowFocus: false,
    },
  },
]);
```

#### `useApiMutation(key, method, config)`

Hook for making POST, PUT, DELETE, or PATCH requests using React Query.

```typescript
const mutation = useApiMutation('createOrganization', 'POST', {
  method: 'POST',
  options: {
    onSuccess: (data) => console.log(data),
    onError: (error) => console.error(error),
  },
});

// Use mutation
mutation.mutate({ name: 'New Org' });

// With path/query params
mutation.mutate({
  pathParams: { id: '123' },
  queryParams: { validate: true },
  name: 'Updated Org',
});
```

## Advanced Usage

### Custom Request Interceptor

```typescript
const { useApiQuery, useApiMutation } = createApiHooks(apiConfig, {
  mfePort: 3001,
  getAccessToken,
  onRequest: async (config) => {
    // Add custom headers
    config.headers['X-Custom-Header'] = 'value';

    // Log requests
    console.log(`${config.method} ${config.url}`);

    // Modify request based on conditions
    if (config.url.includes('/sensitive')) {
      config.headers['X-Extra-Auth'] = 'token';
    }
  },
});
```

### Error Handling

```typescript
const { useApiQuery, useApiMutation } = createApiHooks(apiConfig, {
  mfePort: 3001,
  getAccessToken,
  onError: async (error) => {
    // Log to monitoring service
    if (error.response?.status === 401) {
      // Handle unauthorized
      window.location.href = '/login';
    } else if (error.response?.status >= 500) {
      // Log server errors
      console.error('Server error:', error);
    }
  },
});
```

### Response Transformation

```typescript
const { useApiQuery } = createApiHooks(apiConfig, {
  mfePort: 3001,
  getAccessToken,
  onResponse: async (response) => {
    // Transform or validate response
    if (response.data.deprecationWarning) {
      console.warn('API deprecation:', response.data.deprecationWarning);
    }
  },
});
```

## Integration with @rxbenefits/mfe-config

This package automatically uses `@rxbenefits/mfe-config` for cookie isolation. The `buildFullUrl` function uses `getMfeApiOrigin` to determine whether to use 127.0.0.1 or localhost.

```typescript
// In integrated mode (shell on port 3000), API calls automatically use:
// http://127.0.0.1:3001/api/... (avoids cookie collisions)

// In standalone mode, API calls use:
// /api/... (relative URLs, same-origin)
```

## TypeScript

This package is written in TypeScript and provides full type inference.

```typescript
import type {
  ApiConfig,
  ApiClientConfig,
  QueryConfig,
  MutationConfig,
  MutationData,
} from '@rxbenefits/mfe-api-client';
```

## License

MIT © RxBenefits