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
awaitinstead 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-fetchif Node.js >= 16.15 < 18.0 ⚠️ node-fetch is not supported with @tkrotoff/fetch >= 0.17 due toRequestclass 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.