Axios: How to use Axios with TypeScript when using response interceptors (AxiosResponse issue)

Created on 30 Apr 2018  ·  50Comments  ·  Source: axios/axios

Summary

In a project I am migrating to TypeScript (TS), I have a response interceptor r => r.data. How do I inform TS that I am not expecting a type of AxiosResponse? I've tried overriding using as Array<... but that doesn't work as AxiosResponse can't be cast as an Array (e.g. does not has .length).

Thanks!

Context

  • axios version: 0.16.2
  • Environment: Visual Studio Code

Most helpful comment

I override AxiosResponse in my axios.d.ts:

import axios from 'axios'

declare module 'axios' {
  export interface AxiosResponse<T = any> extends Promise<T> {}
}

All 50 comments

Use the axios.request<T>(...args) style definition.
The last Response-interceptor in the array implicitly comply to an interface like (currentResponse: any) => T

So if you have data being something like:

interface ServerResponse {
  data: ServerData
}

interface ServerData {
  foo: string
  bar: number
}

Then you can say:

axios.request<ServerData>({
  url: 'https://example.com/path/to/data',
  transformResponse: (r: ServerResponse) => r.data
}).then((response) => {
  // `response` is of type `AxiosResponse<ServerData>`
  const { data } = response
  // `data` is of type ServerData, correctly inferred
})

@zcei ooh actually this wouldn't work for global interceptors e.g. axios.interceptors.response.use

