-
Notifications
You must be signed in to change notification settings - Fork 0
Requests and APIs
APIs (Application Programming Interfaces) are the backbone of modern frontend applications, enabling seamless communication between the client and server. They allow frontend applications to fetch, update, and synchronize data with backend systems, making it possible to build dynamic, interactive, and data-driven user experiences. As web applications grow in complexity, the need for robust, reliable, and efficient API communication becomes even more critical, impacting everything from performance to security and maintainability.
Choosing the right approach and tools for handling API requests is essential for frontend development. A well-structured API layer not only simplifies data fetching and error handling but also improves code readability and maintainability. By abstracting the details of HTTP requests, developers can focus on building features rather than dealing with low-level networking concerns. This document outlines our approach to API requests in this project, including the rationale behind our choice of dependencies.
For this project, we have selected Axios as our primary HTTP client for making API requests. Axios is a promise-based HTTP client for the browser and Node.js, designed to make it easy to send asynchronous HTTP requests to REST endpoints and perform CRUD operations. Its intuitive API, support for request and response interceptors, and automatic transformation of JSON data make it a popular choice among frontend developers.
According to the official documentation: "Axios is a simple to use library that allows you to make HTTP requests in JavaScript. It supports the Promise API that is native to JS ES6+, and it works in both the browser and Node.js environments. Axios automatically transforms request and response data, and provides a clean and concise syntax for handling requests, responses, and errors."
Axios also offers features such as request cancellation, automatic JSON parsing, and the ability to set default headers and base URLs, which help streamline API integration and reduce boilerplate code. Its widespread adoption means there is a large community and plenty of resources available for troubleshooting and best practices.
While selecting an HTTP client, we considered several alternatives to Axios, including the native fetch API, ky, and superagent. The native fetch API is built into modern browsers and provides a low-level interface for making HTTP requests, but it lacks some features out of the box, such as request cancellation, automatic JSON transformation, and convenient error handling. Libraries like ky and superagent offer more features and a simpler API than fetch, but may not be as widely adopted or as feature-rich as Axios.
Ultimately, we chose Axios for its balance of simplicity, flexibility, and community support. Its rich feature set, excellent documentation, and proven track record in production applications make it a reliable choice for handling API requests in modern frontend projects.
To get started with Axios, you need to install it in your project:
npm install axiosAnd then add the types to allow typescript to operate properly
npm install --save-dev @types/axiosThe API framework in this project is designed to provide a robust, type-safe, and extensible way to interact with backend services using Axios as the underlying HTTP client. The configuration is organized into several key files:
-
src/config/api/types.ts: This file defines the core types and interfaces for API requests, such asApiRequestOptions,ApiMiddleware, andHttpMethod. It also includes type definitions for request and response interceptors, making it easy to extend or customize request handling. -
src/config/api/CancelablePromise.ts: This utility implements aCancelablePromiseclass, which extends the standard Promise API to support request cancellation. This is particularly useful for aborting in-flight requests, such as when a user navigates away from a page before a response is received. -
src/config/api/request.ts: This is the main implementation file for making HTTP requests. It wraps Axios to provide additional features like request cancellation, error handling, and support for middleware. The file includes utility functions for type checking, request building, and response processing, ensuring that all API interactions are consistent and reliable. -
src/services/APIs.ts: This file acts as the entry point for all API services. It imports and registers individual service classes (such asCustomerService), each of which extends a baseHttpClientclass. The API configuration (such as base URL, version, and credentials) is defined here and passed to each service, allowing for centralized management of API settings.
Together, these files create a flexible and maintainable API layer that leverages Axios for HTTP requests while adding project-specific features like type safety, request cancellation, and middleware support. This structure makes it easy to add new services, update configurations, and ensure consistent API usage throughout the application.
To configure the API, you need to implement an instance of the HttpClient, and add it to the API static caller:
//src/services/APIs.ts
// Backend API configuration, set as a variable to make it easier to reuse
export const BackendApiConfig: APIConfig = {
BASE: 'http://localhost:8080',
VERSION: '0',
WITH_CREDENTIALS: false,
CREDENTIALS: 'include',
TOKEN: undefined,
USERNAME: undefined,
PASSWORD: undefined,
HEADERS: undefined,
ENCODE_PATH: undefined,
};
// Add the service to the constructors. You can have multiple services using the same config, or different configs for each service.
const serviceConstructors = {
customers: new CustomerService(BackendApiConfig),
} as const;While the service can be implemented as:
import { HttpClient, type APIConfig } from '@/config/api/types';
import type { CancelablePromise } from '@/config/api/CancelablePromise';
export type CustomerDto = {
id: string;
firstName: string;
lastName: string;
email: string;
phoneNumber: string;
addressLine1: string;
addressLine2: string;
city: string;
state: string;
postalCode: string;
country: string;
};
export type ListCustomersParams = {
page?: number; // zero-based
size?: number;
q?: string;
};
type CustomerListResponse = { content: CustomerDto[]; page: ListCustomersParams };
export class CustomerService extends HttpClient {
constructor(readonly config: APIConfig) {
super(config);
}
// Returns the actual success payload. Errors reject.
list(params: ListCustomersParams = {}): CancelablePromise<CustomerListResponse> {
return this.doRequest<CustomerListResponse>(this.config, {
method: 'GET',
url: '/api/customers',
query: params,
});
}
}A simple request can be done as follows:
import APIs from '@/services/APIs';
const req = APIs.customers.list();
req
.then((res) => {
setCustomers(res.content);
})
.catch((err) => {
console.error('Failed to fetch customers', err);
});Or when using react query:
import APIs from '@/services/APIs';
import { useQuery } from '@tanstack/react-query';
const { data } = useQuery({
queryKey: ['customerList'],
queryFn: async () => await APIs.customers.list(),
});