You may not need Axios
This is not an attack on Axios .
Rather, it’s advocacy for the fetch
API which has become quite capable. 🦄
Overview
This article is a collection of the “missing” fetch
code snippets and common use cases I wish were easier to find.
Is your Use Case not listed? Let me know ✉️
Feature Comparison
fetch axios request Intercept request and response ✅ ✅ ✅ Transform request and response data ✅ ✅ ✅ Cancel requests ✅ ✅ ❌ Automatic transforms for JSON data ✅ ✅ ✅ Client side support for protecting against XSRF ✅ ✅ ✅ Progress ✅ ✅ ✅ Streaming ✅ ✅ ✅ Redirects ✅ ✅ ✅
When starting this article (late 2018, updated 2024) I assumed I’d end with a table of mixed check boxes. Surely there are special Use Cases which justified axios
, request
, r2
, superagent
, got
, etc.
Well, as it turns out, I overestimated the need for 3rd party http libraries.
Despite using fetch
for several years (including for non-trivial tasks: file uploads & error/retry support) I still had misconceptions of fetch
’s abilities and limits.
Well, let’s check out what fetch
can do…
Fetch Recipes
Get JSON from a URL
fetch ( ' https://api.github.com/orgs/nodejs ' )
. then ( response => response. json ())
console. log (data) // result from `response.json()` above
. catch ( error => console. error (error))
fetch ( ' https://api.github.com/orgs/nodejs ' , {
' User-agent ' : ' Mozilla/4.0 Custom User Agent '
. then ( response => response. json ())
. catch ( error => console. error (error))
HTTP Error Handling
const isOk = response => response.ok ? response. json () : Promise . reject ( new Error ( ' Failed to load data from server ' ))
fetch ( ' https://api.github.com/orgs/nodejs ' )
. then (isOk) // <= Use `isOk` function here
console. log (data) // Prints result from `response.json()`
. catch ( error => console. error (error))
CORS example
CORS is primarily checked at the server - so make sure your configuration is correct on the server-side.
The credentials
option controls if your cookies are automatically included.
fetch ( ' https://api.github.com/orgs/nodejs ' , {
credentials : ' include ' , // Useful for including session ID (and, IIRC, authorization headers)
. then ( response => response. json ())
console. log (data) // Prints result from `response.json()`
. catch ( error => console. error (error))
Posting JSON
postRequest ( ' http://example.com/api/v1/users ' , {user : ' Dan ' })
. then ( data => console. log (data)) // Result from the `response.json()` call
function postRequest ( url , data ) {
credentials : ' same-origin ' , // 'include', default: 'omit'
method : ' POST ' , // 'GET', 'PUT', 'DELETE', etc.
body : JSON . stringify (data), // Use correct payload (matching 'Content-Type')
headers : { ' Content-Type ' : ' application/json ' },
. then ( response => response. json ())
. catch ( error => console. error (error))
Posting an HTML <form>
postForm ( ' http://example.com/api/v1/users ' , ' form#userEdit ' )
. then ( data => console. log (data))
function postForm ( url , formSelector ) {
const formData = new FormData (document. querySelector (formSelector))
method : ' POST ' , // 'GET', 'PUT', 'DELETE', etc.
body : formData // a FormData will automatically set the 'Content-Type'
. then ( response => response. json ())
. catch ( error => console. error (error))
To post data with a Content-Type of application/x-www-form-urlencoded
we will use URLSearchParams
to encode the data like a query string.
For example, new URLSearchParams({a: 1, b: 2})
yields a=1&b=2
.
postFormData ( ' http://example.com/api/v1/users ' , {user : ' Mary ' })
. then ( data => console. log (data))
function postFormData ( url , data ) {
method : ' POST ' , // 'GET', 'PUT', 'DELETE', etc.
body : new URLSearchParams (data),
' Content-type ' : ' application/x-www-form-urlencoded; charset=UTF-8 '
. then ( response => response. json ())
. catch ( error => console. error (error))
Uploading a file
postFile ( ' http://example.com/api/v1/users ' , ' input[type="file"].avatar ' )
. then ( data => console. log (data))
function postFile ( url , fileSelector ) {
const formData = new FormData ()
const fileField = document. querySelector (fileSelector)
formData. append ( ' username ' , ' abc123 ' )
formData. append ( ' avatar ' , fileField.files[ 0 ])
method : ' POST ' , // 'GET', 'PUT', 'DELETE', etc.
body : formData // Coordinate the body type with 'Content-Type'
. then ( response => response. json ())
. catch ( error => console. error (error))
Uploading multiple files
Setup a file upload element with the multiple
attribute:
< input type = ' file ' multiple class = ' files ' name = ' files ' />
Then use with something like:
postFile ( ' http://example.com/api/v1/users ' , ' input[type="file"].files ' )
. then ( data => console. log (data))
function postFile ( url , fileSelector ) {
const formData = new FormData ()
const fileFields = document. querySelectorAll (fileSelector)
// Add all files to formData
Array .prototype.forEach. call (fileFields.files, f => formData. append ( ' files ' , f))
// Alternatively for PHPeeps, use `files[]` for the name to support arrays
// Array.prototype.forEach.call(fileFields.files, f => formData.append('files[]', f))
method : ' POST ' , // 'GET', 'PUT', 'DELETE', etc.
body : formData // Coordinate the body type with 'Content-Type'
. then ( response => response. json ())
. catch ( error => console. error (error))
Timeouts
Here’s a generic Promise timeout, using the “Partial Application” pattern. It’ll work with any Promise interface. Don’t do too much work in the supplied promise chain, it will keep running - and any failures have a way of creating long term memory leaks.
function promiseTimeout ( msec ) {
const timeout = new Promise (( yea , nah ) => setTimeout (() => nah ( new Error ( ' Timeout expired ' )), msec))
return Promise . race ([promise, timeout])
promiseTimeout ( 5000 )( fetch ( ' https://api.github.com/orgs/nodejs ' ))
. then ( response => response. json ())
console. log (data) // Prints result from `response.json()` in getRequest
. catch ( error => console. error (error)) // Catches any timeout (or other failure)
////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////
fetchTimeout ( 5000 , ' https://api.github.com/orgs/nodejs ' )
// Alternative implementation:
function fetchTimeout ( msec , ... args ) {
return raceTimeout ( fetch ( ... args))
function raceTimeout ( promise ) {
const timeout = new Promise (( yea , nah ) => setTimeout (() => nah ( new Error ( ' Timeout expired ' )), msec))
return Promise . race ([promise, timeout])
And a more complex example, featuring a tracking flag __timeout
so you can intercept any costly work.
function promiseTimeout ( msec ) {
promise. then (() => isDone = true )
const timeout = new Promise (( yea , nah ) => setTimeout (() => {
nah ( new Error ( ' Timeout expired ' ))
return Promise . race ([promise, timeout])
promiseTimeout ( 5000 )( fetch ( ' https://api.github.com/orgs/nodejs ' ))
. then ( response => response. json ())
console. log (data) // Prints result from `response.json()` in getRequest
. catch ( error => console. error (error))
Download Progress Helper
Upload Progress is currently a bit buggy outside of Chrome.
The Progress Handler technique shown below avoids wrapping the fetch
call in a closure. 👍
progressHelper
has the following interface (source available below)
const progressHelper = require ( ' ./progressHelper.js ' )
const handler = ({ loaded , total }) => {
console. log ( `Downloaded ${ loaded } of ${ total } ` )
// handler args: ({ loaded = Kb, total = 0-100% })
const streamProcessor = progressHelper (handler)
// => streamProcessor is a function for use with the response _stream_
Let’s look at a usage example:
// The progressHelper could be inline w/ .then() below...
const streamProcessor = progressHelper (console.log)
fetch ( ' https://fetch-progress.anthum.com/20kbps/images/sunrise-progressive.jpg ' )
. then (streamProcessor) // note: NO parentheses because `.then` needs to get a function
. then ( response => response. blob ())
// ... set as base64 on an <img src="base64...">
A reusable image downloader might look like getBlob()
:
const getBlob = url => fetch (url)
. then ( progressHelper (console.log)) // progressHelper used inside the .then()
. then ( response => response. blob ())
By the way, a Blob
is a Binary Large Object.
It’s important to choose ONE of the 2 usage patterns below (they are functionally equivalent):
// OPTION #1: no temp streamProcessor var
. then ( progressHelper (console.log))
// OPTION #2: define a `streamProcessor` to hold our console logger
const streamProcessor = progressHelper (console.log)
My preference is Option #1
. However, your scope design may force you to use Option #2
.
Finally, here’s the last part of this recipe, our progressHelper
:
Source: Progress Helper
function progressHelper ( onProgress ) {
if ( ! response.body) return response
const contentLength = response.headers. get ( ' content-length ' )
const total = ! contentLength ? - 1 : parseInt (contentLength, 10 )
const reader = response.body. getReader ()
. then (({ done , value }) => {
if (done) return void controller. close ()
loaded += value.byteLength
onProgress ({ loaded, total })
controller. enqueue (value)
credit: Special thanks to Anthum Chris and his fantastic Progress+Fetch PoC shown here
Recursive Retry Helper
* A **Smarter** retry wrapper with currying!
function retryCurry ( fn , retriesLeft = 5 ) {
const retryFn = ( ... args ) => fn ( ... args)
. catch ( err => retriesLeft > 0
? retryFn (fn, retriesLeft - 1 )
const getJson = ( url ) => fetch (url)
. then ( response => response. json ())
const retryGetJson = retryCurry (getJson, 3 );
// Now you can pass any arguments through to your function!
retryGetJson ( ' https://api.github.com/orgs/elite-libs ' )
/** Basic retry wrapper for Promises */
function retryPromise ( fn , retriesLeft = 5 ) {
. catch ( err => retriesLeft > 0
? retryPromise (fn, retriesLeft - 1 )
const getJson = ( url ) => fetch (url)
. then ( response => response. json ())
retry (() => getJson ( ' https://api.github.com/orgs/elite-libs ' ))
Handling HTTP Redirects
const checkForRedirect = ( response ) => {
// Check for temporary redirect (307), or permanent (308)
if (response.status === 307 || response.status === 308 ) {
const location = response.headers. get ( ' location ' )
return Promise . reject ( new Error ( ' Invalid HTTP Redirect! No Location header. ' ));
// You can change the behavior here to any custom logic:
// e.g. open a "confirm" modal, log the redirect url, etc.
// Bonus: this will handle recursive redirects ✨
fetch ( ' https://api.github.com/orgs/elite-libs ' )
// Next line will handle redirects
. then ( response => response. json ())
Canceling a fetch request
const httpWithTimeout = ( timeout = 5000 , url ) => {
const controller = new AbortController ();
// Set an Nsec cancellation timeout
const timer = setTimeout (() => controller. abort (), timeout);
return fetch (url, { signal : controller.signal })
clearTimeout (timer); // not required but closes open ref
Compatibility
As of 2022, the fetch
API is widely supported in all modern browsers and in more recent versions of NodeJS v18+.
If you must support IE you can polyfill fetch with the github/fetch
package (maintained by an awesome team at GitHub). It’s possible to go as far back as IE8 - Your mileage may vary .
Earlier NodeJS can take advantage of the the fetch
API with the node-fetch
package:
After polyfill+node-fetch: 99.99% compatible ✅
Please Tweet at me if you have other Use Cases you’d like to see. ❤️