I think it does - the interceptors are currently very "relaxed" typed (aka any), so you can just attach the response interceptor and have it (r: any): any => r.data (which is basically as if you'd omit any typing in non-strict mode).

Only at the point where you call axios.request you can configure it's generic, which then get's passed through as the type of the data property.

(Actually I made a mistake in above code snippet, data is actually AxiosResponse<ServerData> which has a field called .data with the type ServerData - updated it)

@zcei hi sorry but this really doesn't work with my example, i've created a codesandbox to highlight this issue.

Check it out here

Having the same issue, the interceptor can essentially be boiled down to:

this.instance.interceptors.response.use(response => response.data);

Despite this, the return type of

this.instance.post<string>(...);

is AxiosPromise<string>, which expects the data to be wrapped.

This is intentionally. You are supposed to transform the data in the interceptor, but not to hoist response keys.

https://github.com/axios/axios/blob/0b3db5d87a60a1ad8b0dce9669dbc10483ec33da/lib/core/dispatchRequest.js#L63-L67

I thought you would have a server response like

{
  "data": {
    "foo": 42
  }
}

and wanted to get rid of the response.data.data nesting. If you return response.data in the interceptor, then you can later access it via response.data.foo instead of response.data.data.foo.

But accessing response.foo wouldn't work, as this is the "root" response level that keeps track of other stuff, like the response code and alike.

Sorry, but I don't agree with your premise, at least not functionality-wise.
Without using the interceptor to unwrap the data property, I would have to do

this.instance.post<string>(...).then(response => response.data);
this.instance.get<number>(...).then(response => response.data);

in _every_ endpoint that I define. What's the point in all that code repetition?

Whenever a request is successful, which having a then means, I don't really care about the response code etc. I only care about response codes if something _didn't_ work, and that's what error handlers are there for, both on the interceptor as well as on specific endpoints.

this.instance.interceptors.response.use(response => {
  // Everything went well, pass only relevant data through
  return response.data;
}, error => {
  // Something went wrong, figure out how to handle it here or in a `.catch` somewhere down the pipe
});

Whenever a request is successful, which having a then means, I don't really care about the response code

Well, you might not care for any other information, but constraining to everyone using an HTTP client that you only care about the body is not really a solution.
There are enough legit use-cases for status codes & headers even on successful calls. Like making distinctions on 204 vs 200, checking rate limit headers, extracting Link headers for additional resources (pagination), etc. pp.

If you don't care, wrap Axios and throw away everything:

this.instance.request = <T>(...args) => {
  return axios.request<T>(...args).then(({ data }) => data)
}

this.instance.request<string>({ method: 'post', ... }).then(data => { ... });
this.instance.request<number>({ method: 'get', ... }).then(data => { ... });

I don't see where you got the notion that everyone should be doing things the way I do them in one project. I'm simply trying to solve a problem of how to automatically unwrap data on every request and given that this issue was here before me, I'm not the only one with that problem.

Interceptors _seem_ like the correct place to do it, both by their name and their description in the readme (do something with every response). Handling 204 vs 200 etc and then passing the data would also make sense in an interceptor because then you get to do it in one central place.

In my opinion, the intuitive solution would be to return what ever you want from the interceptor, not have the library force a single way.

If you want to transform some fields in data:

this.instance.interceptors.response.use(response => {
  response.data.foo = JSON.parse(response.data.bar);
  return response;
});

If you want to just unwrap Axios' data:

this.instance.interceptors.response.use(response => response.data);

This would leave the choice of what to do to the developer, which in my opinion is better than a very strongly opinionated interceptor. You're free to disagree, of course.

You were reasoning about "the point in all that code repetition", so I just tried to explain why the information is necessary.

I can't really find a way on how you'd like to keep type safety in the interceptors and the response, if any interceptor could scramble the whole response without a common structure. :/
The response type for a request would need to become any I guess and put the effort into developers hands to make sure they do the correct thing.

That to me at least sounds more error prone due to the relaxed typings, than accessing one property.

If you can come up with a PR that allows interceptors to overwrite the root response while keeping type safety for "the 80% use-cases", I'd be more than happy!

The thing is, both of the above examples I made already work the way I described, functionality wise, it is just the types that are wrong. If returning anything else than the (modified) response in the interceptor is wrong, I think it would be good to update the expected types there.

First it's interesting to know you are doing the same thing @Etheryte !

In our app the r => r.data is the final response interceptor in the chain and we use others which rely on status codes to handle refresh tokens etc. but at a global level as there is no need for us to handle this for specific calls.

I understand that it might just not be possible to accommodate this use case with TS. However, @zcei it's undeniable that this is a legitimate way to use Axios as it is using an official part of the Axios API (interceptors) :).

Doh! I've been looking at the wrong code all the time 🤦‍♂️ and was in the transformResponse section, not the interceptor - I'm so sorry!

How would you update the typings to account for the interceptor changing the return type?

Maybe something like this?

interface AxiosInstance {
  request<T = any, R = AxiosResponse<T>> (config: AxiosRequestConfig): Promise<R>;
}

You'd call it like this:

axios.request<User, string>({
  method: 'get',
  url: '/user?id=12345'
})
  .then((foo: string) => { // <-- you could leave out the type annotation here, it's inferred
    console.log(foo)
  })

Just tried that out locally and seems to do what you're looking for.
Maybe I can get a PR together in the evening to update that for all the methods.

@zcei That looks good! Defaulting to AxiosResponse of course makes sense 99% of the time 👍

Great to hear! Sorry again for my confusion 😓

@zcei not a problem! Out of interest what is the release cycle for Axios?

There is none - I still have some pending things for the v1 alpha (#1333) and in the meantime @nickuraltsev / @emilyemorehouse are doing releases whenever necessary.
Since v0.18 there has been some traction (including my favorite: scoping options to instances) so I guess they could cut a release. For more I'd just kindly invite you over to Gitter, so that we keep the issues on point 🙂

I’ve been hoping to get out a 0.19 release but last I checked, master was failing CI. I’d definitely like a more solid/regular release schedule once we get 1.0 to land.

meet the same problem. Are there any solutions?

@qidaneix you could try installing npm install axios/axios#master until there is a release. #1605 should have fixed it. Would be nice to get some feedback, whether that really helps with real life use-cases and not just tests 🙂

@zcei i'll try it out tomorrow

@zcei May I ask when it will release?

+1 when 1.0.0 will release?

This will be a pre-1.0 release for sure. @Khaledgarbaya did you get added to NPM by Matt as well? Otherwise we need to be nice to the remaining maintainers releasing it 😉

Hey all. I'm currently managing the NPM releases. I'd love to issue another pre-1.0.0 release, but the Travis tests are failing and I haven't had a chance to fix them yet. Once they're fixed, I'm more than happy to get this out immediately 😬

@zcei I was not added to the npm repo, I can only merge changes

Is there any update on this issue, will there be a release in the near future, like month-two?

+1

❤️ if this could be released now

We are aware, but blocked currently. 🙁 You can get more information over here: https://github.com/axios/axios/issues/1657#issuecomment-410766031

Released as part of 0.19.0-beta.1.

This can be installed using npm install [email protected] or npm install axios@next

Maybe something like this?

interface AxiosInstance {
  request<T = any, R = AxiosResponse<T>> (config: AxiosRequestConfig): Promise<R>;
}

You'd call it like this:

axios.request<User, string>({
  method: 'get',
  url: '/user?id=12345'
})
  .then((foo: string) => { // <-- you could leave out the type annotation here, it's inferred
    console.log(foo)
  })

Just tried that out locally and seems to do what you're looking for.
Maybe I can get a PR together in the evening to update that for all the methods.

User(T, the first generic param) seems not used, if I want to use custom return types, I looks strange 😕

axios.request<void, string>({
    ...
})

using void may be more clear.

@emilyemorehouse Not to sound ungrateful but 0.19-beta has been open for three months now, is there an ETA for a GA release? Our project has an issue which requires one of these fixes. I asked this in the Gitter channel but doesn't seem like maintainers respond over there...

Perhaps a better wording would be what are the issues that need to be resolved before a release and is there a place to track it? Since there seems to be a lot of interest, spreading the work out and having a clear understanding of the required work could help speed things up.

There's already a project milestone for 0.19 but the tickets listed haven't seen any change for several months.

const func1: any = () => { return axios.request(...) }

I override AxiosResponse in my axios.d.ts:

import axios from 'axios'

declare module 'axios' {
  export interface AxiosResponse<T = any> extends Promise<T> {}
}

Bumping this issue. It works... okay when I just copied AxiosInstance definition to local typings, but the implemented solution is very verbose in my opinion, unless I'm doing something wrong (not a Typescript expert). Since I'm using a separate axios instance created with axios.create and using this interceptor:

AxiosClient.interceptors.response.use(
  response => response && response.data,
  error => error && error.response && error.response.data
);

where response.data always has this form:

export interface APIResponse<T = any> {
  data: T;
  message: string;
  status: boolean;
}

it seems like I have to use AxiosClient.post like this:

AxiosClient.post<EndpointAPIResponse, APIResponse<EndpointAPIResponse>>('/endpoint', { someData });

to have proper types in .then. Am I doing something wrong here or should it really be THAT verbose? If not, it would be much better if I could just pass expected response schema when creating the instance, but I can't make it work:

export interface AxiosInstance<R> {
  <T = any>(config: AxiosRequestConfig): Promise<R<T>>;
  <T = any>(url: string, config?: AxiosRequestConfig): Promise<R<T>>;
  defaults: AxiosRequestConfig;
  interceptors: {
    request: AxiosInterceptorManager<AxiosRequestConfig>;
    response: AxiosInterceptorManager<R>;
  };
  getUri(config?: AxiosRequestConfig): string;
  request<T = any>(config: AxiosRequestConfig): Promise<R<T>>;
  get<T = any>(url: string, config?: AxiosRequestConfig): Promise<R<T>>;
  delete<T = any>(url: string, config?: AxiosRequestConfig): Promise<R<T>>;
  head<T = any>(url: string, config?: AxiosRequestConfig): Promise<R<T>>;
  post<T = any>(url: string, data?: any, config?: AxiosRequestConfig): Promise<R<T>>;
  put<T = any>(url: string, data?: any, config?: AxiosRequestConfig): Promise<R<T>>;
  patch<T = any>(url: string, data?: any, config?: AxiosRequestConfig): Promise<R<T>>;
}

export interface AxiosStatic extends AxiosInstance<AxiosResponse> {
  create<T = AxiosResponse>(config?: AxiosRequestConfig): AxiosInstance<T>;
  Cancel: CancelStatic;
  CancelToken: CancelTokenStatic;
  isCancel(value: any): boolean;
  all<T>(values: (T | Promise<T>)[]): Promise<T[]>;
  spread<T, R>(callback: (...args: T[]) => R): (array: T[]) => R;
}

It works fine with axios.create() without a generic type or just axios, but if I pass my interface like this:

const AxiosClient = axios.create<APIResponse>({
  // ...
});

and then use it like this: AxiosClient.post<string>('/endpoint').then(value => value.data), value.data has type... T. Plus the version above only works if I actually replace these typings in node_modules, otherwise it gets totally mixed up and I end up with some total disaster. Could anyone help me out with this?

Edit: okay, I guess it won't work because it's not possible to use generics in that way (so R<T> when R is a generic type is not correct syntax but I guess WebStorm for some reason didn't highlight it for me); https://github.com/Microsoft/TypeScript/issues/1213 this, I assume, would solve it but no idea if it ever gets implemented.

const request = <T>(options: AxiosRequestConfig) => {
    return axios.request<IResponse<T>>(options).then<T>(response => {
        const data = response.data;
        if (data.c !== '0') {
            return Promise.reject(new Error(data.m || 'error'));
        }
        return data.d;
    });
}

use:

request<IApiGetBrandGoodsInfoByIds>({
        method: 'GET',
        url: '/api/a/b',
    });

@zcei Is this resolved yet? Am not able to get following to work:

// so I created an axios instance:
const instance = axios.create(someOptions);

// then used interceptors
instance.interceptors.response.use((res) => res.data.data);   // server response will always have 'data'

// then when using the following to make a request
instance.get<string>(someURI);  // suppose server response was {data: 'some message'}

// ^^ the above returns type AxiosPromise<string>. How do I get it to return type Promise<string> instead?

having same problem, i install version 0.19beta,also ts cannot parse the correct type

I override AxiosResponse in my axios.d.ts:

import axios from 'axios'

declare module 'axios' {
  export interface AxiosResponse<T = any> extends Promise<T> {}
}

oh niupi

I use interceptor to do something like: response => response.data. So I need to remove the AxiosResponse wrapper completely.

I finally end up with:

import axios from 'axios'

declare module 'axios' {
  export interface AxiosInstance {
    request<T = any> (config: AxiosRequestConfig): Promise<T>;
    get<T = any>(url: string, config?: AxiosRequestConfig): Promise<T>;
    delete<T = any>(url: string, config?: AxiosRequestConfig): Promise<T>;
    head<T = any>(url: string, config?: AxiosRequestConfig): Promise<T>;
    post<T = any>(url: string, data?: any, config?: AxiosRequestConfig): Promise<T>;
    put<T = any>(url: string, data?: any, config?: AxiosRequestConfig): Promise<T>;
    patch<T = any>(url: string, data?: any, config?: AxiosRequestConfig): Promise<T>;
  }
}

In case someone doesn't know how to use it:

  1. Write the types somewhere, e.g. src/types/axios/axios.d.ts.
  2. Update your tsconfig.json, e.g.
{
  "compilerOptions": {
    "typeRoots": [
        "./node_modules/@types",
        "./src/types/",
    ]
  }
}

This works for me:

Api.boards.createBoard = jest.fn((name:string) => {
      const mockBoardResult = {
        created_on: mockBoardData.date,
        name,
        threads: [],
        updated_on: mockBoardData.date,
        _id: mockBoardData._id
      };
      return Promise.resolve({data:mockBoardResult}) as Promise<AxiosResponse<any>>
    });

another way to override

import * as axios from 'axios'

declare module 'axios' {
  interface AxiosInstance {
    (config: AxiosRequestConfig): Promise<any>
  }
}

i think we maybe should't add property to AxiosResponse when use interceptors, because interceptors can be eject.

// @axios-exts/add-foo-to-resp
declare module 'axios' {
  interface AxiosResponse<T = any> {
    foo: string
  }
}
const addFooToResp = (res: AxiosResponse) => {
  res.foo = 'bar'
  return res
}
export default addFooToResp
// Your Project
import axios from 'axios'
import addFooToResp from '@axios-exts/add-foo-to-resp'

var id = axios.interceptors.response.use(addFooToResp)

axios.get('xx').then(res => {
  // have type defined
  // because we use `declare module 'axios'` ts can infer type
  console.log(res.foo)
})

// but if then eject?
axios.interceptors.response.eject(id)
axios.get('xx').then(res => {
  // also have type defined, it's maybe not reasonable
  console.log(res.foo)
})

why we use typescript? because we hope our project will be safely.
if some day we remove a property from base utils, we would like the code that references it to produce an error at compile time.

so, we want use interceptor add some property to AxiosResponse and have type inference, it's contradictory, because there is no way to ensure that type inference can be updated when interceptor is eject

i think axios should tell user:
the interceptor should handle something that has no effect on AxiosResponse, if you want to extends AxiosResponse property and have type inference, you should like 'plugin'

// @axios-exts/add-foo-to-resp
declare module 'axios' {
  interface AxiosResponse<T = any> {
    foo: string
  }
}
const addFooToResp = (res: AxiosResponse) => {
  res.foo = 'bar'
  return res
}
export default (axios) => {
  axios.interceptors.response.use(addFooToResp)
}
// Your Project
import axios from 'axios'
import addFooToResp from '@axios-exts/add-foo-to-resp'

addFooToResp(axios)

it's not 100% safety, but it's safety than just use axios.interceptors.response.use

and i recommend axios desgin a plugin system, now we always see like

import axios from 'axios'
import wrapper from 'axios-cache-plugin'

let http = wrapper(axios, {
  maxCacheSize: 15
})
export default http

use like wrapper to connect a plugin to axios, every plugin don't have a common rule, it's not elegant enough. i think should like

import axios from 'axios'
import axiosCache from 'axios-cache-plugin'

axios.use(axiosCache, {
  // some options
})
export axios
// axios-cache-plugin
export default (axios) => {}
// or
export default {
  install(axios){}
}
// like Vue.use for Vue.js

Use the axios.request<T>(...args) style definition.
The last Response-interceptor in the array implicitly comply to an interface like (currentResponse: any) => T

So if you have data being something like:

interface ServerResponse {
  data: ServerData
}

interface ServerData {
  foo: string
  bar: number
}

Then you can say:

axios.request<ServerData>({
  url: 'https://example.com/path/to/data',
  transformResponse: (r: ServerResponse) => r.data
}).then((response) => {
  // `response` is of type `AxiosResponse<ServerData>`
  const { data } = response
  // `data` is of type ServerData, correctly inferred
})

Pls Can U Tell Me How I Can Add Header And Config In Axios Request With tsx

@MoZiadAlhafez

declare module 'axios' {
  interface AxiosRequestConfig {
    useCache: boolean
  }
}

But this is not recommended

This is a long story, but seems to have been solved in #1605. See https://github.com/axios/axios/issues/1510#issuecomment-396894600.

If I misunderstood something, better to open a new issue with references to here. Thanks.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

kalaspuffar picture kalaspuffar  ·  36Comments

Donovantxy picture Donovantxy  ·  58Comments

alborozd picture alborozd  ·  220Comments

kyeotic picture kyeotic  ·  64Comments

mzabriskie picture mzabriskie  ·  54Comments