@rxbenefits/mfe-api-client
v1.0.2
Published
Generic API client with mapping and React Query hooks for RxBenefits Micro Frontend applications
Maintainers
Readme
@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
npm install @rxbenefits/mfe-api-client @rxbenefits/mfe-config @tanstack/react-query axiosFeatures
- 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
// 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
// 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)
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)
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
// 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=contactsAPI Reference
Types
ApiConfig
Configuration object mapping keys to API endpoints.
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.
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.
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.
const { useApiQuery, useApiMutation } = createApiHooks(apiConfig, clientConfig);Hooks
useApiQuery(queries)
Hook for making GET requests using React Query.
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.
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
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
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
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.
// 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.
import type {
ApiConfig,
ApiClientConfig,
QueryConfig,
MutationConfig,
MutationData,
} from '@rxbenefits/mfe-api-client';License
MIT © RxBenefits
