Skip to content

Making Requests

FetchEngine provides type-safe HTTP methods that return rich response objects with full request context.

HTTP Methods

All request methods return a FetchPromise<T> that resolves to FetchResponse<T>.

GET

typescript
api.get<Res, ResHdr>(path: string, options?: CallConfig): FetchPromise<Res>

Retrieve data from the server.

typescript
const { data: users } = await api.get<User[]>('/users');

const { data: user } = await api.get<User>('/users/123', {
    params: { include: 'profile' }
});

POST

typescript
api.post<Res, Data, ResHdr>(path: string, payload?: Data, options?: CallConfig): FetchPromise<Res>

Create a new resource.

typescript
const { data: user } = await api.post<User, CreateUserData>('/users', {
    name: 'John Doe',
    email: '[email protected]'
});

PUT

typescript
api.put<Res, Data, ResHdr>(path: string, payload?: Data, options?: CallConfig): FetchPromise<Res>

Replace a resource.

typescript
const { data: user } = await api.put<User, UpdateUserData>('/users/123', {
    name: 'Jane Doe',
    email: '[email protected]'
});

PATCH

typescript
api.patch<Res, Data, ResHdr>(path: string, payload?: Data, options?: CallConfig): FetchPromise<Res>

Partially update a resource.

typescript
const { data: user } = await api.patch<User, Partial<User>>('/users/123', {
    email: '[email protected]'
});

DELETE

typescript
api.delete<Res, Data, ResHdr>(path: string, payload?: Data, options?: CallConfig): FetchPromise<Res>

Remove a resource.

typescript
await api.delete('/users/123');

// With request body
await api.delete('/users/batch', { ids: ['1', '2', '3'] });

OPTIONS

typescript
api.options<Res, ResHdr>(path: string, options?: CallConfig): FetchPromise<Res>

Check server capabilities.

typescript
const { headers } = await api.options('/users');
typescript
api.head<ResHdr>(path: string, options?: CallConfig): FetchPromise<null>

Retrieve headers only (no body).

typescript
const { headers } = await api.head('/users/123');

Generic Request

typescript
api.request<Res, Data, ResHdr>(
    method: HttpMethods,
    path: string,
    options?: CallConfig & { payload?: Data }
): FetchPromise<Res>

Make a request with any HTTP method.

typescript
const { data } = await api.request<User>('PATCH', '/users/123', {
    payload: { name: 'Updated' },
    headers: { 'X-Custom': 'value' }
});

FetchResponse

Every HTTP method returns a FetchResponse object:

typescript
interface FetchResponse<T, H, P, RH> {

    data: T;                  // Parsed response body
    headers: Partial<RH>;     // Response headers
    status: number;           // HTTP status code
    request: Request;         // Original request object
    config: FetchConfig<H, P>; // Configuration used for request
}

Example:

typescript
const response = await api.get<User[]>('/users');

console.log(response.data);      // User[]
console.log(response.status);    // 200
console.log(response.headers);   // { 'content-type': 'application/json', ... }
console.log(response.config);    // { baseUrl: '...', headers: {...}, ... }

// Destructure just the data
const { data: users } = await api.get<User[]>('/users');

Per-Request Options

Override instance configuration for individual requests using CallConfig:

OptionTypeDescription
headersDictAndT<H>Request-specific headers
paramsDictAndT<P>Request-specific URL parameters
totalTimeoutnumberTotal timeout including retries (ms)
attemptTimeoutnumberPer-attempt timeout (ms)
retryRetryConfig | booleanOverride retry configuration
abortControllerAbortControllerCustom abort controller
determineTypeDetermineTypeFnCustom response type detection
onBeforeReq(opts) => voidCalled before request
onAfterReq(response, opts) => voidCalled after response
onError(err) => voidCalled on error

Example:

typescript
const { data } = await api.get<User>('/users/123', {
    headers: { 'X-Include': 'profile' },
    params: { version: 'v2' },
    totalTimeout: 60000,
    retry: { maxAttempts: 5 }
});

Response Chaining

Declare how the response body should be parsed by chaining a directive method before awaiting. Without a directive, the response is auto-parsed based on content-type (backwards compatible).

typescript
// Explicit response type via chaining
const { data: user } = await api.get<User>('/users/123').json();
const { data: html } = await api.get('/page').text();
const { data: file } = await api.get('/file').blob();
const { data: buf } = await api.get('/binary').arrayBuffer();
const { data: form } = await api.get('/form').formData();
const { data: res } = await api.get('/endpoint').raw();

