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:

MethodPurposeBodyIdempotentSafe
GETRetrieve dataNoYesYes
POSTCreate resourceYesNoNo
PUTReplace resourceYesYesNo
PATCHUpdate resourceYesNoNo
DELETERemove resourceOptionalYesNo
HEADGet headers onlyNoYesYes
OPTIONSCheck capabilitiesNoYesYes


Automatic Response Parsing

Caller automatically parses responses based on the Content-Type header:

Content-TypeParsingResult Type
application/jsonJSON.parse()Object/Array
text/plainRaw stringstring
text/htmlRaw stringstring
application/xmlParse to objectObject (if parser available)
multipart/form-dataFormDataFormData
application/octet-streamBlobBlob
image/*, video/*, audio/*BlobBlob
// 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 Blob

Explicit 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 TypeAuto Content-Type
Object/Arrayapplication/json
FormDatamultipart/form-data
URLSearchParamsapplication/x-www-form-urlencoded
stringtext/plain
BlobBlob'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

  1. Always check for errors — Never assume data is defined
  2. Use explicit types for edge cases — When Content-Type is unreliable
  3. Handle all relevant status codes — Don't catch-all to 'error'
  4. Clean up Blobs — Call URL.revokeObjectURL() after use
  5. Stream large responses — Don't load huge files into memory

Next Steps