Requests & Responses
Learn how to make different types of HTTP requests and handle their responses.
Making Requests
Section titled “Making Requests”HTTP Methods
Section titled “HTTP Methods”fetchquack supports all standard HTTP methods:
// GET requestawait client.fetch({ method: 'GET', url: '/api/users' });
// POST requestawait client.fetch({ method: 'POST', url: '/api/users', body: { name: 'John' } });
// PUT requestawait client.fetch({ method: 'PUT', url: '/api/users/1', body: { name: 'Jane' } });
// PATCH requestawait client.fetch({ method: 'PATCH', url: '/api/users/1', body: { email: 'new@email.com' } });
// DELETE requestawait client.fetch({ method: 'DELETE', url: '/api/users/1' });
// HEAD requestawait client.fetch({ method: 'HEAD', url: '/api/health', parseJson: false });
// OPTIONS requestawait client.fetch({ method: 'OPTIONS', url: '/api/users', parseJson: false });Request Body
Section titled “Request Body”JSON Body (Automatic)
Section titled “JSON Body (Automatic)”Object bodies are automatically serialized to JSON. The Content-Type header is auto-set to application/json if not provided:
// Content-Type: application/json is set automaticallyawait client.fetch({ method: 'POST', url: '/api/users', body: { name: 'John Doe', email: 'john@example.com', age: 30 }});Text Body
Section titled “Text Body”String bodies get Content-Type: text/plain automatically if not set:
await client.fetch({ method: 'POST', url: '/api/log', body: 'Plain text log message'});Form Data
Section titled “Form Data”const formData = new FormData();formData.append('file', fileInput.files[0]);formData.append('description', 'My file');
await client.fetch({ method: 'POST', url: '/api/upload', body: formData // Don't set Content-Type - browser sets it automatically with the boundary});Binary Data
Section titled “Binary Data”const fileBuffer = await file.arrayBuffer();
await client.fetch({ method: 'POST', url: '/api/upload', body: fileBuffer, headers: { 'Content-Type': 'application/octet-stream' }});Request Headers
Section titled “Request Headers”await client.fetch({ method: 'GET', url: '/api/data', headers: { 'Authorization': 'Bearer token123', 'Accept': 'application/json', 'X-Custom-Header': 'value' }});Handling Responses
Section titled “Handling Responses”JSON Response (Default)
Section titled “JSON Response (Default)”By default, responses are parsed as JSON:
interface User { id: number; name: string; email: string;}
const user = await client.fetch<User>({ method: 'GET', url: '/api/users/1'});
console.log(user.name); // TypeScript knows about the name propertyText Response
Section titled “Text Response”Get raw text instead of parsed JSON:
const html = await client.fetch({ method: 'GET', url: '/api/template', parseJson: false});
console.log(html); // Raw HTML stringBinary Response
Section titled “Binary Response”Get binary data as Uint8Array:
const imageBytes = await client.fetch({ method: 'GET', url: '/api/images/photo.png', decodeToString: false});
// Create a blob and object URLconst blob = new Blob([imageBytes], { type: 'image/png' });const url = URL.createObjectURL(blob);imageElement.src = url;Request Options
Section titled “Request Options”Full Request Configuration
Section titled “Full Request Configuration”interface HttpRequest { method: string; // Required: HTTP method url: string; // Required: Request URL body?: any; // Optional: Request body headers?: Record<string, string>; // Optional: HTTP headers parseJson?: boolean; // Default: true - parse response as JSON decodeToString?: boolean; // Default: true - false returns Uint8Array signal?: AbortSignal; // Optional: Cancellation signal interceptors?: HttpInterceptorFn[]; // Optional: Request-specific interceptors onUploadProgress?: (progress: HttpProgressEvent) => void; // Optional onDownloadProgress?: (progress: HttpProgressEvent) => void; // Optional}Cancellation
Section titled “Cancellation”Use AbortController to cancel requests:
const controller = new AbortController();
const requestPromise = client.fetch({ method: 'GET', url: '/api/slow-endpoint', signal: controller.signal});
// Cancel after 5 secondssetTimeout(() => controller.abort(), 5000);
try { const data = await requestPromise;} catch (error) { if (error instanceof HttpError && error.statusCode === 0) { console.log('Request was cancelled'); }}Request-Specific Interceptors
Section titled “Request-Specific Interceptors”Add interceptors to individual requests. These run after global interceptors:
await client.fetch({ method: 'GET', url: '/api/special', interceptors: [ customAuthInterceptor, retryInterceptor ]});Error Handling
Section titled “Error Handling”HttpError
Section titled “HttpError”Thrown when a request fails (non-2xx status) or a network error occurs:
import { HttpError } from 'fetchquack';
try { await client.fetch({ method: 'GET', url: '/api/users/999' });} catch (error) { if (error instanceof HttpError) { console.log('Status code:', error.statusCode); // e.g., 404, 500 console.log('Message:', error.message); // Error description console.log('Original error:', error.error); // Underlying Error, if any
if (error.statusCode === 404) { console.log('User not found'); } else if (error.statusCode >= 500) { console.log('Server error'); } else if (error.statusCode === 0) { console.log('Network error or request aborted'); } }}HttpJsonParseError
Section titled “HttpJsonParseError”Thrown when the response body cannot be parsed as JSON (extends HttpError):
import { HttpJsonParseError } from 'fetchquack';
try { await client.fetch({ method: 'GET', url: '/api/data' });} catch (error) { if (error instanceof HttpJsonParseError) { console.log('Invalid JSON response'); console.log('Raw text:', error.responseText); // Truncated to ~500 chars }}Error Properties Reference
Section titled “Error Properties Reference”class HttpError extends Error { readonly statusCode: number; // HTTP status code (0 for network/abort errors) readonly message: string; // Error description readonly error?: Error; // Original error (for chaining)}
class HttpJsonParseError extends HttpError { readonly responseText: string; // Raw response text (truncated to ~500 chars) // statusCode is always 0}Best Practices
Section titled “Best Practices”Type Safety
Section titled “Type Safety”Always specify response types:
// Good - typed responseconst user = await client.fetch<User>({ method: 'GET', url: '/api/user' });
// Avoid - untyped responseconst user = await client.fetch({ method: 'GET', url: '/api/user' });Error Handling
Section titled “Error Handling”Always handle errors appropriately:
import { HttpError, HttpJsonParseError } from 'fetchquack';
try { const data = await client.fetch<DataType>({ method: 'GET', url: '/api/data' }); processData(data);} catch (error) { if (error instanceof HttpJsonParseError) { handleJsonError(error); } else if (error instanceof HttpError) { handleHttpError(error); } else { handleUnexpectedError(error); }}Reusable Request Functions
Section titled “Reusable Request Functions”Create typed request functions for your API:
async function getUser(id: number): Promise<User> { return client.fetch<User>({ method: 'GET', url: `/api/users/${id}` });}
async function createUser(userData: CreateUserDto): Promise<User> { return client.fetch<User>({ method: 'POST', url: '/api/users', body: userData });}
async function deleteUser(id: number): Promise<void> { await client.fetch({ method: 'DELETE', url: `/api/users/${id}`, parseJson: false });}Automatic Content-Type
Section titled “Automatic Content-Type”fetchquack automatically sets Content-Type when not provided:
- Object body →
application/json(body is JSON-stringified) - String body →
text/plain - FormData → Set automatically by the browser (with boundary)
// These are equivalent:await client.fetch({ method: 'POST', url: '/api', body: { key: 'value' } });await client.fetch({ method: 'POST', url: '/api', body: { key: 'value' }, headers: { 'Content-Type': 'application/json' }});Next Steps
Section titled “Next Steps”- Learn about Interceptors for request/response modification
- Explore Progress Tracking for uploads and downloads
- Discover Streaming for large responses