// No directive — auto-parse based on content-type (backwards compatible)
const { data } = await api.get<User>('/users/123');

Available directives:

MethodReturnsDescription
.json()FetchPromise<T>Parse as JSON (preserves generic type)
.text()FetchPromise<string>Parse as plain text
.blob()FetchPromise<Blob>Parse as Blob
.arrayBuffer()FetchPromise<ArrayBuffer>Parse as ArrayBuffer
.formData()FetchPromise<FormData>Parse as FormData
.raw()FetchPromise<Response>Return raw Response without parsing
.stream()FetchStreamPromiseStream mode with async iteration

Override Guard

Setting a directive more than once throws an error. This prevents accidental double-calls that would silently discard the first directive:

typescript
// Throws: 'Response type already set'
api.get('/users').json().text();

Stream Mode

Use .stream() to get raw Response objects with unconsumed body streams. Cache and deduplication are skipped (each caller needs its own readable stream). Rate limiting and lifecycle events still fire normally.

.stream() returns a FetchStreamPromise which supports for await iteration over Uint8Array chunks:

typescript
// Async iteration over response body chunks
for await (const chunk of api.get('/events').stream()) {
    console.log(new TextDecoder().decode(chunk));
}

// With error handling
const stream = api.get('/events').stream();
const [, err] = await attempt(async () => {
    for await (const chunk of stream) {
        console.log(new TextDecoder().decode(chunk));
    }
});
if (err) console.error('Stream failed:', err.message);

// Works with all HTTP methods
for await (const chunk of api.post('/upload-stream', largePayload).stream()) {
    process(chunk);
}

FetchPromise

All HTTP methods return a FetchPromise — an extended Promise that supports abort, response chaining, and streaming:

typescript
interface FetchPromise<T, H, P, RH> extends Promise<FetchResponse<T, H, P, RH>> {

    isFinished: boolean;  // Whether request completed
    isAborted: boolean;   // Whether request was aborted
    abort(reason?: string): void;  // Cancel the request

    // Response chaining directives
    json(): FetchPromise<T, H, P, RH>;
    text(): FetchPromise<string, H, P, RH>;
    blob(): FetchPromise<Blob, H, P, RH>;
    arrayBuffer(): FetchPromise<ArrayBuffer, H, P, RH>;
    formData(): FetchPromise<FormData, H, P, RH>;
    raw(): FetchPromise<Response, H, P, RH>;
    stream(): FetchStreamPromise<H, P, RH>;
}

Example:

typescript
const request = api.get('/slow-endpoint');

// Abort after 5 seconds
setTimeout(() => {
    if (!request.isFinished) {
        request.abort('Timeout');
    }
}, 5000);

const [data, err] = await attempt(() => request);

if (err && request.isAborted) {
    console.log('Request was cancelled');
}

URL Handling

Relative Paths

Relative paths are joined with the base URL:

typescript
const api = new FetchEngine({ baseUrl: 'https://api.example.com' });

await api.get('/users');  // → https://api.example.com/users
await api.get('/users/123');  // → https://api.example.com/users/123

Absolute URLs

Absolute URLs bypass the base URL:

typescript
// Uses external URL directly
const { data } = await api.get('https://other-api.com/data');

URL Parameters

Parameters are appended to the query string:

typescript
await api.get('/users', {
    params: { page: '1', limit: '10' }
});
// → https://api.example.com/users?page=1&limit=10

// Combined with existing query string
await api.get('/users?active=true', {
    params: { page: '1' }
});
// → https://api.example.com/users?active=true&page=1

Request Lifecycle Hooks

Add per-request callbacks:

typescript
const { data } = await api.post('/users', userData, {
    onBeforeReq: (opts) => {
        console.log('Starting request:', opts.method, opts.url);
    },

    onAfterReq: (response, opts) => {
        console.log('Completed:', response.status);
    },

    onError: (err) => {
        console.error('Failed:', err.message);
    }
});

Type Safety

FetchEngine supports full TypeScript generics:

typescript
interface User {
    id: string;
    name: string;
    email: string;
}

interface CreateUserData {
    name: string;
    email: string;
}

// Response type is inferred
const { data } = await api.get<User>('/users/123');
// data: User

// Payload type is validated
const { data: newUser } = await api.post<User, CreateUserData>('/users', {
    name: 'John',
    email: '[email protected]'
    // TypeScript error if payload doesn't match CreateUserData
});