A wrapper around Fetch just for JSON
Why would you fetch anything but json? ;)
fetch-json is a lightweight JavaScript library to reduce the boilerplate code needed to make HTTP calls to JSON endpoints. The minified JS file is under 4 KB.
fetch-json automatically:
- Adds the HTTP header
content-type: application/jsonto ensure the correct data type - Runs
.json()on the response - Serializes the body payload with
JSON.stringify() - Appends
paramsto the URL ofGETrequests - Sets
credentialsto'same-origin'(support user sessions in frameworks like Grails, Rails, PHP, Django, Flask, etc.) - Converts the HTTP text response to JSON if it's not already JSON (convenient for handling HTTP errors)
- Maps HTTP response headers from a
HEADrequest into a simple object
fetch-json is ideal for a JAMstack architecture where "dynamic programming during the request/response cycle is handled by JavaScript, running entirely on the client".
In a web page:
<script src=fetch-json.min.js></script>or from the jsdelivr.com CDN:
<script src=https://cdn.jsdelivr.net/npm/fetch-json@3.5/dist/fetch-json.min.js></script>Install package for node:
$ npm install fetch-jsonand then import:
import { fetchJson } from 'fetch-json';Fetch the NASA Astronomy Picture of the Day:
// NASA APoD
const url = 'https://api.nasa.gov/planetary/apod';
const params = { api_key: 'DEMO_KEY', count: 1 };
const handleData = (data) =>
console.info('The NASA APoD for today is at:', data[0]?.url);
fetchJson.get(url, params).then(handleData);Example output:
> The NASA APoD for today is at:
> https://apod.nasa.gov/apod/image/2107/LRVBPIX3M82Crop1024.jpg
Create a resource for the planet Jupiter:
// Create Jupiter
const url = 'https://centerkey.com/rest/echo/';
const resource = { name: 'Jupiter', position: 5 };
const handleData = (data) =>
console.info('New planet:', data); //http response body as an object literal
fetchJson.post(url, resource).then(handleData);For more examples, see the Mocha specification suite:
spec/node.spec.js
(Mocha output for each build under Run npm test)
To see a website that incorporates fetch-json, check out DataDashboard:
data-dashboard.js.org π
Fetch from a test endpoint that responds with an HTTP error:
// handle HTTP status code 500
const url = 'https://centerkey.com/rest/status/500/'; //mock server error
const handleData = (data) => {
if (data.error)
console.error('HTTP Status Code:', data.status, data);
else
console.info('Valid JSON Data:', data);
};
fetchJson.get(url).then(handleData);Example output:
"HTTP Status Code:" 500
{
error: true,
ok: false,
status: 500,
message: 'Response not JSON',
contentType: 'text/plain;charset=UTF-8',
bodyText: 'Internal Server Error',
response: {
status: 500,
headers: Headers {
'access-control-allow-origin': '*',
'content-type': 'text/plain;charset=UTF-8',
'cache-control': 'public, max-age=30',
},
ok: false,
url: 'https://centerkey.com/rest/status/500/'
}
}If the endpoint is expected to return JSON, check the error field.Β
If the endpoint is expected to return text, CSV, XML, or HTML, check the ok field
or status field and read the payload from the bodyText field.
Example of using AbortSignal to set a timeout in the Fetch options:
// Force a timeout error by waiting for only 1 ms
const url = 'https://dna-dom.org/api/books/';
const abortSignal = globalThis.AbortSignal.timeout(1); //maximum impatience
const handleData = (data) =>
console.info('Haste makes waste:', data);
fetchJson.get(url, {}, { signal: abortSignal }).then(handleData);Output:
"Haste makes waste:" {
http: 'GET https://dna-dom.org/api/books/',
error: true,
ok: false,
status: 499,
message: 'Fetch API exception',
details: {
name: 'TimeoutError',
code: 23,
cause: null
},
contentType: null,
bodyText: 'TimeoutError: The operation was aborted due to timeout',
data: null,
response: null
}Check the details.name field to verify the exception type.
Fetch the NASA Astronomy Picture of the Day:
// NASA APoD
const show = async () => {
const url = 'https://api.nasa.gov/planetary/apod';
const params = { api_key: 'DEMO_KEY', count: 1 };
const data = await fetchJson.get(url, params);
console.info('The NASA APoD for today is at:', data[0]?.url);
};
show();Create a resource for the planet Jupiter:
// Create Jupiter
const url = 'https://centerkey.com/rest/echo/';
const create = async (resource) => {
const data = await fetchJson.post(url, resource);
console.info('New planet:', data); //http response body as an object literal
};
create({ name: 'Jupiter', position: 5 });fetch-json calls the native Fetch API.
For comparison, the POST example in section C) Examples to create a planet would be done calling the Fetch API directly with the code:
// Create Jupiter (WITHOUT fetch-json)
const url = 'https://centerkey.com/rest/echo/';
const resource = { name: 'Jupiter', position: 5 };
const options = {
method: 'POST',
headers: {
'content-type': 'application/json',
'accept': 'application/json',
},
body: JSON.stringify(resource),
};
const handleData = (data) =>
console.info(data); //http response body as an object literal
fetch(url, options)
.then(response => response.json())
.then(handleData);The example with fetch-json and the example without fetch-json each produce the same output.
The format for using fetch-json is:
fetchJson.get(url, params, options).then(callback);fetchJson.post(url, resource, options).then(callback);fetchJson.put(url, resource, options).then(callback);fetchJson.patch(url, resource, options).then(callback);fetchJson.delete(url, resource, options).then(callback);fetchJson.head(url, params, options).then(callback); //headers returned as an objectNotes:
- Only the
urlparameter is required.Β The other parameters are optional. - The
paramsobject forfetchJson.get()is converted into a query string and appended to theurl. - The
resourceobject is turned into the body of the HTTP request. - The
optionsparameter is passed through to the Fetch API (see theinitdocumentation on MDN).
If you need to programmatically set the method, use the format:
fetchJson.request(method, url, data, options).then(callback);Where method is 'GET', 'POST', 'PUT', 'PATCH', or 'DELETE', and data represents
either params or resource.
Turn on basic logging to the console with:
fetchJson.enableLogger();To use a custom logger, pass in a function that accepts 9 parameters to log.
To get an array containing the names of the parameters:
fetchJson.getLogHeaders();
// 'Timestamp', 'HTTP', 'Method', 'Domain', 'URL', 'Ok', 'Status', 'Type'The default console output looks like:
2018-09-12T07:20:12.372Z β "request" - "GET" β "api.nasa.gov" β "https://api.nasa.gov/planetary/apod"
2018-09-12T07:20:13.009Z β "response" - "GET" β "api.nasa.gov" β "https://api.nasa.gov/planetary/apod" - true - 200 - "application/json"
Turn off logging with:
fetchJson.enableLogger();Note
Protocol-Level vs. Application Level
HTTP status codes are a mess.Β
This is not an opinion β it's a fact.Β
This fact is proven out by the large number of HTTP status code questions on Stack overflow with multiple very popular directly conflicting answers.Β
The mutually exclusive answers will often each have hundreds of upvotes.Β
At its core this problem is due to the impedance mismatch between protocol-level status and application-level status.Β
The "P" in HTTP stands for protocol, and it's the wrong level to pass application level status codes.Β
Applications cannot reasonably be expected to conform to a fixed set of status codes defined back in the 1990s.
fetch-json returns the requested data as a simple object (JavaScript Object Literal) for any JSON response with an HTTP status code of 200.Β
All other responses are wrapped into a simple object so itβs straightforward to analyze the HTTP status code, "content-type" header, response "ok" field, and body payload as needed.
The HTTP response body is considered to be JSON if the content-type is
"application/json" or "text/javascript".Β
If the HTTP response body is not JSON, fetch-json passes back
through the promise an object with a bodyText string field containing response body text.
In addition to the bodyText field, the object will have the fields: ok, status,
contentType, and data.Β
If an HTML error response is JSON, the data will contain the parsed JSON.
For example, an HTTP response for an error status of 500 would be converted to an object similar to:
{
error: true,
ok: false,
status: 500, //INTERNAL SERVER ERROR
message: 'Response not JSON',
contentType: 'text/html; charset=utf-8',
bodyText: '<!doctype html><html lang=en><body>Server Error</body></html>',
data: null,
}Every response that is not JSON or is an HTTP error will be consistently formatted like the object above.Β With fetch-json you know the response will always be passed back to you as a consistent, simple object literal.
Use fetchJson.setBaseOptions() to configure options to be used on future fetchJson requests.
The example below sets the Authorization HTTP header so it is sent on the subsequent GET and
DELETE requests:
const url = 'https://dna-dom.org/api/books/3';
fetchJson.setBaseOptions({ headers: { Authorization: 'Basic WE1MIGlzIGhpZGVvdXM=' } });
fetchJson.get(url).then(display); //with auth header
fetchJson.delete(url); //with auth headerTo have multiple base options available at the same time, use the FetchJson class to instantiate
multiple copies of fetchJson:
import { FetchJson } from 'fetch-json';
const fetchJsonA = new FetchJson({ headers: { from: 'aaa@example.com' } }).fetchJson;
const fetchJsonB = new FetchJson({ headers: { from: 'bbb@example.com' } }).fetchJson;
const url = 'https://dna-dom.org/api/books/3';
fetchJsonA.get(url).then(display); //"from: aaa@example.com"
fetchJsonB.delete(url); //"from: bbb@example.com"See the TypeScript declarations at the top of the fetch-json.ts file.
The declarations provide type information about the API.Β
For example, the fetchJson.post() function returns a Promise for a FetchResponse:
fetchJson.post(url: string, resource?: RequestData,
options?: FetchOptions): Promise<FetchResponse>Example TypeScript for fetching a library book:
import { fetchJson, FetchJsonErrorResponse } from 'fetch-json';
type Book = {
id: number,
title: string,
author: string,
};
const url1 = 'https://dna-dom.org/api/books/3';
const handleData = (data: Book | FetchJsonErrorResponse) => {
const response = <FetchJsonErrorResponse>data;
const book = <Book>data;
if (response.error)
console.error('HTTP Status Code:', response.status, data);
else
console.info('Book Resource:', book);
};
fetchJson.get(url).then(handleData);JSDOM does not include fetch by default, so you need to add a polyfill.
$ npm install --save-dev whatwg-fetchTell JSDOM to load and run the polyfill file:
node_modules/whatwg-fetch/dist/fetch.umd.js
For a full example, see the usage of whatwg-fetch in spec/jsdom.spec.js.
π‘οΈ npm Security Aggregator
See the runScriptsConfig section of package.json for a clean way to organize build tasks:
- π
add-dist-headerβΒ Prepend a one-line banner comment (with license notice) to distribution files - π
copy-file-utilβΒ Copy or rename a file with optional package version number - π
copy-folder-utilβΒ Recursively copy files from one folder to another folder - πͺΊ
recursive-execβΒ Run a command on each file in a folder and its subfolders - π
replacer-utilβΒ Find and replace strings or template outputs in text files - π’
rev-web-assetsβΒ Revision web asset filenames with cache busting content hash fingerprints - π
run-scripts-utilβΒ Organize npm package.json scripts into groups of easy-to-manage commands - π¦
w3c-html-validatorβΒ Check the markup validity of HTML files using the W3C validator
