UNPKG

@vepler/http-client

Version:

A flexible and extensible API service library for making HTTP requests with built-in authentication support for bearer tokens and API keys.

185 lines (163 loc) 6.02 kB
import axios, { AxiosInstance, AxiosRequestConfig, AxiosResponse } from 'axios'; import logRequest from './interceptors/request'; import interceptorResponseSuccess from './interceptors/response-success'; import interceptorResponseError from './interceptors/response-error'; import PinoWrapper from '@vepler/logger'; import { HttpError } from './errors/http-error'; interface Options { host: string; timeout?: number; logLevel: string; headers?: Record<string, string>; } interface QueryParams { token?: string; apiKey?: string; params?: Record<string, any>; headers?: Record<string, string>; } type ResourceParams = Record<string, any>; const ensureProtocol = (hostname: string): string => /^https?:\/\//i.test(hostname) ? hostname : `http://${hostname}`; const configureRequest = ( token?: string, apiKey?: string, headers?: Record<string, string> ): AxiosRequestConfig => { const config: AxiosRequestConfig = { headers: {} }; // Initialize headers if they are undefined config.headers = config.headers || {}; if (token) { config.headers['Authorization'] = `Bearer ${token}`; } if (apiKey) { config.headers['x-api-key'] = apiKey; } if (headers) { config.headers = { ...config.headers, ...headers }; } return config; }; /** * Extract data from API response */ const handleResponse = (response: AxiosResponse): any => response.data; /** * Standardized error handler that passes through HttpError instances * and wraps other errors in a HttpError */ const handleError = (error: any): never => { // If it's already our custom HttpError, just rethrow it if (error instanceof HttpError || (error && error.isHttpError)) { throw error; } // Otherwise, it's an unexpected error type, so wrap it throw new HttpError(`Unexpected API error: ${error.message || 'Unknown error'}`); }; const createAxiosInstance = (options: Options): AxiosInstance => { // Initialize the logger PinoWrapper.initialize({ level: options.logLevel || 'info' }); const http = axios.create({ baseURL: ensureProtocol(options.host), timeout: options.timeout || 3000, headers: options.headers, }); // @ts-expect-error - The interceptors are not defined in the AxiosInstance type http.interceptors.request.use(logRequest); http.interceptors.response.use(interceptorResponseSuccess, interceptorResponseError); return http; }; const ApiService = { /** * Create a new Axios instance with the provided options. * @param options - Configuration options for the Axios instance. * @returns An object containing the Axios client and CRUD methods. */ create(options: Options) { const http = createAxiosInstance(options); return { client: http, /** * Send a GET request to the specified resource with optional query parameters. * @param resource - The resource endpoint. * @param params - The request parameters. * @param queryParams - Additional query parameters and headers. * @returns The response data. */ async query(resource: string, params: ResourceParams, queryParams: QueryParams = {}) { return http.get(resource, { ...configureRequest( queryParams.token, queryParams.apiKey, queryParams.headers ), params } ).then(handleResponse).catch(handleError); }, /** * Send a GET request to the specified resource with an optional slug and query parameters. * @param resource - The resource endpoint. * @param slug - The resource identifier. * @param queryParams - Additional query parameters and headers. * @returns The response data. */ async get(resource: string, slug: string = '', queryParams: QueryParams = {}) { return http.get(`${resource}/${slug}`, configureRequest( queryParams.token, queryParams.apiKey, queryParams.headers )) .then(handleResponse).catch(handleError); }, /** * Send a POST request to the specified resource with the provided data. * @param resource - The resource endpoint. * @param data - The request payload. * @param queryParams - Additional query parameters and headers. * @returns The response data. */ async post(resource: string, data: any, queryParams: QueryParams = {}) { return http.post(resource, data, configureRequest( queryParams.token, queryParams.apiKey, queryParams.headers )) .then(handleResponse).catch(handleError); }, /** * Send a PUT request to the specified resource with the provided data. * @param resource - The resource endpoint. * @param data - The request payload. * @param queryParams - Additional query parameters and headers. * @returns The response data. */ async put(resource: string, data: any, queryParams: QueryParams = {}) { return http.put(resource, data, configureRequest( queryParams.token, queryParams.apiKey, queryParams.headers )) .then(handleResponse).catch(handleError); }, /** * Send a DELETE request to the specified resource with an optional slug and query parameters. * @param resource - The resource endpoint. * @param slug - The resource identifier. * @param queryParams - Additional query parameters and headers. * @returns The response data. */ async delete(resource: string, slug: string = '', queryParams: QueryParams = {}) { return http.delete(`${resource}/${slug}`, configureRequest( queryParams.token, queryParams.apiKey, queryParams.headers )) .then(handleResponse).catch(handleError); }, }; }, }; // Export error classes for consumers export * from './errors'; export default ApiService;