Fetch
A small Fetch API wrapper
Install / Use
/learn @tkrotoff/FetchREADME
@tkrotoff/fetch
A Fetch wrapper.
- Simplifies the use of Fetch
- Tiny: less than 200 lines of code
- No dependencies
- Supports Node.js & web browsers
- Comes with test utilities
- Fully tested (against Undici & whatwg-fetch)
- Written in TypeScript
Why?
When using Fetch, you must write some boilerplate:
const url = 'https://example.com/profile';
const data = { username: 'example' };
try {
const response = await fetch(url, {
method: 'POST',
body: JSON.stringify(data),
headers: {
'content-type': 'application/json'
}
});
if (!response.ok) {
throw new Error('Network response was not ok');
}
const json = await response.json();
console.log('Success:', json);
} catch (e) {
console.error('Error:', e);
}
With @tkrotoff/fetch it becomes:
try {
const response = await postJSON(url, data).json();
console.log('Success:', response);
} catch (e /* HttpError | TypeError | DOMException */) {
console.error('Error:', e);
}
You don't have to worry about:
- HTTP headers: Accept and Content-Type are already set
- stringifying the request body
- One
awaitinstead of two - No need to manually throw an exception on HTTP error status (like 404 or 500)
Usage
Examples:
- https://stackblitz.com/github/tkrotoff/fetch/tree/codesandbox.io/examples/web
- https://stackblitz.com/github/tkrotoff/fetch/tree/codesandbox.io/examples/node
- https://github.com/tkrotoff/MarvelHeroes
npm install @tkrotoff/fetch
import { defaults, postJSON } from '@tkrotoff/fetch';
defaults.init = { /* ... */ };
const response = await postJSON(
'https://jsonplaceholder.typicode.com/posts',
{ title: 'foo', body: 'bar', userId: 1 }
).json();
console.log(response);
Or copy-paste Http.ts into your source code.
JavaScript runtimes support
@tkrotoff/fetch supports Node.js and modern browsers
Node.js
- Nothing is needed if Node.js >= 18.0
- Use
--experimental-fetchif Node.js >= 16.15 < 18.0 - ⚠️ node-fetch is not supported with @tkrotoff/fetch >= 0.17 due to
Requestclass limitations
Check examples/node
Browsers
Check examples/web
API
-
get(input:RequestInfo| URL, init?:RequestInit): ResponsePromiseWithBodyMethods -
post(input: RequestInfo | URL, body?:BodyInit, init?: RequestInit): ResponsePromiseWithBodyMethods -
postJSON(input: RequestInfo | URL, body: object, init?: RequestInit): ResponsePromiseWithBodyMethods -
put(input: RequestInfo | URL, body?: BodyInit, init?: RequestInit): ResponsePromiseWithBodyMethods -
putJSON(input: RequestInfo | URL, body: object, init?: RequestInit): ResponsePromiseWithBodyMethods -
patch(input: RequestInfo | URL, body?: BodyInit, init?: RequestInit): ResponsePromiseWithBodyMethods -
patchJSON(input: RequestInfo | URL, body: object, init?: RequestInit): ResponsePromiseWithBodyMethods -
del(input: RequestInfo | URL, init?: RequestInit): ResponsePromiseWithBodyMethods -
isJSONResponse(response:Response): boolean
ResponsePromiseWithBodyMethods being Promise<Response> with added methods from Body.
HttpError
@tkrotoff/fetch throws HttpError with response and request properties when the HTTP status code is < 200 or >= 300.
Test utilities
-
createResponsePromise(body?:BodyInit, init?:ResponseInit): ResponsePromiseWithBodyMethods -
createJSONResponsePromise(body: object, init?: ResponseInit): ResponsePromiseWithBodyMethods -
createHttpError(body: BodyInit, status: number, statusText?: string): HttpError -
createJSONHttpError(body: object, status: number, statusText?: string): HttpError
HttpStatus
Instead of writing HTTP statuses as numbers 201, 403, 503... you can replace them with HttpStatus and write more explicit code:
import { HttpStatus } from '@tkrotoff/fetch';
console.log(HttpStatus._201_Created);
console.log(HttpStatus._403_Forbidden);
console.log(HttpStatus._503_ServiceUnavailable);
type HttpStatusEnum = typeof HttpStatus[keyof typeof HttpStatus];
const status: HttpStatusEnum = HttpStatus._200_OK;
Configuration
@tkrotoff/fetch exposes defaults.init that will be applied to every request.
import { defaults } from '@tkrotoff/fetch';
defaults.init.mode = 'cors';
defaults.init.credentials = 'include';
Testing
When testing your code, use createResponsePromise() and createJSONResponsePromise():
import * as Http from '@tkrotoff/fetch';
// https://github.com/aelbore/esbuild-jest/issues/26#issuecomment-968853688
// https://github.com/swc-project/swc/issues/5059
jest.mock('@tkrotoff/fetch', () => ({
__esModule: true,
...jest.requireActual('@tkrotoff/fetch')
}));
test('OK', async () => {
const mock = jest.spyOn(Http, 'get').mockImplementation(() =>
Http.createResponsePromise('test')
);
const response = await Http.get(url).text();
expect(response).toEqual('test');
expect(mock).toHaveBeenCalledTimes(1);
expect(mock).toHaveBeenCalledWith(url);
mock.mockRestore();
});
test('fail', async () => {
const mock = jest.spyOn(Http, 'get').mockImplementation(() =>
Http.createResponsePromise(
'<!DOCTYPE html><title>404</title>',
{ status: 404, statusText: 'Not Found' }
)
);
await expect(Http.get(url).text()).rejects.toThrow('Not Found');
expect(mock).toHaveBeenCalledTimes(1);
expect(mock).toHaveBeenCalledWith(url);
mock.mockRestore();
});
Other possible syntax with jest.mock instead of jest.spyOn:
import { createResponsePromise, get } from '@tkrotoff/fetch';
beforeEach(() => jest.resetAllMocks());
jest.mock('@tkrotoff/fetch', () => ({
...jest.requireActual('@tkrotoff/fetch'),
get: jest.fn(),
post: jest.fn(),
postJSON: jest.fn(),
put: jest.fn(),
putJSON: jest.fn(),
patch: jest.fn(),
patchJSON: jest.fn(),
del: jest.fn()
}));
test('OK', async () => {
jest.mocked(get).mockImplementation(() =>
createResponsePromise('test')
);
const response = await get(url).text();
expect(response).toEqual('test');
expect(get).toHaveBeenCalledTimes(1);
expect(get).toHaveBeenCalledWith(url);
});
test('fail', async () => {
jest.mocked(get).mockImplementation(() =>
createResponsePromise(
'<!DOCTYPE html><title>404</title>',
{ status: 404, statusText: 'Not Found' }
)
);
await expect(get(url).text()).rejects.toThrow('Not Found');
expect(get).toHaveBeenCalledTimes(1);
expect(get).toHaveBeenCalledWith(url);
});
Check examples/node and examples/web.
Related Skills
node-connect
336.9kDiagnose OpenClaw node connection and pairing failures for Android, iOS, and macOS companion apps
frontend-design
83.0kCreate distinctive, production-grade frontend interfaces with high design quality. Use this skill when the user asks to build web components, pages, or applications. Generates creative, polished code that avoids generic AI aesthetics.
openai-whisper-api
336.9kTranscribe audio via OpenAI Audio Transcriptions API (Whisper).
commit-push-pr
83.0kCommit, push, and open a PR
