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
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
await
instead of two - No need to manually throw an exception on HTTP error status (like 404 or 500)
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.
@tkrotoff/fetch supports Node.js and modern browsers
- Nothing is needed if Node.js >= 18.0
- Use
--experimental-fetch
if Node.js >= 16.15 < 18.0 ⚠️ node-fetch is not supported with @tkrotoff/fetch >= 0.17 due toRequest
class limitations
Check examples/node
Check examples/web
-
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
.
@tkrotoff/fetch throws HttpError
with response
and request
properties when the HTTP status code is < 200
or >= 300
.
-
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
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;
@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';
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.