Creating a customized Axios instance in Typescript can provide more flexibility and control over

I am looking to create an API with a customizable instance using Axios. Ideally, I want to be able to use a basic instance like this:

api.get("url")...

In addition, I would like to have the flexibility to add dynamic bodies and access them using something like:

api.myApi.get("url")

Lastly, being able to view a list of instances in this manner:

api.list

The expected return should be:

myApi

Whenever I try to extend AxiosInstance or inject properties into the main instance, I encounter a type error. Any suggestions on how to resolve this issue?

Below are my attempted solutions :

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

let api: apiInstances | apiList


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

const instances = { createInstance('myApi') }

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

Error message when trying api.myApi.get("..."):

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

Answer №1

It seems like you're heading in the right direction. One effective approach is to abstract the axios client just as you would with any other http client and conceal the axios implementation. To achieve this, consider creating a separate class for that purpose.


export class HttpClient extends HttpMethods {
  _http: AxiosInstance;

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

  setAdditionalHeaders(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.setAdditionalHeaders(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>>;
}

You can then explore chainable functions where your class, which conceals the use of axios, is injected into the process.

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 example serves as a guide to enhancing functionalities of your http client, whether it be axios, fetch, or any other choice you prefer :-)

Answer №3

If you want to make use of the typed-axios-instance package, you can utilize it to transform routes into a fully typed axios instance:

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

// If you need assistance with creating 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",
})

// Now, myAxiosInstance comes equipped with intelligent autocomplete!

Answer №4

I've discovered the remedy:

interface Service {
  [key: string]: AxiosInstance
  list: never
}

let services: Service

It operates flawlessly

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

What is the most efficient way to load data just once when a user reaches the bottom of the page?

I am encountering an issue with a webpage that dynamically loads HTML from a PHP script which scraps image links from another site when the user scrolls to the bottom of the page. The problem is that the scraping script takes some time to complete, causing ...

Unable to process despite clicking button (no error messages)

I'm in the process of setting up an options page for my very first extension. As a beginner in coding, I might have made some rookie mistakes along the way. However, I am facing challenges with basic functionality that was previously working seamlessl ...

TypeScript mandates the inclusion of either one parameter or the other, without the possibility of having neither

Consider the following type: export interface Opts { paths?: string | Array<string>, path?: string | Array<string> } The requirement is that the user must provide either 'paths' or 'path', but it is not mandatory to pa ...

Dropdown list remains open despite a selection being made

My problem arises when I attempt to select an item from the list, as the dropdown menu does not populate automatically and the list remains open. Here is the HTML code for the dropdown list: <label id='choose' for='options'>Sele ...

Utilize vue.js to cache and stream videos

I'm new to the world of vue.js and I'm facing a certain dilemma. I implemented a caching system using resource-loader that preloads my images and videos and stores the data in an array. Everything is functioning correctly, but now I'm unsur ...

The YouTube Iframe API encountered an issue while attempting to send a message to an HTTP recipient. The recipient's origin

Encountering an error when using the YouTube Iframe API on Safari 9.1, OS X Yosemite Issue: Unable to post message to http://www.youtube.com. Recipient has origin https://www.youtube.com This problem only occurs in Safari, as other browsers like Firefo ...

Utilizing JavaScript variables to generate a custom pie chart on Google

Greetings! I must admit that I am a novice, especially when it comes to JavaScript. My background is mainly in PHP. Recently, I came across a fantastic pie chart created by Google https://developers.google.com/chart/interactive/docs/gallery/piechart I a ...

Unable to adjust the text color to white

As someone who is new to Typescript, I'm facing a challenge in changing the text color to white and struggling to find a solution. I'm hoping that someone can guide me in the right direction as I've tried multiple approaches without success ...

Guide to setting up index.js with ApiProvider in the Redux Toolkit (RTK)

Have you ever considered customizing the index.js file in the root directory based on ChatGPT's recommendations? I'm not entirely convinced that it's the most common practice. What are your thoughts on this approach? // Here is an example of ...

Begin counting when a particular div element is visible on the screen

I have a plugins.init.js file that contains a try-catch block which runs on page load. I am looking for a way to execute this code only once when the div with the class counter-value comes into view. Is there a method to achieve this? try { const count ...

Decorators are not allowed in this context, the Angular component constructor may not include them

Currently working on developing a dialog component in Angular 17 using Angular Material 17 Encountering an issue inside the constructor of the dialog component where utilizing the @Inject decorator as shown in the official documentation example is not pos ...

Exploring the potential of React with Typescript: Learn how to maximize

Having some difficulties working with Amplitude in a React and Typescript environment. Anyone else experiencing this? What is the proper way to import Amplitude and initialize it correctly? When attempting to use import amp from 'amplitude-js'; ...

Conceal the <p> element when the user interacts with the internal href

After creating this document using JQuery, whenever the user clicks on different internal links, a div with id = "change" is loaded which effectively "erases" the content. My challenge at the moment is that while images are successfully deleted, text rema ...

Guide to configuring Winston logging with Sequelize correctly

Currently, I am setting up winston with Sequelize and have the code snippet below: const logger = winston.createLogger({ level: 'info', format: winston.format.json(), transports: [ new winston.transports.File({ filename: path. ...

Handling events for components that receive props from various components in a list

In my code, I have a component called PrivateReview which includes event handlers for updating its content. export default function PrivateReview(props) { const classes = useStyles(); const removeReviewAndReload = async () => { await ...

Using Ionic to send email verification via Firebase

I have encountered an issue while attempting to send an email verification to users upon signing up. Even though the user is successfully added to Firebase, the email verification is not being sent out. Upon checking the console for errors, I found the f ...

When an array object is modified in Vue, it will automatically trigger the get method to validate any

One of the challenges I am facing is related to a button component that has a specific structure: <template> <button class="o-chip border-radius" :class="{ 'background-color-blue': theValue.isSelected, ...

AngularJS - A guide on adding a new property to every object within an array

I'm looking to update each item in an array by pushing a scope variable into it. This is my current code: $scope.ProcessExcel = function (data) { //Read the Excel File data. var workbook = XLSX.read(data, { type: 'bin ...

How to ensure that the iframe is completely loaded before proceeding with Selenium JavaScript

I have a webpage that displays an iframe, within the iframe there is a spinning spinner (overlying the fields). My approach involves using selenium to navigate to the iframe, return this.driver.wait(function() { return self.webdriver.until.ableToSw ...

The use of custom loaders alongside ts-node allows for more flexibility

Is it possible to utilize ts-node with a custom loader? The documentation only mentions enabling esm compatibility. ts-node --esm my-file.ts I am attempting to implement a custom loader for testing an ESM module, but I prefer not to rely on node for compi ...