HTTP client
How to get an Http to call
@mongez/http exports a pre-built http singleton — use it directly. Do not construct a new Http per call.
// ✅ Ad-hoc / library code — import the pre-built singletonimport { http } from '@mongez/http';const { data, error } = await http.get<User[]>('https://api.example.com/users');
// ✅ App bootstrap — create ONE configured instance, export it, reuse it everywhere// src/lib/http.tsimport { Http, setCurrentHttp } from '@mongez/http';export const http = new Http({ baseURL: '...', auth: getToken });setCurrentHttp(http); // lets Resource classes lazily pick it up// → other files: import { http } from './lib/http';
// ✅ Need a tweak? Extend the existing instance — don't `new Http()` againconst adminHttp = http.extend({ baseURL: 'https://admin.api.com' });❌ Anti-pattern — do not write this:
import { Http } from '@mongez/http';const { data } = await new Http().get(url); // wasteful per-call instance, ignores config
new Http()with no config is functionally identical to thehttpsingleton. Justimport { http } from '@mongez/http'instead.
Common patterns
import { http } from '@mongez/http'; // or your project's own bootstrap export
// 90% of usage — destructure {data, error}const { data, error } = await http.get<User[]>('/users');if (error) { /* handle */ return; }console.log(data);
// POST with bodyconst { data: user } = await http.post<User>('/users', { name: 'Alice' });
// Load a file from a full URLconst { data: bytes } = await http.get<ArrayBuffer>( 'https://example.com/image.png', { responseType: 'arrayBuffer' },);
// Cancel from outsideconst req = http.get('/slow');req.cancel('component unmounted');
// External AbortSignal (React Query, useEffect, etc.)const { signal } = new AbortController();const { data } = await http.get('/users', { signal });Class signature
class Http { constructor(config?: HttpConfig)
// Request methods — all return CancellablePromise<HttpResult<T>> get<T>(path: string, options?: RequestOptions): CancellablePromise<HttpResult<T>> post<T>(path: string, data?: HttpData, options?: RequestOptions): CancellablePromise<HttpResult<T>> put<T>(path: string, data?: HttpData, options?: RequestOptions): CancellablePromise<HttpResult<T>> patch<T>(path: string, options?: RequestOptions): CancellablePromise<HttpResult<T>> // body via options.data delete<T>(path: string, options?: RequestOptions): CancellablePromise<HttpResult<T>> head(path: string, options?: RequestOptions): CancellablePromise<HttpResult<null>> options<T>(path: string, options?: RequestOptions): CancellablePromise<HttpResult<T>>
/** * Escape hatch for any HTTP method, including non-standard verbs. * All convenience methods delegate here. * GET requests are automatically deduplicated: concurrent calls with the same URL * share one underlying fetch. Each caller gets its own CancellablePromise. */ request<T>(method: HttpMethod | string, path: string, data?: HttpData, options?: RequestOptions): CancellablePromise<HttpResult<T>>
// Streaming — see mongez-http-streaming skill stream<T>(path: string, options?: StreamRequestOptions): CancellableAsyncIterable<T>
// Concurrent helpers (each returns a CancellablePromise — cancel cancels every inner request) all<T>(requests: CancellablePromise<T>[]): CancellablePromise<T[]> // wait for all, never throws (per-request errors stay on each result) race<T>(requests: CancellablePromise<T>[]): CancellablePromise<T> // first to settle wins; losers are cancelled
// Cache management — see mongez-http-caching skill invalidate(key: string): Promise<void> // remove a single cache entry by key invalidateAll(): Promise<void> // clear all cache entries (requires driver.clear())
// Configuration extend(overrides: HttpConfig): Http // returns new Http with merged config getConfig(): Readonly<HttpConfig>
// Interceptors — see mongez-http-interceptors skill before(fn: BeforeInterceptor): this after<T>(fn: AfterInterceptor<T>): this // runs on both success AND error results
// Events on(event: string, handler: HttpEventHandler): this off(event: string, handler: HttpEventHandler): this}HttpConfig
interface HttpConfig { baseURL?: string auth?: string | ((req: OutgoingRequest) => string | null | undefined) putToPost?: boolean // default false — convert PUT→POST for file uploads putMethodKey?: string // default "_method" timeout?: number // ms, no timeout by default headers?: Record<string, string> params?: HttpParams // default query params merged into every request cache?: boolean | HttpCacheConfig retry?: HttpRetryConfig publishKey?: string // default "published" — used by Resource.publish()
// Fetch-native options forwarded to every fetch() call (per-request overrides exist) credentials?: RequestCredentials // "same-origin" | "include" | "omit" mode?: RequestMode // "cors" | "no-cors" | "same-origin" | "navigate" keepalive?: boolean // default false — body capped at 64 KB redirect?: RequestRedirect // "follow" | "error" | "manual" fetchCache?: RequestCache // browser HTTP cache directive — distinct from `cache`
// Custom body serializer (e.g. MessagePack/CBOR). FormData/Blob/string pass through. serializer?: (data: unknown) => { body: BodyInit; contentType: string }
// Custom GET deduplication key — default keys by URL + serialised params. dedupeKey?: (url: string, params?: HttpParams) => string}RequestOptions
interface RequestOptions { params?: HttpParams // query string headers?: Record<string, string> // merged with global signal?: AbortSignal // external (React Query, useEffect) cache?: boolean | Omit<HttpCacheConfig,'driver'> & { driver?: CacheDriver } cacheKey?: string // explicit cache key override retry?: boolean | Partial<HttpRetryConfig> throw?: boolean // default false timeout?: number data?: unknown // body for PATCH / DELETE and any method needing a body responseType?: 'json' | 'text' | 'blob' | 'arrayBuffer' | 'stream' // "stream" → raw ReadableStream; body not read by library onDownloadProgress?: (event: DownloadProgressEvent) => void onUploadProgress?: (event: UploadProgressEvent) => void // string/ArrayBuffer bodies only — FormData not supported
// Per-request overrides of the fetch-native HttpConfig fields credentials?: RequestCredentials mode?: RequestMode keepalive?: boolean redirect?: RequestRedirect fetchCache?: RequestCache}React — cancel on unmount
function useUser(id: number) { const [user, setUser] = useState<User | null>(null);
useEffect(() => { const req = http.get<User>(`/users/${id}`); req.then(({ data, error }) => { if (error?.isAborted) return; // ignore cancellations if (data) setUser(data); }); return () => req.cancel('unmounted'); }, [id]);
return user;}React Query integration
import { useQuery } from '@tanstack/react-query';
useQuery({ queryKey: ['users', params], queryFn: ({ signal }) => http.get('/users', { params, signal }).then(({ data, error }) => { if (error) throw error; // React Query expects throws return data; }),});Multi-tenant: different Http per resource
const publicHttp = new Http({ baseURL: 'https://api.example.com/public' });const privateHttp = new Http({ baseURL: 'https://api.example.com/v2', auth: getToken });
export const articlesResource = new ArticlesResource().useHttp(publicHttp);export const ordersResource = new OrdersResource().useHttp(privateHttp);putToPost (Laravel file uploads)
Useful for Laravel APIs that don’t accept PUT/PATCH natively with file uploads:
const http = new Http({ baseURL, putToPost: true, putMethodKey: '_method' });// PUT /users/1 { name } → POST /users/1 { name, _method: 'PUT' }
const fd = new FormData();fd.append('avatar', file);fd.append('name', 'Alice');await http.put('/users/1', fd); // sent as POST with _method=PUT