Brice Chaponneau
Brice Chaponneau

Reputation: 602

Typescript : Create custom type axios instance

I want to create a complex type name api with an axios instance so that I can use the basic instance like this:

api.get("url")...

But also to be able to have new dynamic bodies such as:

api.myApi.get("url")

And finally, be able to read the list of instances like this:

api.list

Must be return

myApi

When I want to extend AxiosInstance or inject properties to the main instance, I always get a type error. Any idea?

My test :

type apiInstances = Record<string, AxiosInstance>
type apiList = Record<'list', string[]>

let api: apiInstances | apiList


const instance = (baseURL: string) =>
  axios.create({
    baseURL
  })

const instances = { instance('myApi') }

api = { ...instances, ...instance(''), list }

Error when I write api.myApi.get("...")

Property 'myApi' does not exist on type 'apiInstances | apiList'

Upvotes: 0

Views: 7272

Answers (4)

seveibar
seveibar

Reputation: 4943

You can use the typed-axios-instance package to take routes and convert them into a fully typed axios instance:

import type { TypedAxios } from "typed-axios-instance"
import axios from "axios"

// Need help generating these routes? You can generate them from...
// nextlove: https://github.com/seamapi/nextlove
// openapi: TODO
type Routes = [
  {
    route: "/things/create"
    method: "POST"
    jsonBody: {
      name?: string | undefined
    }
    jsonResponse: {
      thing: {
        thing_id: string
        name: string
        created_at: string | Date
      }
    }
  }
]

const myAxiosInstance: TypedAxios<Routes> = axios.create({
  baseURL: "http://example-api.com",
})

// myAxiosInstance now has intelligent autocomplete!

Upvotes: 1

Brice Chaponneau
Brice Chaponneau

Reputation: 602

I have found solution :

type Api = {
  [key: string]: AxiosInstance
  list: never
}

let api: Api

That work like a charm

Upvotes: -1

Chalom.E
Chalom.E

Reputation: 697

I think you're on the good way. The best thing you can do is to abstract axios client as you would do with another http client and hide axios implementation. For this, you can make a class for instead


export class HttpClient extends HttpMethods {
  _http: AxiosInstance;

  constructor() {
    super();
    this._http = axios.create({
      ...this._options,
      validateStatus: status => status >= 200 && status < 400,
    });
  }

  setAdditionnalHeaders(headers: object, override?: boolean): void {
    this._options = _.merge({}, override ? {} : this._options, { headers });
  }

   public async get<T>(path: string, params?: any, headers?: object): Promise<Result<T>> {
    if (headers) {
      this.setAdditionnalHeaders(headers, true);
    }
    const result = await this._http({
      method: 'GET',
      url: path,
      params,
      headers: this._options,
      ...this.hydrateConfig(this._config),
    });
    return result;
  }
}


export abstract class HttpMethods {
     public _http: any;

  protected _options: object;
public abstract get<T>(path: string, params?: any, headers?: object): Promise<Result<T>>;
}

And then play with chainable functions where you will inject your class which hide that axios is use in this.

export function httpBuilder<I extends HttpMethods>(client: I): IHttpBuilder {
  return {
    ...httpRequestMethods(client),
  };
}

function httpRequestMethods(instance: HttpMethods): BuilderMethod {
  const { config } = instance;
  return {
    get<T>(path: string, params?: any, headers?: object): ChainableHttp & HttpExecutableCommand<T> {
      return {
        ...executableCommand<T>(path, instance, 'GET', requests, null, params, headers),
      };
    },
}

function executableCommand<T>(
  path: string,
  instance: HttpMethods,
  commandType: CommandType,
  requests: RequestType[],
  data?: any,
  params?: any,
  headers?: object,
): HttpExecutableCommand<T> {
  return {
    async execute(): Promise<Result<T>> {
      const result = await getResolvedResponse<T>(path, instance, commandType, data, params, headers);
      return result;
    },
  };
}

async function getResolvedResponse<T>(
  path: string,
  instance: HttpMethods,
  commandType: CommandType,
  data?: any,
  params?: any,
  headers?: object,
): Promise<Result<T>> {
  let result: Result<T>;
  if (commandType === 'GET') {
    result = await instance.get<T>(path, params, headers);
  }
  return result;
}

This is an example for help if you want to perform extra feature on your http client whether is axios, fetch or whatever you want :-)

Upvotes: 1

Profesor08
Profesor08

Reputation: 1258

https://www.typescriptlang.org/docs/handbook/utility-types.html#recordkeystype

type ApiKeys = "myApi" | "yourApi";
type Api = Partial<Record<ApiKeys, AxiosInstance>>;

Upvotes: 0

Related Questions