⚡ Fetch-Go Documentation
Complete API reference for Fetch-Go v1.2 — a lightweight, Axios-compatible HTTP client built on native fetch().
Installation
$ npm install fetch-go $ yarn add fetch-go $ pnpm add fetch-go
Requirements: Node.js 18+ or any modern browser with fetch() support.
Quick Start
import fetchgo from 'fetch-go'; // GET — auto-parses JSON const { data } = await fetchgo.get('/api/users'); // POST — auto-serializes body const { data: user } = await fetchgo.post('/api/users', { name: 'John', email: 'john@example.com' }); // TypeScript generics interface User { id: number; name: string; } const { data: users } = await fetchgo.get<User[]>('/api/users');
Create Instance
Create a pre-configured instance with defaults that apply to every request:
const api = fetchgo.create({ baseURL: 'https://api.example.com', timeout: 5000, headers: { 'Authorization': 'Bearer token' } }); const { data } = await api.get('/protected');
Migrate from Axios
// Before import axios from 'axios'; const api = axios.create({ baseURL: '...' }); // After import fetchgo from 'fetch-go'; const api = fetchgo.create({ baseURL: '...' }); // Everything else is the same! const { data } = await api.get('/users');
HTTP Methods
| Method | Signature |
|---|---|
request | fetchgo.request(config) |
get | fetchgo.get(url[, config]) |
post | fetchgo.post(url[, data[, config]]) |
put | fetchgo.put(url[, data[, config]]) |
patch | fetchgo.patch(url[, data[, config]]) |
delete | fetchgo.delete(url[, config]) |
head | fetchgo.head(url[, config]) |
options | fetchgo.options(url[, config]) |
postForm | fetchgo.postForm(url[, data[, config]]) |
putForm | fetchgo.putForm(url[, data[, config]]) |
patchForm | fetchgo.patchForm(url[, data[, config]]) |
getUri | fetchgo.getUri(config) |
create | fetchgo.create(config) |
all | fetchgo.all(promises) |
spread | fetchgo.spread(callback) |
isCancel | fetchgo.isCancel(error) |
Config Options
| Property | Type | Default | Description |
|---|---|---|---|
baseURL | string | — | Prepended to url unless url is absolute |
url | string | — | Request URL (relative to baseURL) |
method | string | 'GET' | HTTP method |
headers | object | {} | Custom headers or per-method headers |
params | object | — | URL query parameters |
paramsSerializer | function | — | Custom params serializer |
data | any | — | Request body |
timeout | number | 0 | Timeout in ms (0 = no timeout) |
signal | AbortSignal | — | Cancel signal |
responseType | string | 'json' | json|text|blob|arraybuffer|formdata|stream|document |
validateStatus | function | 200–299 | Resolve or reject based on status |
retry | number|bool|object | false | Retry configuration |
adapter | string|function | auto | 'fetch'|'http'|function |
auth | object | — | { username, password } for Basic Auth |
withCredentials | boolean | false | Include cookies in cross-site requests |
maxRedirects | number | 21 | Max redirects to follow |
maxContentLength | number | — | Max response size in bytes |
maxBodyLength | number | — | Max request body size in bytes |
maxRate | number|[up,down] | — | Throttle bytes/sec |
formSerializer | string|object | — | 'formdata'|'urlencoded'|{dots,indexes,metaTokens} |
transitional | object | — | Backward-compat options |
env | object | — | Polyfill injection { FormData, Blob } |
proxy | object|false | — | Proxy config (Node.js only) |
httpVersion | 1|2 | 1 | HTTP version (Node.js only) |
Per-Method Headers NEW
Set different default headers for each HTTP method, just like Axios:
const api = fetchgo.create({ headers: { common: { 'Accept': 'application/json' }, get: { 'Cache-Control': 'no-cache' }, post: { 'Content-Type': 'application/json' }, put: { 'Content-Type': 'application/json' }, patch: { 'Content-Type': 'application/json' }, } });
common headers are merged into every request. Method-specific headers are added only for that method.
Query Parameters
await fetchgo.get('/users', { params: { page: 1, limit: 10, tags: ['js', 'ts'] } }); // → /users?page=1&limit=10&tags=js&tags=ts // Custom serializer await fetchgo.get('/users', { params: { ids: [1, 2, 3] }, paramsSerializer: (p) => qs.stringify(p, { arrayFormat: 'brackets' }) });
Request Body
Fetch-Go auto-detects the body type and sets Content-Type:
| Data type | Content-Type |
|---|---|
| Object / Array | application/json (auto) |
| String | Passed as-is |
| FormData | multipart/form-data (auto) |
| URLSearchParams | application/x-www-form-urlencoded |
| Blob / ArrayBuffer / Stream | Passed as-is |
Authentication
// HTTP Basic Auth await fetchgo.get('/protected', { auth: { username: 'jane', password: 's3cret' } }); // → Authorization: Basic amFuZTpzM2NyZXQ=
Response Schema
{
data: {}, // response body (parsed)
status: 200, // HTTP status code
statusText: 'OK', // HTTP status message
headers: Headers, // Response headers (Headers object)
config: {}, // The config used for the request
request: Response // The raw Response object
}
Response Type
| Value | Returns |
|---|---|
'json' | Parsed JSON object (default) |
'text' | String |
'blob' | Blob |
'arraybuffer' | ArrayBuffer |
'formdata' | FormData |
'stream' | ReadableStream |
'document' NEW | DOM Document (browser only, via DOMParser) |
Transforms
const api = fetchgo.create({ transformRequest: [(data, headers) => { // Modify data before sending return JSON.stringify(data); }], transformResponse: [(data) => { // Modify data after receiving return data; }] });
Transitional Options NEW
| Option | Default | Description |
|---|---|---|
silentJSONParsing | true | Don't throw on JSON parse failure, return null |
forcedJSONParsing | false | Force JSON parse regardless of content-type |
clarifyTimeoutError | true | Use ETIMEDOUT instead of ECONNABORTED |
const api = fetchgo.create({ transitional: { silentJSONParsing: true, forcedJSONParsing: false, clarifyTimeoutError: true, } });
Interceptors ENHANCED
Basic Usage
// Request interceptor api.interceptors.request.use( (config) => { config.headers['X-Token'] = getToken(); return config; }, (error) => Promise.reject(error) ); // Response interceptor api.interceptors.response.use( (response) => response, (error) => { if (error.status === 401) logout(); throw error; } ); // Eject const id = api.interceptors.request.use(myInterceptor); api.interceptors.request.eject(id); // Clear all api.interceptors.request.clear();
Conditional Interceptors (runWhen) NEW
api.interceptors.request.use( (config) => { config.headers['X-Auth'] = getToken(); return config; }, undefined, { runWhen: (config) => config.url?.startsWith('/api/') } );
The interceptor only runs when runWhen returns true.
Synchronous Interceptors NEW
api.interceptors.request.use( (config) => { config.headers['X-Time'] = Date.now().toString(); return config; }, undefined, { synchronous: true } );
When all request interceptors are synchronous: true, they run without await — saving one micro-task per interceptor.
Retry
// Simple await fetchgo.get('/api', { retry: 3 }); await fetchgo.get('/api', { retry: true }); // 3 retries // Advanced await fetchgo.get('/api', { retry: { retries: 5, delay: 500, // initial delay ms backoff: 2, // exponential: 500, 1000, 2000... retryOn: ['GET', 'PUT'], retryStatusCodes: [429, 500, 502, 503], retryCondition: (error) => !fetchgo.isCancel(error), } });
Timeout & Cancellation
// Timeout await fetchgo.get('/slow', { timeout: 5000 }); // Cancel with AbortController const controller = new AbortController(); fetchgo.get('/long', { signal: controller.signal }); controller.abort(); // Both combined — whichever fires first await fetchgo.get('/endpoint', { timeout: 5000, signal: controller.signal });
Progress Events
// Upload await fetchgo.post('/upload', file, { onUploadProgress: (e) => { console.log(`Upload: ${Math.round((e.progress||0)*100)}%`); console.log(`Rate: ${e.rate} bytes/sec`); console.log(`ETA: ${e.estimated}s`); } }); // Download await fetchgo.get('/large-file', { onDownloadProgress: (e) => { console.log(e.loaded, e.total, e.progress, e.bytes, e.rate, e.estimated); } });
Progress Event Schema
| Property | Type | Description |
|---|---|---|
loaded | number | Bytes transferred so far |
total | number? | Total bytes (if known) |
progress | number? | 0–1 fraction complete |
bytes | number | Bytes in this chunk |
rate | number? | Bytes per second |
estimated | number? | Estimated seconds remaining |
upload | boolean? | true if upload event |
download | boolean? | true if download event |
Rate Limiting (maxRate) NEW
// Limit both directions to 100 KB/s await fetchgo.get('/file', { maxRate: 100 * 1024 }); // Separate upload/download rates await fetchgo.post('/upload', file, { maxRate: [50 * 1024, 200 * 1024] // [upload, download] bytes/sec });
Form Handling
Shorthand Methods
await fetchgo.postForm('/upload', { name: 'John', avatar: file }); await fetchgo.putForm('/update', { name: 'Jane' }); await fetchgo.patchForm('/patch', { avatar: newFile });
URL-Encoded
await fetchgo.post('/login', { user: 'john', pass: 'secret' }, { formSerializer: 'urlencoded' });
Advanced FormData Options NEW
import { toFormData, formToJSON } from 'fetch-go'; // Dot notation toFormData({ user: { name: 'John' } }, undefined, { dots: true }); // → user.name=John (instead of user[name]) // Array indexes toFormData({ tags: ['a','b'] }, undefined, { indexes: true }); // tags[0]=a toFormData({ tags: ['a','b'] }, undefined, { indexes: false }); // tags[]=a toFormData({ tags: ['a','b'] }, undefined, { indexes: null }); // tags=a // Meta tokens toFormData({ users: [{ name: 'a' }] }, undefined, { metaTokens: true }); // Convert back const obj = formToJSON(formData);
| Option | Type | Description |
|---|---|---|
dots | boolean | Use parent.child instead of parent[child] |
indexes | bool|null | true: arr[0], false: arr[], null: bare key |
metaTokens | boolean | Add {}/[] type hints to keys |
XSRF Protection
const api = fetchgo.create({ xsrfCookieName: 'XSRF-TOKEN', xsrfHeaderName: 'X-XSRF-TOKEN', }); // Token is auto-read from cookies and sent as a header
Environment Auto-Detection NEW
Fetch-Go automatically selects the best adapter:
| Environment | Adapter | Features |
|---|---|---|
| Browser | fetch() | Full browser API support |
| Node.js | http/https | Proxy, HTTP/2, agents, socket |
Override with adapter: 'fetch' or adapter: 'http'.
HTTP/2 (Node.js)
await fetchgo.get('https://api.example.com', { httpVersion: 2, auth: { username: 'user', password: 'pass' }, onDownloadProgress: (e) => console.log(e.progress) });
Proxy (Node.js)
await fetchgo.get('https://api.example.com', { proxy: { protocol: 'https', host: '127.0.0.1', port: 9000, auth: { username: 'proxyuser', password: 'proxypass' } } });
Agents & Socket Path
import http from 'node:http'; import https from 'node:https'; await fetchgo.get('https://api.example.com', { httpAgent: new http.Agent({ keepAlive: true }), httpsAgent: new https.Agent({ rejectUnauthorized: false }), }); // Unix socket (e.g., Docker) await fetchgo.get('/containers/json', { socketPath: '/var/run/docker.sock', baseURL: 'http://localhost' });
Static Helpers NEW
// Parallel requests const [users, posts] = await fetchgo.all([ fetchgo.get('/users'), fetchgo.get('/posts'), ]); // Spread helper fetchgo.all([fetchgo.get('/a'), fetchgo.get('/b')]) .then(fetchgo.spread((a, b) => console.log(a.data, b.data))); // Cancel detection fetchgo.isCancel(error); // true if AbortError
Error Handling
import { FetchGoError, isFetchGoError, isCancel } from 'fetch-go'; try { await api.get('/fail'); } catch (error) { if (isCancel(error)) { console.log('Cancelled'); } else if (isFetchGoError(error)) { console.log(error.code); // ERR_BAD_REQUEST console.log(error.status); // 404 console.log(error.response?.data); // response body console.log(error.config); // request config console.log(error.toJSON()); // serializable } }
Error Codes
| Code | Description |
|---|---|
ERR_NETWORK | Network error (no response) |
ECONNABORTED | Timeout exceeded |
ETIMEDOUT | Timeout (with clarifyTimeoutError: true) |
ERR_CANCELED | Request was cancelled |
ERR_BAD_REQUEST | 4xx status code |
ERR_BAD_RESPONSE | 5xx status code |
Custom Adapters
const mockAdapter = async (config) => ({ data: { mock: true }, status: 200, statusText: 'OK', headers: new Headers(), config, request: new Response(), }); await fetchgo.get('/test', { adapter: mockAdapter });
Env Config NEW
Inject custom FormData or Blob implementations:
import FormData from 'form-data'; await fetchgo.post('/upload', { file: stream }, { env: { FormData }, formSerializer: 'formdata', });
TypeScript
import fetchgo, { type FetchGoRequestConfig, type FetchGoResponse, type FetchGoProgressEvent, type InterceptorOptions, type FormSerializerOptions, type TransitionalOptions, type MethodHeaders, type EnvConfig, } from 'fetch-go'; // Full generics interface User { id: number; name: string; } const { data } = await fetchgo.get<User[]>('/users'); // data is User[]
MIT © 2025 — Fetch-Go v1.2.1