@fukict/fetch
v0.1.14
Published
Axios-style HTTP client built on native fetch for Fukict framework
Maintainers
Readme
@fukict/fetch
Axios-style HTTP client built on native fetch, zero dependencies.
Features
- Axios-Compatible API:
get/post/put/patch/delete/head/options - Interceptors: Request and response interceptors with
use()/eject() - Timeout: Automatic request cancellation via AbortController
- Typed Errors:
FetchErrorwith error codes (ERR_NETWORK,ERR_TIMEOUT,ERR_BAD_REQUEST, ...) - Streaming:
responseType: 'stream'returns rawReadableStream - Download Progress:
onDownloadProgresscallback for tracking transfer progress - Zero Dependencies: Built entirely on native
fetchAPI - Type-Safe: Full TypeScript support with generic response typing
Installation
pnpm add @fukict/fetchQuick Start
import { createFetch } from '@fukict/fetch';
const http = createFetch({
baseURL: '/api',
timeout: 10000,
});
// GET
const { data } = await http.get<User[]>('/users');
// POST
const { data: user } = await http.post<User>('/users', {
name: 'Alice',
email: '[email protected]',
});Interceptors
Request Interceptor
http.interceptors.request.use(config => {
const token = localStorage.getItem('token');
if (token) {
config.headers = { ...config.headers, Authorization: `Bearer ${token}` };
}
return config;
});Response Interceptor
http.interceptors.response.use(
response => response,
error => {
if (FetchError.isFetchError(error) && error.response?.status === 401) {
// Redirect to login
window.location.href = '/login';
}
return Promise.reject(error);
},
);Remove Interceptor
const id = http.interceptors.request.use(config => {
// ...
return config;
});
// Remove later
http.interceptors.request.eject(id);API Reference
createFetch(config?)
Creates an HTTP client instance (equivalent to axios.create()).
const http = createFetch({
baseURL: 'https://api.example.com',
timeout: 5000,
headers: {
'Content-Type': 'application/json',
},
});Request Config
| Property | Type | Description |
| -------------------- | --------------------------------------------------------- | ----------------------------------------------- |
| baseURL | string | Base URL prepended to relative paths |
| url | string | Request URL |
| method | string | HTTP method |
| headers | Record<string, string> | Request headers |
| params | Record<string, string \| number \| boolean> | URL query parameters |
| data | unknown | Request body (auto JSON serialized for objects) |
| timeout | number | Timeout in milliseconds |
| responseType | 'json' \| 'text' \| 'blob' \| 'arraybuffer' \| 'stream' | Response parsing strategy |
| onDownloadProgress | (progress: ProgressEvent) => void | Download progress callback |
| signal | AbortSignal | External abort signal |
Response Structure
interface FetchResponse<T> {
data: T; // Parsed response body
status: number; // HTTP status code
statusText: string; // HTTP status text
headers: Headers; // Response headers
config: FetchRequestConfig; // Request config used
}Instance Methods
http.get<T>(url, config?)
http.post<T>(url, data?, config?)
http.put<T>(url, data?, config?)
http.patch<T>(url, data?, config?)
http.delete<T>(url, config?)
http.head<T>(url, config?)
http.options<T>(url, config?)
http.request<T>(config)Error Handling
import { FetchError } from '@fukict/fetch';
try {
await http.get('/users');
} catch (error) {
if (FetchError.isFetchError(error)) {
switch (error.code) {
case 'ERR_NETWORK':
console.error('Network error:', error.message);
break;
case 'ERR_TIMEOUT':
console.error('Request timed out');
break;
case 'ERR_BAD_REQUEST':
console.error('Client error:', error.response?.status);
break;
case 'ERR_BAD_RESPONSE':
console.error('Server error:', error.response?.status);
break;
case 'ERR_CANCELED':
console.error('Request was canceled');
break;
}
}
}FetchError Properties
| Property | Type | Description |
| ---------- | ---------------------------- | ---------------------------------- |
| message | string | Error message |
| code | FetchErrorCode | Error code |
| config | FetchRequestConfig | Original request config |
| response | FetchResponse \| undefined | Response (present for HTTP errors) |
Streaming
ReadableStream Response
const stream = await http.get<ReadableStream>('/events', {
responseType: 'stream',
});
const reader = stream.data.getReader();
const decoder = new TextDecoder();
while (true) {
const { done, value } = await reader.read();
if (done) break;
console.log(decoder.decode(value));
}Download with Progress
const { data } = await http.get<Blob>('/files/report.pdf', {
responseType: 'blob',
onDownloadProgress({ loaded, total, percent }) {
console.log(`${percent}% (${loaded}/${total} bytes)`);
},
});
// Create download link
const url = URL.createObjectURL(data);
const a = document.createElement('a');
a.href = url;
a.download = 'report.pdf';
a.click();File Upload
const formData = new FormData();
formData.append('file', fileInput.files[0]);
formData.append('name', 'avatar');
const { data } = await http.post<{ url: string }>('/upload', formData);
// Content-Type is automatically set to multipart/form-data by the browserRequest Cancellation
const controller = new AbortController();
// Cancel after user action
cancelButton.onclick = () => controller.abort();
try {
const { data } = await http.get('/slow-endpoint', {
signal: controller.signal,
});
} catch (error) {
if (FetchError.isFetchError(error) && error.code === 'ERR_CANCELED') {
console.log('Request was canceled by user');
}
}Migration from Axios
| Axios | @fukict/fetch |
| ----------------------------------------- | ------------------------------------- |
| axios.create(config) | createFetch(config) |
| instance.get(url, config) | http.get(url, config) |
| instance.post(url, data, config) | http.post(url, data, config) |
| instance.interceptors.request.use(fn) | http.interceptors.request.use(fn) |
| instance.interceptors.request.eject(id) | http.interceptors.request.eject(id) |
| axios.isAxiosError(error) | FetchError.isFetchError(error) |
| error.response | error.response |
| error.config | error.config |
| { cancelToken } | { signal: controller.signal } |
Key differences:
- Response
headersis a nativeHeadersobject instead of a plain object responseType: 'stream'returnsReadableStream(not Node stream)- No upload progress (native
fetchlimitation), useFormDatafor uploads - Cancel via standard
AbortControllerinstead ofCancelToken
Related Packages
- @fukict/basic - Core rendering engine
- @fukict/router - SPA routing
- @fukict/i18n - Internationalization
License
MIT
