@andranik-arakelyan/js-utilities
Version:
Javascript utilities
201 lines (200 loc) • 7.75 kB
TypeScript
import { AxiosInstance, AxiosResponse } from 'axios';
/**
* Configuration options for the BaseApiClient.
*
* @interface BaseApiClientConfig
* @property {string} baseUrl - The base URL for all API requests
* @property {string} [urlPrefix] - Optional URL prefix to append to baseUrl (e.g., '/v1' for versioning)
*
* @example
* const config: BaseApiClientConfig = {
* baseUrl: 'https://api.example.com',
* urlPrefix: '/v1'
* };
*/
export interface BaseApiClientConfig {
baseUrl: string;
urlPrefix?: string;
}
/**
* Base API client class providing common request handling functionality.
* All specific API clients should extend this class.
*
* This class manages HTTP requests using axios and maintains custom headers
* that are sent with every request. Headers are stored in the client instance
* (not in axios defaults) for better reliability and explicit control.
*
* @abstract
* @class BaseApiClient
*
* @example
* class UserApiClient extends BaseApiClient {
* async getUser(id: number) {
* return this.request({
* path: `/users/${id}`,
* method: 'GET'
* });
* }
* }
*
* const client = new UserApiClient({
* baseUrl: 'https://api.example.com',
* urlPrefix: '/v1'
* });
*
* client.setHeaders({ 'Authorization': 'Bearer token123' });
* const user = await client.getUser(1);
*/
export declare abstract class BaseApiClient {
protected config: BaseApiClientConfig;
protected axiosInstance: AxiosInstance;
protected customHeaders: Record<string, string>;
/**
* Creates a new base API client instance.
*
* Initializes the axios instance with the provided configuration and
* sets up default headers (Content-Type: application/json).
* Headers are managed in the client instance for explicit control.
*
* @param {BaseApiClientConfig} config - The configuration options for the API client
* @param {string} config.baseUrl - The base URL for all API requests
* @param {string} [config.urlPrefix] - Optional URL prefix (e.g., '/v1')
*
* @example
* constructor(config: BaseApiClientConfig) {
* super(config);
* }
*/
constructor(config: BaseApiClientConfig);
/**
* Sets headers for all subsequent requests.
*
* Headers are stored in the client instance and passed explicitly with each request.
* This provides better reliability compared to using axios default headers.
* Headers set to null will be deleted.
*
* @param {Record<string, string | null>} headers - Object containing headers to set.
* Use null as a value to delete a header.
*
* @example
* // Set custom headers
* client.setHeaders({
* 'X-Request-Id': 'req-123',
* 'Authorization': 'Bearer token123'
* });
*
* // Remove a header
* client.setHeaders({ 'X-Request-Id': null });
*/
setHeaders(headers: Record<string, string | null>): void;
/**
* Gets the current headers.
*
* Returns a copy of all current headers stored in the client instance.
* This includes default headers (Content-Type) and any custom headers set via setHeaders().
*
* @returns {Record<string, string>} A copy of all current headers
*
* @example
* const headers = client.getHeaders();
* console.log(headers['Content-Type']); // 'application/json'
* console.log(headers['Authorization']); // 'Bearer token123' (if set)
*/
getHeaders(): Record<string, string>;
/**
* Makes an HTTP request to the API.
*
* Sends a request to the API with the specified configuration and current headers.
* Headers are explicitly passed with each request for reliable header management.
* Handles both successful responses and axios errors.
*
* When an error occurs, the full response data is bound to the Error instance via the
* `response` property, allowing access to structured error information from the server.
*
* @template T The expected response data type
* @param {Object} options - Request configuration options
* @param {string} options.path - The API endpoint path (e.g., '/users/1')
* @param {Record<string, string | number | undefined>} [options.query] - Optional query parameters
* @param {any} [options.body] - Optional request body for POST/PUT/PATCH requests
* @param {string} [options.method='GET'] - HTTP method (GET, POST, PUT, PATCH, DELETE)
* @returns {Promise<T>} The parsed response data
* @throws {Error} If the request fails, returns an error status code, or response has success: false.
* The error object will have a `response` property containing the full error response data.
*
* @example
* // GET request
* const user = await this.request({
* path: '/users/1',
* method: 'GET'
* });
*
* // Handling errors with structured error data
* try {
* await this.request({ path: '/users', method: 'POST', body: data });
* } catch (error: any) {
* console.log(error.message); // 'Validation Error'
* console.log(error.response.code); // 'VALIDATION_ERROR'
* console.log(error.response.errors); // array of field errors
* }
*
* // GET with query parameters
* const users = await this.request({
* path: '/users',
* method: 'GET',
* query: { page: 1, limit: 10 }
* });
*/
protected request<T>(options: {
path: string;
query?: Record<string, string | number | undefined>;
body?: any;
method?: "GET" | "POST" | "PUT" | "PATCH" | "DELETE";
}): Promise<T>;
/**
* Handles API response parsing and error checking.
*
* Validates the response status code and checks for error indicators in the response data.
* Supports two error patterns:
* 1. HTTP error status codes (< 200 or >= 300)
* 2. JSON response with success: false field
*
* Priority for error messages: error field > message field > statusText
*
* When an error is thrown, the response data is bound to the Error instance via the
* `response` property, making the full error details accessible to callers.
*
* @template T The expected response data type
* @param {AxiosResponse} response - The axios Response object
* @returns {T} The parsed and validated response data
* @throws {Error} If the response indicates an error or has success: false.
* The error object will have a `response` property containing the response data.
*
* @example
* // Successful response
* const data = handleResponse({ status: 200, data: { id: 1, name: 'John' } });
*
* // Error with success: false
* // throws Error('Operation failed') with error.response = { success: false, error: 'Operation failed', ... }
* const data = handleResponse({
* status: 200,
* data: { success: false, error: 'Operation failed' }
* });
*
* // HTTP error status
* // throws Error('Not Found') with error.response = { error: 'Resource not found', code: 'NOT_FOUND', ... }
* const data = handleResponse({
* status: 404,
* statusText: 'Not Found',
* data: { error: 'Resource not found', code: 'NOT_FOUND' }
* });
*
* // Usage:
* // try {
* // await client.getUser(1);
* // } catch (error: any) {
* // console.log(error.message); // 'Not Found'
* // console.log(error.response); // { error: 'Resource not found', code: 'NOT_FOUND', ... }
* // }
*/
protected handleResponse<T>(response: AxiosResponse): T;
}