Skip to content

Tiny wrapper for DOM fetch with reuse-ability in mind using the builder pattern

License

Notifications You must be signed in to change notification settings

launchcodedev/fetch

Repository files navigation

Fetch

Tiny wrapper around DOM fetch for common API wrappings. Isomorphic (supports browsers and Node.js), if fetch is available or polyfilled.

Licensed under MPL 2.0 Build Status npm BundlePhobia

yarn add @lcdev/fetch@VERSION

Features:

  • Easy to use builder-style API
  • Quick JSON, blob and text parsing options
  • Shareable builders for common options (authorization headers, onResponse hooks, etc.)
  • No magic - call build() and pass to fetch if you want
  • TypeScript friendly
  • Tiny footprint (2kb)

If you are looking for something not available here, try ky-universal or axios.

There are two main functions exported by this package:

  1. The apiCall function, which is used for creating a one-off fetch request
  2. The api function, which creates a shared builder for many fetch requests

apiCall

The simplest function is apiCall, which sets up a fetch request.

import { HttpMethod, apiCall } from '@lcdev/fetch';

await apiCall('https://base-url.com/endpoint', HttpMethod.GET).json<TheResponseObject>();

This can be shortened by using the http method aliases exported by this package.

import { get } from '@lcdev/fetch';

await get('https://base-url.com/endpoint').json<TheResponseObject>();

There are get, post, put, patch, and remove aliases.

With a ApiCall builder (the object returned by apiCall), we can chain many options for the request.

  • withQuery(object, options?: SerializationOptions): adds query parameters, stringifying the object with query-string
  • withHeaders(Headers): adds headers to request
  • withHeader(key, value): adds a single header to the request
  • withContentType(string): changes the content-type header
  • withBearerToken(object: { token?: string }): adds Authorization: Bearer {token} header
  • withBody(object, isJson?: boolean, options?: SerializationOptions): adds a request body
  • withJsonBody(object, options?: SerializationOptions): adds JSON request body
  • withFormDataBody(FormData, options?: SerializationOptions): adds form-data request body
  • withURLEncodedBody(object, options?: SerializationOptions): adds 'application/x-www-form-urlencoded' request body
  • withExtraOptions(options: ExtraOptions): escape hatch to add extra options to fetch while still using the builder pattern
  • expectStatus(number): throw an error if the response status isn't the expected one
  • expectSuccessStatus(): throw an error if the response status isn't in 200 range
  • onPreBuild(callback): calls your function before options are built for every fetch
  • onResponse(callback): calls your function whenever responses are received
  • onJsonResponse(callback): calls your function whenever JSON responses are received
  • build(): constructs options that can be passed into fetch directly
  • json<T>(): calls fetch and parses response as JSON
  • jsonAndResponse<T>(): calls fetch and parses response as JSON, along with the full Response object
  • blob<T>(): calls fetch and parses response as a blob
  • blobAndResponse<T>(): calls fetch and parses response as a blob, along with the full Response object
  • text<T>(): calls fetch and parses response as text
  • textAndResponse<T>(): calls fetch and parses response as text, along with the full Response object

Because we expose build, there is always an escape hatch if you need something non-standard.

Note that fetch calls are lazy - meaning that nothing will run until you call .then or await it.

api

Most of the time, we make web apps that call APIs many times in different ways (endpoints, authorization, etc.). This package provides a way to share configuration easily between all calls, without being "global".

import { api } from '@lcdev/fetch';

const myBackend = api('https://base-url.com')
  .withBearerToken({ token: '...' })
  .onResponse((res) => {
    if (res.status === 401) logout();
  });

// we can re-use myBackend where we want to
// you might put myBackend in a React Context, or inject it into state management
await myBackend.get('/endpoint').json<TheResponseObject>();
await myBackend.post('/endpoint').withJsonBody({ foo: 'bar' }).json<TheOtherResponse>();

Here, myBackend is an Api object, which exposes ways to create ApiCalls (like above). You can perform the same builder functions on these as with apiCall.

You can add little callbacks to myBackend using onResponse or onJsonResponse. You might do this for logging, for business logic, etc.

You can change the base URL if required with changeBaseURL(path), though be warned that every request from then on will then be based on that.

NodeJS Support

Just polyfill fetch, and this package will work. Install cross-fetch package and add the following to your main file.

yarn add cross-fetch@3
import fetch from 'cross-fetch';
import { setGlobalFetch } from '@lcdev/fetch';

setGlobalFetch(fetch);

Client Certificates

Some API servers require a client TLS certificate to authenticate against their API. In NodeJS, you can do this using a custom HTTPS agent that is aware of the client certificate. Then you can use .withExtraOptions() to pass the custom agent to the fetch options

Note: agent is a non-standard option for node-fetch.

import * as https from 'https';

const myApi = api('https://base-url.com')
  .withBearerToken({ token: '...' })
  .withExtraOptions({
    agent: new https.Agent({
      pfx: myPfxClientCertificate,
    }),
  });

About

Tiny wrapper for DOM fetch with reuse-ability in mind using the builder pattern

Resources

License

Stars

Watchers

Forks

Packages

No packages published