Skip to content

HTTP client based on native fetch with middleware and a bit more

Notifications You must be signed in to change notification settings

glebcha/extended-fetch

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

85 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

  • No dependencies
  • Middleware can be applied for all requests or single
  • Requests can be aborted by timeout
  • Modular architecture
  • Typescript
  • ESM + UMD
  • Tiny size < 4KB

React + Typescript sandbox:

Edit extended-fetch

Browser sandbox:

Edit extended-fetch

Install:

npm i extended-fetch

Interface:

type MiddlewareType = 'request' | 'response';
type MiddlewareHandlers = Array<MiddlewareHandler | unknown>;
type Middleware = {
  [key in MiddlewareType]?: MiddlewareHandlers
};
type MiddlewareHandler = (params: RequestInit) => Promise<RequestInit>;

type CreateMethod = {
  query?: unknown,
  url?: string,
  baseUrl?: string,
  timeout?: number,
  middleware?: Middleware,
  params?: RequestInit,
  method?: "GET" | "POST" | "PUT" | "PATCH" | "DELETE",
  format?: (property) format?: "formData" | "text" | "blob" | "json" | "arrayBuffer">,
}

More info about params can be found on MDN

Basic usage:

const api = createHttpClient();

api.get('/api/books/12')
 .then((jsonFormattedResponse) => {})
 .catch(async (pureResponseInstance) => {
  const { status, statusText } = pureResponseInstance;

  return { status, statusText };
 });

Basic error handling:

const api = createHttpClient();

api.get('/api/books/12')
 .then((jsonFormattedResponse) => {})
 .catch(async (pureResponseInstance) => {
  const {
    status,
    statusText,
    formattedResponse: response
  } = pureResponseInstance;

  return { status, statusText, response };
 });

Custom response type:

const api = createHttpClient();

interface Book {
  id: string,
  description?: string,
}

api.get<Book>('/api/books/12')
 .then(({ id, description }) => {})
 .catch(async (pureResponseInstance) => {
  const { status, statusText } = pureResponseInstance;

  return { status, statusText };
 });

Abort request by timeout:

const api = createHttpClient();

api.get({ url: '/api/books/12', timeout: 10000 });

Abort with signal passed:

const api = createHttpClient();
const abortController = new AbortController();

api.get({
  url: '/api/books/12',
  params: { signal: abortController.signal }
});

setTimeout(() => abortController.abort(), 5000);

Middleware:

function setAuthHeader(options) {
  const headers = { ...options.headers, Authorization = 'Bearer shdkjhf798798jsjjs' };

  return Object.assign(options, { headers });
}

function collectErrors(response) {
  const { errors = [] } = response;
  const errorText = errors.reduce((merged, { message }) =>  `${merged}${message}`, '');

  response.errorText = errorText;

  return response;
}

const middleware = {
  request: [setAuthHeader],
  response: [collectErrors]
};
const api = createHttpClient({ middleware });

api.post({
  url: '/api/books',
  query: { text: 'New record' }
  middleware: {
    request: [
      (options) => ({ ...options, format: 'text' })
    ]
  }
});

Auth Middleware:

import { createHttpClient, initAuthMiddleware } from 'extended-fetch';

const middleware = {
  request: [
    setRequestHeaders: async (options) => options,
  ],
  response: [
    initAuthMiddleware({
      url: async () => 'refresh',
      getTokens: () => ({ accessToken: '', refreshToken: '' }),
      setTokens: () => {},
      handleAuthError: (error) => console.log(error.message),
    }),
  ],
};
const api = createHttpClient({ middleware });

export const getPerson = (id: number) => {

  return api
    .get({ url: `person/${id}` })
    .catch((error) => {
      const errorResponse = {
        status: 'error',
        code: error.status ?? 0
      };

      return errorResponse;
    });
};

Utilities:

  • Check data type
  • Array
  • AbortSignal
  • AbortController
  • AsyncFunction
  • Boolean
  • Date
  • Function
  • Headers
  • Number
  • Null
  • Object
  • Promise
  • String
  • Symbol
import { is } from 'extended-fetch';

const year = is.Date(unknownVar) ? unknownVar.getFullYear() : null;

console.log({ year });
  • Safely stringify JSON
import { safeJsonStringify } from 'extended-fetch';

const stringifiedJson = safeJsonStringify({ data: ['test'] });
const stringifiedJsonPretty = safeJsonStringify({ pretty: { data: 'test' } }, 2);

console.log({ stringifiedJson, stringifiedJsonPretty });
  • Extend fetch headers
import { applyHeaders } from 'extended-fetch';

const authHeaders = { 'Authorization': `Bearer ${tokens.accessToken}` };

applyHeaders(authHeaders, responseHeadersFromFetchInstance);

console.log(responseHeadersFromFetchInstance);

About

HTTP client based on native fetch with middleware and a bit more

Resources

Stars

Watchers

Forks

Packages

No packages published