Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

tkrotoff/fetch

Open more actions menu

Repository files navigation

@tkrotoff/fetch

npm version Node.js CI Test Coverage Bundle size Prettier Airbnb Code Style

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 await instead of two
  • No need to manually throw an exception on HTTP error status (like 404 or 500)

Usage

Examples:

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

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.

Morty Proxy This is a proxified and sanitized view of the page, visit original site.