BibiliotecaDev

Biblioteca de Conhecimento para Devs

Snippets de código, padrões de projeto e anotações teóricas em um só lugar.

Typescript

Classe HTTP para interagir com API

Classe HTTP para abstrair a interação com APIs no frontend, já resolve o endpoint da api, extrai os dados, se retornou erro extrai a message de erro
http.tsx
interface FetchOptions extends RequestInit {
  params?: Record<string, string | undefined | null | number>;
  formData?: boolean;
}

class Http {
  private baseURL: string;

  constructor(baseURL: string) {
    this.baseURL = baseURL;
  }

  private async request<T>(path: string, options: FetchOptions = {}): Promise<T> {
    //const url = new URL(path, this.baseURL);

    let urlString: string;

    if (path.startsWith("http")) {
      urlString = path;
    } else {
      // Garante que o path tenha barra inicial
      const safePath = path.startsWith("/") ? path : \`/\${path}\`;
      // Garante que a baseURL não tenha barra final para não duplicar
      const safeBase = this.baseURL.replace(/\\/\$/, "");

      // Junta as strings manualmente: .../api + /auth/sign-in
      urlString = \`\${safeBase}\${safePath}\`;
    }

    const url = new URL(urlString);

    if (options.params) {
      for (const [key, value] of Object.entries(options.params)) {
        if (value) {
          url.searchParams.append(key, String(value));
        }
      }
    }

    const headers = {
      ...options.headers,
      ...(!options.formData
        ? {
            "Content-Type": "application/json",
          }
        : {}),
    };

    const response = await fetch(url, {
      ...options,
      headers,
      credentials: "include",
    });

    let data: any = null;

    try {
      data = await response.json();
    } catch (error) {
      data = null;
    }

    console.log(\`[HTTP] \${response.status} \${url.pathname}\`, data);

    if (!response.ok) {
      let errorMessage: string;

      if (data && typeof data === "object") {
        // 1. Tenta pegar propriedades comuns de erro
        const rawMessage = data.message || data.error || data.errors;

        if (typeof rawMessage === "string") {
          // Se for uma string simples, usa ela
          errorMessage = rawMessage;
        } else if (rawMessage) {
          // Se message/error for um objeto/array (ex: lista de validações), converte para JSON
          errorMessage = JSON.stringify(rawMessage);
        } else {
          // Se não tiver propriedades conhecidas, converte o objeto inteiro
          errorMessage = JSON.stringify(data);
        }
      } else if (typeof data === "string") {
        // Se a resposta for texto puro
        errorMessage = data;
      } else {
        // Fallback genérico
        errorMessage = \`Erro \${response.status}: Ocorreu um erro desconhecido na requisição.\`;
      }

      throw new Error(errorMessage);
    }

    return data as T;
  }

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

  async post<T>(path: string, data?: unknown, options: FetchOptions = {}): Promise<T> {
    const isFormData = options.formData;

    return this.request<T>(path, {
      ...options,
      method: "POST",
      body: isFormData ? (data as BodyInit) : JSON.stringify(data),
    });
  }

  async put<T>(path: string, data?: unknown, options: FetchOptions = {}): Promise<T> {
    const isFormData = options.formData;
    return this.request<T>(path, {
      ...options,
      method: "PUT",
      body: isFormData ? (data as BodyInit) : JSON.stringify(data),
    });
  }

  async patch<T>(path: string, data?: unknown, options?: RequestInit): Promise<T> {
    return this.request<T>(path, {
      ...options,
      method: "PATCH",
      body: JSON.stringify(data),
    });
  }

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

export const http = new Http(process.env.NEXT_PUBLIC_API_URL || "");
Ver Detalhes