eRodY
eRodY

Reputation: 695

Set default header for every fetch() request

Is it possible, using the fetch API, to set default headers for every single request?
What I want to do is set an Authorization header whenever there is a json web token in the localStorage. My current solution is to set the headers with this function:

export default function setHeaders(headers) {
    if(localStorage.jwt) {
        return {
            ...headers,
            'Authorization': `Bearer ${localStorage.jwt}`
        }
    } else {
        return headers;
    }
}

Setting the headers in a fetch request would then look like this:

return fetch('/someurl', {
        method: 'post',
        body: JSON.stringify(data),
        headers: setHeaders({
            'Content-Type': 'application/json'
        })
    })

But there has to be a better way to do this. I'm currently developing a React/Redux/Express app if that is of any help.

Upvotes: 64

Views: 56929

Answers (6)

iuridiniz
iuridiniz

Reputation: 2433

You can override default fetch api:

var originalFetch = window.fetch;
window.fetch = function (input, init) {
    if (!init) {
        init = {};
    }
    if (!init.headers) {
        init.headers = new Headers();
    }

    // init.headers could be: 
    //   `A Headers object, an object literal, 
    //    or an array of two-item arrays to set request’s headers.`
    if (init.headers instanceof Headers) {
        init.headers.append('MyHeader', 'Value');
    } else if (init.headers instanceof Array) {
        init.headers.push(['MyHeader', 'Value']);
    } else {
        // object ?
        init.headers['MyHeader'] = 'Value';
    }
    return originalFetch(input, init);
};

References:

Notes:

  • Despite I would not advise you to override the global fetch API, I already did this to include a header as a quickly and temporary fix to a production problem.
  • The best solution is to write your fetch like function that will always add your header and use it instead of original fetch. See other answers in this thread.

Upvotes: 6

L.Blondy
L.Blondy

Reputation: 468

2024 answer

I would not advise you to override the global fetch API, you could run into issues if other libs or services depend on fetch.

Making a fetch wrapper is the way to go. In the following I'll assume that we want to preserve all fetch API behaviours.

First step: create a helper to merge the headers

Since the headers of the fetch API can be passed either as:

  • an object
  • an entries array (eg [['key1', 'val1'], ['key2', 'val2']])
  • an Headers instance

We will need a mergeHeaders helper. I'll pass on the details but this is a working implementation:

function mergeHeaders (...headerInits) {
   let result = {}
   headerInits.forEach((init) => {
      new Headers(init).forEach((value, key) => {
         if (value === 'null' || value === 'undefined') {
            // same as object spread: undefined overrides the current value
            // 'null' and 'undefined' got stringified in the process and are not valid headers values
            // therefore in this case we can remove the header
            delete res[key]
         } else {
            // add the header
            res[key] = value
         }
      })
   })
   return result
}

Second step: create the new fetcher

we can now proceed to implement a basic fetcher

function fetcher(input, options) {
    // your headers
    const defaultHeaders = { Authorization: localStorage.getItem('auth-header') }
    // merge them with the headers of the options
    const headers = mergeHeaders(defaultHeaders, options.headers)
    // add the headers to the options
    return fetch(input, { ...options, headers })
}

Third step: fix the autocomplete

If you don't use typescript you'll need to add a bit of jsdocs

jsdocs are type annotations that make use of the typescript lsp under the hood. They will enable autocomplete

/**
 * @type {typeof fetch}
 */
function fetcher(input, options) {
    // your headers
    const defaultHeaders = { Authorization: localStorage.getItem('auth-header') }
    // merge them with the headers of the options
    const headers = mergeHeaders(defaultHeaders, options.headers)
    // add the headers to the options
    return fetch(input, { ...options, headers })
}

Here we go, we have a fetcher with default headers!

If your use case starts becoming a bit more complex, consider using a library. I recently published my fetch API configuration library, called up-fetch

Here is a quick example using up-fetch

const fetcher = up(fetch, () => ({
    headers: { Authorization: localStorage.getItem('auth-header') }
}))

This achieves basically the same thing with a few differences: the response is automatically parsed, and an error is thrown if response.ok is false. There are many more features like params as objects, baseUrl config, validation adapters, interceptors...

Hope that helps

Upvotes: 3

Alex from Jitbit
Alex from Jitbit

Reputation: 60712

A quick and unrecommended hack is to redefine the default .fetch() function:

const oldFetch = window.fetch;
window.fetch = function() {
    arguments[1].headers = { 'blahblah' : 'blabla' };
    return oldFetch.apply(window, arguments);
}

Code is untested and unfinished. If you decide to use this answer, check arguments.length, add code to preserve existing headers, etc. etc. I'm just providing the direction for further exploration.

Upvotes: 2

Nantaphop
Nantaphop

Reputation: 564

Andri Möll created a FetchDefaults.js mixin for fetch that sets fetch defaults:

var Url = require("url")
var assign = require("oolong").assign
var merge = require("oolong").merge
var PARSE_QUERY = false
var PROTOCOL_RELATIVE = true // Enable //example.com/models to mimic browsers.

exports = module.exports = function(fetch, rootUrl, defaults) {
  if (typeof rootUrl === "string") rootUrl = parseUrl(rootUrl)
  else defaults = rootUrl, rootUrl = null
  return assign(exports.fetch.bind(null, fetch, rootUrl, defaults), fetch)
}

exports.fetch = function(fetch, rootUrl, defaults, url, opts) {
  if (rootUrl != null) url = rootUrl.resolve(url)
  if (typeof defaults === "function") defaults = defaults(url, opts)
  return fetch(url, opts == null ? defaults : merge({}, defaults, opts))
}

function parseUrl(url) {
  return Url.parse(url, PARSE_QUERY, PROTOCOL_RELATIVE)
}

Distributed under AGPL-3.0-only license

Upvotes: 10

underblob
underblob

Reputation: 966

Creating a fetch wrapper could solve your problem:

function updateOptions(options) {
  const update = { ...options };
  if (localStorage.jwt) {
    update.headers = {
      ...update.headers,
      Authorization: `Bearer ${localStorage.jwt}`,
    };
  }
  return update;
}

export default function fetcher(url, options) {
  return fetch(url, updateOptions(options));
}

You also get the added benefit of being able to switch your request client easily for all the calls in your application if you decide you like Axios or other package better. And you can do other things like check if options.body is an object and add the 'Content-Type: application/json header.

Upvotes: 40

Hugo Chevalier
Hugo Chevalier

Reputation: 139

You could use Axios instead of fetch, with Interceptors

const setAuthorization = (token) => {

  api.interceptors.request.use((config) => {
    config.headers.Authorization = 'Bearer ' + token;
    return config;
  });

}

Where Api is an axios Object with a base URL

const api= axios.create({
  baseURL: 'http://exemple.com'
});

And when you get your token, u just have to call the function setAuthorization.

Source: Axios README.md

Upvotes: 9

Related Questions