ministry-platform-provider
Version:
TypeScript client library for Ministry Platform API integration
351 lines (263 loc) • 9.44 kB
Markdown
# HttpClient - Low-Level HTTP Operations
## Overview
The `HttpClient` class provides low-level HTTP operations for communicating with the Ministry Platform API. It handles request/response cycles, authentication headers, query parameter serialization, and error handling.
## Class Structure
```typescript
export class HttpClient {
private baseUrl: string;
private getToken: () => string;
constructor(baseUrl: string, getToken: () => string)
}
```
## Constructor Parameters
- **baseUrl**: The base URL for the Ministry Platform API (e.g., `https://your-instance.ministryplatform.com`)
- **getToken**: A function that returns the current authentication token
## Methods
### GET Requests
```typescript
async get<T = unknown>(endpoint: string, queryParams?: QueryParams): Promise<T>
```
**Purpose**: Performs HTTP GET requests with automatic authentication
**Parameters**:
- `endpoint`: API endpoint path (e.g., `/tables/Contacts`)
- `queryParams`: Optional query parameters object
**Returns**: Parsed JSON response typed as `T`
**Example**:
```typescript
const contacts = await httpClient.get<Contact[]>('/tables/Contacts', {
$filter: 'Contact_Status_ID=1',
$select: 'Contact_ID,Display_Name',
$top: 50
});
```
**Headers Set**:
- `Authorization: Bearer ${token}`
- `Accept: application/json`
### POST Requests
```typescript
async post<T = unknown>(endpoint: string, body?: RequestBody, queryParams?: QueryParams): Promise<T>
```
**Purpose**: Performs HTTP POST requests with JSON body
**Parameters**:
- `endpoint`: API endpoint path
- `body`: Optional request body object
- `queryParams`: Optional query parameters
**Returns**: Parsed JSON response typed as `T`
**Example**:
```typescript
const newContact = await httpClient.post<Contact>('/tables/Contacts', {
First_Name: 'John',
Last_Name: 'Doe',
Email_Address: 'john@example.com'
});
```
**Headers Set**:
- `Authorization: Bearer ${token}`
- `Content-Type: application/json`
- `Accept: application/json`
### POST with Form Data
```typescript
async postFormData<T = unknown>(endpoint: string, formData: FormData, queryParams?: QueryParams): Promise<T>
```
**Purpose**: Performs HTTP POST requests with multipart form data (for file uploads)
**Parameters**:
- `endpoint`: API endpoint path
- `formData`: FormData object containing files and data
- `queryParams`: Optional query parameters
**Returns**: Parsed JSON response typed as `T`
**Example**:
```typescript
const formData = new FormData();
formData.append('file-0', file, file.name);
formData.append('description', 'Profile photo');
const uploadResult = await httpClient.postFormData<FileDescription[]>(
'/files/Contacts/12345',
formData
);
```
**Headers Set**:
- `Authorization: Bearer ${token}`
- `Accept: application/json`
- Content-Type is automatically set by the browser for FormData
### PUT Requests
```typescript
async put<T = unknown>(endpoint: string, body: RequestBody, queryParams?: QueryParams): Promise<T>
```
**Purpose**: Performs HTTP PUT requests for updating resources
**Parameters**:
- `endpoint`: API endpoint path
- `body`: Request body object (required for PUT)
- `queryParams`: Optional query parameters
**Returns**: Parsed JSON response typed as `T`
**Example**:
```typescript
const updatedContact = await httpClient.put<Contact>('/tables/Contacts', {
Contact_ID: 12345,
First_Name: 'John',
Last_Name: 'Smith',
Email_Address: 'john.smith@example.com'
});
```
**Headers Set**:
- `Authorization: Bearer ${token}`
- `Content-Type: application/json`
- `Accept: application/json`
### PUT with Form Data
```typescript
async putFormData<T = unknown>(endpoint: string, formData: FormData, queryParams?: QueryParams): Promise<T>
```
**Purpose**: Performs HTTP PUT requests with multipart form data (for file updates)
**Parameters**:
- `endpoint`: API endpoint path
- `formData`: FormData object containing files and data
- `queryParams`: Optional query parameters
**Returns**: Parsed JSON response typed as `T`
**Example**:
```typescript
const formData = new FormData();
formData.append('file', newFile, newFile.name);
formData.append('description', 'Updated profile photo');
const updateResult = await httpClient.putFormData<FileDescription>(
'/files/67890',
formData
);
```
### DELETE Requests
```typescript
async delete<T = unknown>(endpoint: string, queryParams?: QueryParams): Promise<T>
```
**Purpose**: Performs HTTP DELETE requests
**Parameters**:
- `endpoint`: API endpoint path
- `queryParams`: Optional query parameters (often contains IDs to delete)
**Returns**: Parsed JSON response typed as `T`
**Example**:
```typescript
const deletedContacts = await httpClient.delete<Contact[]>('/tables/Contacts', {
id: [12345, 67890],
$userId: 1
});
```
**Headers Set**:
- `Authorization: Bearer ${token}`
- `Accept: application/json`
## Utility Methods
### buildUrl
```typescript
public buildUrl(endpoint: string, queryParams?: QueryParams): string
```
**Purpose**: Constructs full URLs with query parameters
**Parameters**:
- `endpoint`: API endpoint path
- `queryParams`: Optional query parameters object
**Returns**: Complete URL string
**Example**:
```typescript
const url = httpClient.buildUrl('/tables/Contacts', {
$filter: 'Contact_Status_ID=1',
$select: 'Contact_ID,Display_Name'
});
// Returns: https://your-instance.ministryplatform.com/tables/Contacts?$filter=Contact_Status_ID%3D1&$select=Contact_ID%2CDisplay_Name
```
### buildQueryString (Private)
```typescript
private buildQueryString(params: QueryParams): string
```
**Purpose**: Converts query parameters object to URL-encoded string
**Features**:
- Handles arrays by repeating the parameter name
- URL-encodes all values
- Filters out null/undefined values
- Proper formatting for Ministry Platform API
**Example**:
```typescript
// Input: { $filter: 'Name=John', ids: [1, 2, 3], $top: 10 }
// Output: "$filter=Name%3DJohn&ids=1&ids=2&ids=3&$top=10"
```
## Error Handling
All HTTP methods include comprehensive error handling:
```typescript
if (!response.ok) {
throw new Error(`${METHOD} ${endpoint} failed: ${response.status} ${response.statusText}`);
}
```
**Error Information Included**:
- HTTP method used
- Endpoint that failed
- HTTP status code
- Status text description
**Example Error**:
```
Error: GET /tables/Contacts failed: 401 Unauthorized
Error: POST /tables/Contacts failed: 400 Bad Request
```
## Usage Patterns
### Basic GET Request
```typescript
const httpClient = new HttpClient(baseUrl, () => token);
try {
const data = await httpClient.get<Contact[]>('/tables/Contacts', {
$filter: 'Contact_Status_ID=1',
$orderby: 'Last_Name,First_Name'
});
console.log('Contacts:', data);
} catch (error) {
console.error('Failed to fetch contacts:', error);
}
```
### File Upload
```typescript
const formData = new FormData();
formData.append('file-0', file, file.name);
formData.append('description', 'Document upload');
try {
const result = await httpClient.postFormData<FileDescription[]>(
`/files/Contacts/${contactId}`,
formData,
{ $userId: userId }
);
console.log('Upload successful:', result);
} catch (error) {
console.error('Upload failed:', error);
}
```
### Batch Operations
```typescript
// Create multiple records
const records = [
{ First_Name: 'John', Last_Name: 'Doe' },
{ First_Name: 'Jane', Last_Name: 'Smith' }
];
try {
const created = await httpClient.post<Contact[]>('/tables/Contacts', records);
console.log('Created contacts:', created);
} catch (error) {
console.error('Batch creation failed:', error);
}
```
## Best Practices
1. **Always Use Generic Types**: Specify return types for better TypeScript support
2. **Handle Errors**: Wrap all HTTP calls in try-catch blocks
3. **URL Encoding**: Let the class handle URL encoding automatically
4. **Query Parameters**: Use the queryParams parameter instead of manual URL construction
5. **Form Data**: Use postFormData/putFormData for file uploads, not regular post/put
## Common Pitfalls
1. **Manual URL Construction**: Don't concatenate URLs manually - use the queryParams parameter
2. **Content-Type Headers**: Don't set Content-Type for FormData - let the browser handle it
3. **Token Management**: Don't pass tokens directly - use the getToken function
4. **Error Handling**: Always check response.ok before processing response data
## Integration with Higher-Level Services
The HttpClient is used by the MinistryPlatformClient, which in turn is used by all service classes:
```typescript
// In MinistryPlatformClient
constructor() {
this.httpClient = new HttpClient(this.baseUrl, () => this.token);
}
// In Services
public async getTableRecords<T>(table: string, params?: TableQueryParams): Promise<T[]> {
await this.client.ensureValidToken();
const endpoint = `/tables/${encodeURIComponent(table)}`;
return await this.client.getHttpClient().get<T[]>(endpoint, params);
}
```
This layered approach ensures consistent authentication, error handling, and request formatting across all Ministry Platform operations.