DEV Community

Cover image for ✨ Build a Better Axios Client in NestJS
amir fakoor
amir fakoor

Posted on • Edited on

✨ Build a Better Axios Client in NestJS

When building back-end services with NestJS, making reliable and observable HTTP requests is crucial, especially when dealing with third-party APIs or microservice communication.

In this article, we’ll create a reusable AdvancedAxiosService that wraps Axios with:

  • Request timing
  • Retry capabilities
  • Logging for success, retry, and failure scenarios

Let's get started. 💡


🎯 Why Use a Custom Axios Service?

Axios is a powerful HTTP client, but out of the box, it lacks advanced features like:

  • Built-in retries for failed requests
  • Logging request metadata such as latency
  • Unified interface for all request types

With NestJS, we can wrap Axios into a service that includes all of these features and inject it wherever we need.


📦 Install Dependencies

We’ll use axios-retry, a lightweight library that automatically retries failed Axios requests with customizable logic.

Before diving into the code, make sure you have axios and axios-retry installed in your project:

npm install axios axios-retry
Enter fullscreen mode Exit fullscreen mode

Or with yarn:

yarn add axios axios-retry
Enter fullscreen mode Exit fullscreen mode

🛠️ The Service Implementation

In this section, we’re creating a custom injectable service in NestJS that wraps the Axios HTTP client with enhanced functionality. This includes automatic retries for failed requests using axios-retry, and detailed logging for request duration and outcomes.

import { Injectable, Logger } from '@nestjs/common'
import axios, { AxiosInstance, AxiosRequestConfig, AxiosResponse } from 'axios'
import axiosRetry from 'axios-retry'

@Injectable()
export class AdvancedAxiosService {
  private readonly axiosInstance: AxiosInstance
  private readonly logger = new Logger(AdvancedAxiosService.name)

  constructor() {
    this.axiosInstance = axios.create()

    // Track request start time
    this.axiosInstance.interceptors.request.use((config) => {
      ;(config as any).metadata = { startTime: new Date() }
      return config
    })

    // Setup retry logic, see more details of options: https://github.com/softonic/axios-retry?tab=readme-ov-file#options.
    axiosRetry(this.axiosInstance, {
      retries: 3,
      retryDelay: (retryCount) => retryCount * 1500,
      shouldResetTimeout: true,
      retryCondition: () => true,
      onRetry: (retryCount, error, requestConfig) => {
        const metadata = (requestConfig as any).metadata
        const duration = new Date().getTime() - metadata.startTime.getTime()
        this.logger.warn(
          `Retry #${retryCount} for request to ${requestConfig.url} failed. Error: ${error.message}. Duration: ${duration}ms`,
        )
      },
    })

    // Log response duration
    this.axiosInstance.interceptors.response.use(
      (response: AxiosResponse) => {
        const metadata = (response.config as any).metadata
        const duration = new Date().getTime() - metadata.startTime.getTime()
        this.logger.log(
          `Successful ${response.config.method?.toUpperCase()} request to ${response.config.url}. Status: ${
            response.status
          }. Duration: ${duration}ms`,
        )
        return response
      },
      (error) => {
        const metadata = (error.config as any)?.metadata
        if (metadata) {
          const duration = new Date().getTime() - metadata.startTime.getTime()
          this.logger.error(
            `Failed ${error.config.method?.toUpperCase()} request to ${error.config.url}. Error: ${
              error.message
            }. Duration: ${duration}ms`,
          )
        }
        return Promise.reject(error)
      },
    )
  }

  async request<T = any>(config: AxiosRequestConfig): Promise<T> {
    const response = await this.axiosInstance.request<T>(config)
    return response.data
  }

  async get<T = any>(url: string, config?: AxiosRequestConfig): Promise<T> {
    return this.request<T>({ ...config, method: 'GET', url })
  }

  async post<T = any>(url: string, data?: any, config?: AxiosRequestConfig): Promise<T> {
    return this.request<T>({ ...config, method: 'POST', url, data })
  }
}


Enter fullscreen mode Exit fullscreen mode

Here’s a breakdown of what we’re adding:

  • Axios instance creation: Instead of using Axios globally, we create an isolated instance so we can apply custom configuration and interceptors without side effects.
  • Request interceptor: Captures the start time of each request, so we can later calculate how long it took to complete (latency).
  • Retry configuration: Automatically retries failed requests up to 3 times with a 1.5-second incremental delay between each attempt. This is useful for handling temporary network issues or rate limits.
  • Response interceptor: Logs successful responses with method, URL, status code, and latency.
  • Error handler: Logs failed requests with relevant metadata, including error messages and how long the failed attempt took.
  • Helper methods (get, post, request): These wrap Axios request methods and return only the response data for cleaner usage.

This service is designed to be reusable across your project, giving you consistency and better observability for all outbound HTTP calls.


🧪 How to Use It

  1. Register it in your module:
@Module({
  providers: [AdvancedAxiosService],
  exports: [AdvancedAxiosService],
})
export class HttpClientModule {}
Enter fullscreen mode Exit fullscreen mode

2. Inject and use it in your services:

@Injectable()
export class ExampleService {
  constructor(private readonly http: AdvancedAxiosService) {}

  async fetchData() {
    const data = await this.http.get('http://jsonplaceholder.typicode.com/posts');
    return data;
  }
}
Enter fullscreen mode Exit fullscreen mode

📦 Bonus: Customize It Further

Want to go further? Here are some ideas:

  • Integrate with your centralized logging service
  • Add request queuing or exponential backoff for specific status codes

🧘 Final Thoughts

The AdvancedAxiosService gives your NestJS applications reliable, observable, and extensible HTTP request capabilities. Whether you're calling internal APIs or external services, this service helps you build more robust apps.

Happy coding! 🚀

Top comments (0)