12

I'm using the node-fetch npm module and have a helper function for making authorized requests to a third party service (basically just middleware for adding in the Authorization header).

async function makeAuthorizedRequest(url: string, options: RequestInit) {
  if (!options) {
    options = { headers: {} as HeadersInit }
  }
  if (!options.headers) {
    options.headers = {} as HeadersInit
  }
  options.headers.Authorization = `Bearer ${access_token}`
  if (!options.headers['User-Agent']) {
    options.headers['User-Agent'] = USERAGENT
  }
  return fetch(url, options)
}

The RequestInit type is defined as having a headers property of type HeadersInit defined as the following

export type HeadersInit = Headers | string[][] | { [key: string]: string };

I get two distinct errors in my IDE (VSCode) and tsc refuses to compile this because

Property 'Authorization' does not exist on type 'Headers'.ts(2339)

and

Element implicitly has an 'any' type because expression of type '"User-Agent"' can't be used to index type 'HeadersInit'.
  Property 'User-Agent' does not exist on type 'HeadersInit'.ts(7053)

Now obviously "User-Agent" and "Authorization" are valid http headers, and from my understanding the type {[key: string]: string} definition should allow for this since "User-Agent" and "Authorization" are strings and their values are being set as strings. Why is tsc not able to see this and how can I actually fix it?

(I've used //@ts-ignore on the affected lines in the past, but I'd like to understand what its concerned about and how to fix this in the future - because having ts-ignore all over the codebase does not look professional)

3 Answers 3

7

Here is the solution:

const headersInit: HeadersInit = {};
options.header = headersInit;

Generally you want to avoid Type Assertion (as) if possible.

Alternative solution: if you know options.headers is neither a Headers or a string[][] you can do this:

options.headers = {} as { [key: string]: string }

or the equivalent

options.headers = {} as Record<string, string>
Sign up to request clarification or add additional context in comments.

Comments

7

I solved these errors by realizing HeadersInit and Headers are two different types. So if someone is facing a similar issue, try something like this:

const headers = options?.headers ? new Headers(options.headers) : new Headers();
    
if (!headers.has("Authorization")) {
    headers.set("Authorization", `Bearer ${authToken}`);
}

It's not exactly the same as the question, but I thought this was worth sharing.

1 Comment

This answer solved my problem. Learned that HeadersInit and Headers are two different things. Thanks!
2

The HeadersInit type is not defined with ways to change the items. But it is defined to be compatible with string[][], Record<string, string>, and Headers. Using a Headers object that you can easily manipulate as a substitute for a HeadersInit literal object, your code could work rewritten as:

  async function makeAuthorizedRequest(url: string, options: RequestInit) {
    // Redefining is unnecessary because the parameter must be a defined RequestInit.
    //if (!options) {
    //  options = {} as RequestInit;
    //}
    // A Headers object can be assigned to HeadersInit.
    // const means the variable is immutable, not its content.
    const reqHeaders = new Headers(options.headers);
    // Use `set` in case a header already exists in the collection.
    reqHeaders.set('Authorization', `Bearer ${access_token}`);
    if (!reqHeaders.get('User-Agent')) {
      reqHeaders.set('User-Agent', USERAGENT);
    }
    // Use the updated headers collection in the request.
    options.headers = makeHeaders;
    return fetch(url, options);
  }

I have assumed you had access_token and USERAGENT assigned already.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.