Overview

Make HTTP requests with automatic retries using native fetch.

Features

  • Native fetch
  • Automatic retries
  • Timeout handling

Installation

npx hanma add elysia-http-client --framework elysia

Code

/**
 * Bun-native HTTP client with retry logic
 * Uses native fetch API available in Bun
 */

interface HttpClientOptions extends RequestInit {
  retries?: number;
  backoff?: number;
}

interface HttpResponse<T> {
  data: T;
  status: number;
  headers: Headers;
}

export class HttpClient {
  private baseURL: string;
  private defaultOptions: HttpClientOptions;

  constructor(baseURL: string = "", defaultOptions: HttpClientOptions = {}) {
    this.baseURL = baseURL;
    this.defaultOptions = {
      retries: 3,
      backoff: 1000,
      ...defaultOptions,
    };
  }

  private async request<T>(
    url: string,
    options: HttpClientOptions = {}
  ): Promise<HttpResponse<T>> {
    const { retries, backoff, ...fetchOptions } = {
      ...this.defaultOptions,
      ...options,
    };

    const fullUrl = url.startsWith("http") ? url : `${this.baseURL}${url}`;
    let lastError: Error | null = null;

    for (let attempt = 1; attempt <= (retries || 3); attempt++) {
      try {
        const response = await fetch(fullUrl, {
          ...fetchOptions,
          headers: {
            "Content-Type": "application/json",
            ...fetchOptions.headers,
          },
        });

        if (!response.ok) {
          if (response.status >= 500 && attempt < (retries || 3)) {
            throw new Error(`Server error: ${response.status}`);
          }
          const errorData = await response.json().catch(() => ({}));
          throw new Error(
            errorData.message || `HTTP error! status: ${response.status}`
          );
        }

        const data = (await response.json()) as T;
        return {
          data,
          status: response.status,
          headers: response.headers,
        };
      } catch (error: any) {
        lastError = error;
        if (attempt < (retries || 3)) {
          const delay = (backoff || 1000) * Math.pow(2, attempt - 1);
          console.warn(
            `[HTTP] Attempt ${attempt} failed, retrying in ${delay}ms...`
          );
          await Bun.sleep(delay);
        }
      }
    }

    throw lastError || new Error("Request failed");
  }

  async get<T>(url: string, options?: HttpClientOptions): Promise<HttpResponse<T>> {
    return this.request<T>(url, { ...options, method: "GET" });
  }

  async post<T>(
    url: string,
    body?: any,
    options?: HttpClientOptions
  ): Promise<HttpResponse<T>> {
    return this.request<T>(url, {
      ...options,
      method: "POST",
      body: JSON.stringify(body),
    });
  }

  async put<T>(
    url: string,
    body?: any,
    options?: HttpClientOptions
  ): Promise<HttpResponse<T>> {
    return this.request<T>(url, {
      ...options,
      method: "PUT",
      body: JSON.stringify(body),
    });
  }

  async patch<T>(
    url: string,
    body?: any,
    options?: HttpClientOptions
  ): Promise<HttpResponse<T>> {
    return this.request<T>(url, {
      ...options,
      method: "PATCH",
      body: JSON.stringify(body),
    });
  }

  async delete<T>(url: string, options?: HttpClientOptions): Promise<HttpResponse<T>> {
    return this.request<T>(url, { ...options, method: "DELETE" });
  }
}

// Simple function-based API
export async function httpClient<T>(
  url: string,
  options: HttpClientOptions = {}
): Promise<T> {
  const client = new HttpClient();
  const response = await client.request<T>(url, options);
  return response.data;
}

/* USAGE:

// Class-based
const client = new HttpClient("https://api.example.com", {
  retries: 3,
  backoff: 1000,
});

const { data, status } = await client.get<User>("/users/1");

// Function-based
const user = await httpClient<User>("https://api.example.com/users/1", {
  retries: 3,
  backoff: 1000,
});

*/

Usage

import { httpClient } from './libs/http-client';

Dependencies

No external dependencies required.

Frequently Asked Questions

How do I install HTTP Client?
Run the following command: npx hanma add elysia-http-client --framework elysia
How do I use this snippet?
Copy the code and paste it into your project. The snippet will be saved to src/libs/http-client.ts.
Can I customize this snippet?
Yes! All snippets are copy-paste code that you own. Feel free to modify them to fit your needs.
Does this work with other frameworks?
This snippet is designed for the specific framework. Check the other framework pages for equivalent implementations.