HTTP Methods & Response Parsing
Deep dive into HTTP method handling, automatic content-type detection, and response parsing strategies. Learn how Caller processes different data formats.
Overview
Caller supports all standard HTTP methods and automatically handles response parsing based on content type. You don't need to manually call .json() or .text()—Caller detects the response format and deserializes it appropriately.
This automatic handling covers JSON, XML, plain text, form data, and binary responses. For cases where automatic detection isn't enough, you have full control over response parsing.
HTTP Methods
Every HTTP method has specific semantics. Understanding these helps you build correct, RESTful APIs:
| Method | Purpose | Body | Idempotent | Safe |
|---|---|---|---|---|
| GET | Retrieve data | No | Yes | Yes |
| POST | Create resource | Yes | No | No |
| PUT | Replace resource | Yes | Yes | No |
| PATCH | Update resource | Yes | No | No |
| DELETE | Remove resource | Optional | Yes | No |
| HEAD | Get headers only | No | Yes | Yes |
| OPTIONS | Check capabilities | No | Yes | Yes |
Automatic Response Parsing
Caller automatically parses responses based on the Content-Type header:
| Content-Type | Parsing | Result Type |
|---|---|---|
application/json | JSON.parse() | Object/Array |
text/plain | Raw string | string |
text/html | Raw string | string |
application/xml | Parse to object | Object (if parser available) |
multipart/form-data | FormData | FormData |
application/octet-stream | Blob | Blob |
image/*, video/*, audio/* | Blob | Blob |
// JSON response - automatically parsed
const { data } = await api.get('/api/users').execute();
// data is already a JavaScript object
// Text response - returned as string
const { data } = await api.get('/health').execute();
// data is "OK" (string)
// Binary response - returned as Blob
const { data } = await api.get('/files/123/download').execute();
// data is a BlobExplicit Response Types
When automatic detection isn't sufficient, explicitly specify the response type:
Response Object
Every request returns a standardized response object:
interface CallerResponse<T> {
data: T | null; // Parsed response body
error: CallerError | null; // Error object if failed
status: number; // HTTP status code
headers: Headers; // Response headers
isStale: boolean; // True if from stale cache
}Working with Headers
const { data, headers } = await api.get('/users').execute();
// Read specific headers
const rateLimitRemaining = headers.get('X-RateLimit-Remaining');
const etag = headers.get('ETag');
// Check for pagination headers
const totalCount = parseInt(headers.get('X-Total-Count') || '0');
const nextPage = headers.get('Link')?.match(/<([^>]+)>; rel="next"/)?.[1];Status Code Handling
const { data, error, status } = await api.post('/users')
.body(userData)
.execute();
switch (status) {
case 200:
console.log('Updated existing user');
break;
case 201:
console.log('Created new user:', data.id);
break;
case 400:
console.error('Validation failed:', error?.message);
break;
case 401:
redirectToLogin();
break;
case 403:
console.error('Not authorized');
break;
case 404:
console.error('Resource not found');
break;
case 409:
console.error('Conflict - resource already exists');
break;
case 422:
console.error('Unprocessable entity:', error?.details);
break;
case 429:
console.error('Rate limited - retry after', error?.retryAfter);
break;
case 500:
case 502:
case 503:
console.error('Server error - please try again');
break;
}Content-Type Handling
Request Content Types
Caller automatically sets the Content-Type header based on the body:
| Body Type | Auto Content-Type |
|---|---|
| Object/Array | application/json |
| FormData | multipart/form-data |
| URLSearchParams | application/x-www-form-urlencoded |
| string | text/plain |
| Blob | Blob's type or application/octet-stream |
Override the automatic detection when needed:
// Send XML
await api.post('/soap-api')
.headers({ 'Content-Type': 'application/xml' })
.body('<request><data>value</data></request>')
.execute();
// Send form-urlencoded
const params = new URLSearchParams();
params.append('username', 'john');
params.append('password', 'secret');
await api.post('/login')
.body(params)
.execute();File Uploads
// Single file
const formData = new FormData();
formData.append('file', fileInput.files[0]);
formData.append('description', 'My document');
const { data } = await api.post('/upload')
.body(formData)
.execute();
// Multiple files
const formData = new FormData();
for (const file of fileInput.files) {
formData.append('files', file);
}
const { data } = await api.post('/upload/multiple')
.body(formData)
.execute();Error Responses
When a request fails (status ≥ 400), Caller populates the error property:
interface CallerError {
code: string; // Error code (e.g., 'HTTP_ERROR', 'TIMEOUT')
message: string; // Human-readable message
status: number; // HTTP status code
details?: unknown; // API-specific error details
retryAfter?: number; // Seconds to wait (for 429)
}const { data, error } = await api.get('/users/:id')
.params({ id: 'invalid' })
.execute();
if (error) {
console.log(error.code); // 'HTTP_ERROR'
console.log(error.status); // 404
console.log(error.message); // 'Not Found'
console.log(error.details); // { reason: 'User does not exist' }
}Best Practices
Response Handling Guidelines
- Always check for errors — Never assume
datais defined - Use explicit types for edge cases — When Content-Type is unreliable
- Handle all relevant status codes — Don't catch-all to 'error'
- Clean up Blobs — Call
URL.revokeObjectURL()after use - Stream large responses — Don't load huge files into memory