Caution
This project was ill-conceived, but taught me a lot about how I wanted to structure my packages and other projects going forward.
Useful any time your project needs to consume JSON from some endpoint.
I love unfetch because it reminds me
of my dog it's such a lightweight no-nonsense fetch library. This package is a
small wrapper around unfetch geared specifically for fetching JSON and sharing
configuration across the application. Specifically:
- TypeScript support
- Default method is
POST
(unless using SWR) - Sugar functions for GET/POST/PUT/DELETE methods
content-type
header is set toapplication/json
- Cookies are NOT sent along with the rest of the request (configurable)
- Request bodies can be anything serializable and will be stringified with
JSON.stringify
- Response bodies are automatically parsed with
JSON.parse
fetch()
will not reject if a non-ok response is received (configurable)- Simple differentiation between 2xx responses and non-2xx non-ok responses
- Exports functions to get and set app-wide configuration
- Sugar function for simple integration with SWR
npm install isomorphic-json-fetch
import { fetch } from 'isomorphic-json-fetch'
const URL = 'api/endpoint';
let res, json, error;
// 1. With zero configuration
({ json } = await fetch(URL)); // <== json will be undefined on non-2xx responses
doSomethingWith(json.myData);
// 2. With simple error handling and typed JSON
try {
({ res, json } = await fetch.get<{ myData: number }>(URL));
if(!json) return handleErr(`response code outside 200-299: ${res.status}`);
doSomethingWith(json.myData);
}
catch(e) {
// Could be a JSON parse error or a network issue
handleErr(`fetch failed: ${e}`);
}
// 3. Explicitly capturing typed JSON from non-2xx responses (e.g. 404, 500)
const configuration = { // <== any configs can also be set globally, see below
method: 'POST',
body: { query: 'some-string' }
};
try {
({ json, error } = await fetch<{ myData: number }, { message: string }>(URL, configuration));
// error is undefined on 2xx responses; json is undefined on non-2xx responses
if(error) return handleErr(error.message);
doSomethingWith(json.myData);
}
catch(e) {
// Could be a JSON parse error or a network issue
handleErr(`fetch failed: ${e}`);
}
// 4. Handling non-2xx responses as exceptions in your own catch block instead
try { doSomethingWith((await fetch.post<{ myData: number }>(URL, { rejects: true })).json.myData) }
catch(e) {
// ! Could be a JSON parse error or a network issue OR if the
// ! status code is not between 200-299!
handleErr(`fetch failed: ${e}`);
}
// 5. As a quick little fetcher for SWR
const { data, error } = useSwr(URL, fetch.swr);
// Equivalent to the following:
// ... = useSwr(URL, (url: string) => fetch(url, { swr: true }));
if(error) return <div>failed to load</div>
if(!data) return <div>loading...</div>
return <div>hello #{data.myData}!</div>
// When using SWR, it's best to set fetch configuration globally (below)
See unfetch for possible
configuration values. Additionally, you can add rejects: true
to your config
to cause the promise returned by fetch()
to reject on non-2xx HTTP responses.
By default, the promise returned by fetch()
won't reject on HTTP error status
even if the response is an HTTP 404 or 500. Instead, it will resolve normally,
and it will only reject on network failure or if anything prevented the request
from completing. See
the unfetch docs for more
information.
For use with SWR, add swr: true
to your
config and return a higher order function manually or just use the fetch.swr
sugar (see example above).
If you're using fetch()
across many files in a more complex project, you can
set a global configuration once and it will be used by all fetch()
calls
automatically:
import { fetch, setGlobalFetchConfig } from 'isomorphic-json-fetch'
const URL = 'api/endpoint';
// This sets a new default configuration object for all fetch calls
setGlobalFetchConfig({
method: 'DELETE', // ? POST is the default
credentials: 'include', // ? 'same-origin' by default (no cookies sent!)
// content-type header is included by default so no need to add it yourself!
});
// All the following now use the new global config
let { json } = await fetch(URL); // <== Sends a DELETE request
// You can always override default/global config by providing your own
({ json } = await fetch(URL, { // <== Sends a PUT request
method: 'PUT',
// `headers` and `credentials` keys were not overridden, so their values are
// inherited from global config like normal
}));
// This will ignore any errors thrown by `JSON.parse()`
({ json } = await fetch(URL, { method: 'GET', ignoreParseErrors: true }));
json === undefined // true
// TypeScript support for defining json return type and error return type
({ json } = await fetch.get<'technically valid JSON', { error: string }>(URL));
// Also, if you want to use the normal unfetch/node-fetch isomorphically, like
// for stream support in Node, it can be imported via `unfetch`
import { fetch, unfetch } from 'isomorphic-json-fetch'
Instead of passing method choice through a configuration option, this package provides several sugar functions:
import { fetch } from 'isomorphic-json-fetch'
const { json: get } = await fetch.get('/app?t=1');
const { json: post } = await fetch.post('/app?t=2', { body: { create: true }});
const { json: put } = await fetch.put('/app?t=3', { body: { newData: 'yes' }});
const { json: del } = await fetch.delete('/app?t=4');
Documentation can be found under docs/
and can be built with
npm run build-docs
.
New issues and pull requests are always welcome and greatly appreciated! 🤩 But that's not the only way to contribute! Just as well, you can star 🌟 this project to let me know you found it useful! Thank you for your support ✊🏿
This repository uses a CI/CD
semantic-release
pipeline for vetting PRs and publishing releases. Be sure to
checkout the develop
branch (not
main
) and, when you're ready, fearlessly submit your PR against develop
.
The pipeline will take care of the rest 🚀🚀🚀
Run npm run list-tasks
locally to see which of the following scripts are
available for this project.
Using these scripts requires a linux-like development environment. None of the scripts are likely to work on non-POSIX environments. If you're on Windows, use WSL.
npm run repl
to run a buffered TypeScript-Babel REPLnpm test
to run the unit tests and gather test coverage data- Look for HTML files under
coverage/
- Look for HTML files under
npm run check-build
to run the integration testsnpm run check-types
to run a project-wide type checknpm run test-repeat
to run the entire test suite 100 times- Good for spotting bad async code and heisenbugs
- Uses
__test-repeat
NPM script under the hood
npm run dev
to start a development server or instancenpm run generate
to transpile config files (underconfig/
) from scratchnpm run regenerate
to quickly re-transpile config files (underconfig/
)
npm run clean
to delete all build process artifactsnpm run build
to compilesrc/
intodist/
, which is what makes it into the published packagenpm run build-docs
to re-build the documentationnpm run build-externals
to compileexternal-scripts/
intoexternal-scripts/bin/
npm run build-stats
to gather statistics about Webpack (look forbundle-stats.json
)
npm run start
to start a production instance
npx sort-package-json
to consistently sortpackage.json
npx npm-force-resolutions
to forcefully patch security audit problems
You don't need to read this section to use this package, everything should "just work"!
This is a dual UMD (CJS2)/ES module package. That means this package exposes both UMD+CJS2 and ESM entry points and can be used in most JavaScript environments (browsers, any current or LTS Node version, etc).
Loading this package via require(...)
will cause Node and modern browsers to
use the CJS2 bundle entry point, disable tree shaking in
Webpack 4, and lead to larger bundles in Webpack 5. Alternatively, loading this
package via import { ... } from ...
or import(...)
will cause Node and
modern browsers to use the ESM entry point in versions that support
it, in Webpack, and in the browser. Using the import
syntax
is the modern, preferred choice.
For backwards compatibility with Webpack 4 and Node versions < 14,
package.json
retains the module
key, which
points to the ESM entry point, and the main
key, which
points to both the ESM and CJS2 entry points implicitly (no file extension). For
Webpack 5 and Node versions >= 14, package.json
includes the
exports
key, which points to both entry points explicitly.
Though package.json
includes
{ "type": "commonjs"}
, note that the ESM entry points are ES
module (.mjs
) files. package.json
also includes the
sideEffects
key, which is false
for optimal tree
shaking, and the types
key, which points to a TypeScript
declarations file.
This package does not maintain shared state and so does not exhibit the dual package hazard. However, setting global configuration may not actually be "globally" recognized by third-party code importing this package.