Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/tkrotoff/fetch
A small Fetch API wrapper
https://github.com/tkrotoff/fetch
fetch fetch-api http http-client http-request request whatwg-fetch
Last synced: 17 days ago
JSON representation
A small Fetch API wrapper
- Host: GitHub
- URL: https://github.com/tkrotoff/fetch
- Owner: tkrotoff
- License: mit
- Created: 2019-02-24T10:30:19.000Z (over 5 years ago)
- Default Branch: master
- Last Pushed: 2023-07-06T13:58:39.000Z (over 1 year ago)
- Last Synced: 2024-10-12T07:10:55.020Z (about 1 month ago)
- Topics: fetch, fetch-api, http, http-client, http-request, request, whatwg-fetch
- Language: TypeScript
- Homepage:
- Size: 1.46 MB
- Stars: 61
- Watchers: 3
- Forks: 9
- Open Issues: 2
-
Metadata Files:
- Readme: README.md
- Changelog: CHANGELOG.md
- License: LICENSE
Awesome Lists containing this project
README
# @tkrotoff/fetch
[![npm version](https://badge.fury.io/js/%40tkrotoff%2Ffetch.svg)](https://www.npmjs.com/package/@tkrotoff/fetch)
[![Node.js CI](https://github.com/tkrotoff/fetch/workflows/Node.js%20CI/badge.svg?branch=master)](https://github.com/tkrotoff/fetch/actions)
[![Test Coverage](https://api.codeclimate.com/v1/badges/67aaf07dd7577e2ef340/test_coverage)](https://codeclimate.com/github/tkrotoff/fetch/test_coverage)
[![Bundle size](https://badgen.net/bundlephobia/minzip/@tkrotoff/fetch)](https://bundlephobia.com/package/@tkrotoff/fetch)
[![Prettier](https://img.shields.io/badge/code_style-prettier-ff69b4.svg)](https://github.com/prettier/prettier)
[![Airbnb Code Style](https://badgen.net/badge/code%20style/airbnb/ff5a5f?icon=airbnb)](https://github.com/airbnb/javascript)A [Fetch](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API) 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](https://github.com/nodejs/undici) & [whatwg-fetch](https://github.com/github/fetch))
- Written in TypeScript## Why?
When using Fetch, you must write [some](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch#Uploading_JSON_data) [boilerplate](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch#Checking_that_the_fetch_was_successful):
```JavaScript
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:
```JavaScript
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)## 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`
```JavaScript
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](src/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-fetch`](https://nodejs.org/docs/latest-v16.x/api/cli.html#--experimental-fetch) if Node.js >= 16.15 < 18.0
- ⚠️ [node-fetch](https://github.com/node-fetch/node-fetch) is not supported with @tkrotoff/fetch >= 0.17 due to [`Request` class limitations](https://github.com/node-fetch/node-fetch/blob/v3.3.1/README.md#class-request)Check [examples/node](examples/node)
### Browsers
Check [examples/web](examples/web)
## API
- `get(input:` [`RequestInfo`](https://fetch.spec.whatwg.org/#requestinfo)` | URL, init?:` [`RequestInit`](https://fetch.spec.whatwg.org/#requestinit)`): ResponsePromiseWithBodyMethods`
- `post(input: RequestInfo | URL, body?:` [`BodyInit`](https://fetch.spec.whatwg.org/#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`](https://fetch.spec.whatwg.org/#response)`): boolean`
`ResponsePromiseWithBodyMethods` being `Promise<`[`Response`](https://fetch.spec.whatwg.org/#response)`>` with added methods from [`Body`](https://fetch.spec.whatwg.org/#body-mixin).
### HttpError
@tkrotoff/fetch throws [`HttpError`](src/HttpError.ts) with [`response`](https://fetch.spec.whatwg.org/#response) and [`request`](https://fetch.spec.whatwg.org/#request) properties when the HTTP status code is < `200` or >= `300`.
### Test utilities
- `createResponsePromise(body?:` [`BodyInit`](https://fetch.spec.whatwg.org/#bodyinit)`, init?:` [`ResponseInit`](https://fetch.spec.whatwg.org/#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`](src/HttpStatus.ts) and write more explicit code:
```TypeScript
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.
```TypeScript
import { defaults } from '@tkrotoff/fetch';defaults.init.mode = 'cors';
defaults.init.credentials = 'include';
```## Testing
When testing your code, use `createResponsePromise()` and `createJSONResponsePromise()`:
```TypeScript
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(
'404',
{ 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`:
```TypeScript
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(
'404',
{ 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](examples/node) and [examples/web](examples/web).