HTTP Client Factory Code Refactoring

Dhemy

October 27, 2024 - 2 min read

I’m working on a Proxy/Adapter project, which we’ll call “Banana.” Banana is developed in TypeScript and uses Axios for HTTP requests. It’s designed to communicate with different APIs, each with distinct authentication methods and configurations.

The system started with a single API, “PeelAPI,” for which we provided a createClient function that returned an Axios instance with the correct configuration. When we added a second API, “BunchAPI,” we created another createClient function for it, but in a different subdomain. The same was done for the third API, “SplitAPI.” For each Axios instance, we wrapped errors in a custom error class to identify the source of the error, accomplished by a response interceptor.

One API, one createClient—simple, right? Fast forward to three APIs, and now it’s like juggling bananas 🍌🍌🍌 with TypeScript.

The issue is that we now have a lot of duplicated code, and the createClient functions break the Single Responsibility Principle, as they are responsible for both creating the client and handling authentication. I’m uncertain whether it’s best to keep error wrapping within the same function or move it to a separate module after the client is created.

To add complexity, since client tokens are cached in Redis, we need to pass the Cache service to the createClient function, although not all clients require token caching. This results in different signatures for each createClient function.

I’m considering using a Factory pattern for client creation. The plan is to unify or standardize the client creation functions so they have a consistent signature, allowing us to replace them with a single Factory function that creates clients based on configuration.

I discovered that baseURL is the only configuration that varies between clients so far. I’m unsure whether it’s best to accept a configuration object or just baseURL as a parameter. I chose baseURL, as it’s easier to handle only when needed than creating a configuration object with a single property.

Last week, I began refactoring the “PeelAPI” client and ended up with a createClient(baseURL: string): AxiosInstance function, which returns an Axios instance with the correct configuration. I then moved on to “BunchAPI,” which was less straightforward because, unlike “PeelAPI,” “BunchAPI” requires authentication. I had to extract the authentication logic into a separate function. I didn’t change the signature of the createClient function, planning to address that in the next merge request.

Once standardized, Factory Pattern will take the wheel. But the question remains: what’s the cleanest way to handle authentication? 🤔 #CodeDesign

The first merge request is already merged, but I’m waiting for the second one to be reviewed. Once it’s merged, I will standardize the createClient functions, ensuring that the first two API clients have a consistent signature. Then, the third API client will be refactored in two steps: first to address authentication, then to standardize the signature.

After all createClient functions are standardized, I’ll create the Factory function to generate clients based on configuration. I still need to determine a clean way to handle authentication. Do you have any suggestions?

© 2021 - 2024 . imdhemy.com Published by Jekyll, hosted on GitHub Pages.