REST APIs often return responses with a common structure. For example,
a student API, user API, product API, and login API may all return
success, message, and data.
TypeScript generics allow us to create one reusable response interface
for different types of API data.
ApiResponse<T> interface and use it with PHP REST APIs,
Axios, React Native, authentication, students, lists, errors, and
pagination.
An API response is the data returned by the server after a request.
{
"success": true,
"message": "Data loaded",
"data": {
"id": 1,
"name": "Rahul"
}
}
A common API response can contain:
successmessagedataerrorsmetaKeeping a consistent structure makes API integration easier.
interface ApiResponse {
success: boolean;
message: string;
data: any;
}
The problem with any is that TypeScript loses information
about the actual data structure.
interface ApiResponse {
success: boolean;
message: string;
data: any;
}
With any, TypeScript cannot properly check what properties
exist inside data.
Generics provide a better solution.
A generic allows a type to be supplied when the interface or function is used.
interface ApiResponse<T> {
success: boolean;
message: string;
data: T;
}
Here, T represents the actual data type.
interface User {
id: number;
name: string;
email: string;
}
type UserResponse =
ApiResponse<User>;
Now data is automatically treated as a
User.
interface Student {
id: number;
name: string;
course: string;
}
type StudentResponse =
ApiResponse<Student>;
A generic response can also contain an array.
type StudentListResponse =
ApiResponse<Student[]>;
In this case, data is an array of students.
const response =
await axios.get<ApiResponse<Student[]>>(
"/students"
);
console.log(response.data.data);
Axios now knows that the response contains an array of students.
ApiResponse<Student[]>
The outer type is ApiResponse.
The type supplied to T is Student[].
data: Student[]
interface LoginData {
token: string;
user: User;
}
type LoginResponse =
ApiResponse<LoginData>;
This keeps the common response structure reusable.
type ProfileResponse =
ApiResponse<User>;
The same generic interface can be used for the user profile API.
interface Product {
id: number;
name: string;
price: number;
}
type ProductResponse =
ApiResponse<Product>;
const getStudents =
async (): Promise<
ApiResponse<Student[]>
> => {
const response =
await axios.get<
ApiResponse<Student[]>
>("/students");
return response.data;
};
A standard response can also include validation or error information.
interface ApiResponse<T> {
success: boolean;
message: string;
data?: T;
errors?: string[];
}
The optional properties allow different responses to contain different information.
{
"success": true,
"message": "Student loaded",
"data": {
"id": 10,
"name": "Amit",
"course": "React Native"
}
}
{
"success": false,
"message": "Validation failed",
"errors": [
"Email is required",
"Password is required"
]
}
The same response interface can represent this structure when
data is optional.
interface PaginationMeta {
page: number;
limit: number;
total: number;
totalPages: number;
}
interface ApiResponse<T> {
success: boolean;
message: string;
data: T;
meta?: PaginationMeta;
}
type StudentListResponse =
ApiResponse<Student[]>;
The response can contain both student data and pagination metadata.
response.data.data
response.data.meta
const response =
await api.get<
ApiResponse<Student[]>
>("/students");
setStudents(response.data.data);
The returned data can be directly assigned to a typed
student array.
interface Student {
id: number;
name: string;
course: string;
}
const response =
await api.post<
ApiResponse<Student>
>(
"/students",
{
name: "Ravi",
course: "React Native"
}
);
console.log(response.data.data);
const response =
await api.put<
ApiResponse<Student>
>(
"/students/10",
{
name: "Ravi Kumar",
course: "TypeScript"
}
);
console.log(response.data.data);
A DELETE endpoint may return a response without a large data object.
interface DeleteData {
id: number;
}
type DeleteResponse =
ApiResponse<DeleteData>;
const response =
await api.delete<DeleteResponse>(
"/students/10"
);
interface LoginData {
token: string;
user: User;
}
type LoginResponse =
ApiResponse<LoginData>;
const response =
await api.post<LoginResponse>(
"/login",
loginData
);
const token =
response.data.data.token;
const getUsers =
async (): Promise<
ApiResponse<User[]>
> => {
const response =
await api.get<
ApiResponse<User[]>
>("/users");
return response.data;
};
Generics can also be used directly in reusable functions.
async function getData<T>(
url: string
): Promise<ApiResponse<T>> {
const response =
await api.get<ApiResponse<T>>(
url
);
return response.data;
}
The same function can request users, students, products, or other data.
const users =
await getData<User[]>(
"/users"
);
const students =
await getData<Student[]>(
"/students"
);
console.log(users.data);
console.log(students.data);
The same function can work with different response types.
interface ApiResponse<T> {
success: boolean;
message: string;
data?: T;
errors?: string[];
meta?: PaginationMeta;
}
interface PaginationMeta {
page: number;
limit: number;
total: number;
totalPages: number;
}
interface User {
id: number;
name: string;
email: string;
}
interface Student {
id: number;
name: string;
course: string;
}
interface LoginData {
token: string;
user: User;
}
import axios from "axios";
interface ApiResponse<T> {
success: boolean;
message: string;
data: T;
}
interface Student {
id: number;
name: string;
course: string;
}
const api = axios.create({
baseURL: "https://example.com/api"
});
const getStudents =
async (): Promise<
ApiResponse<Student[]>
> => {
const response =
await api.get<
ApiResponse<Student[]>
>("/students");
return response.data;
};
The response is strongly typed from the API layer to the React Native component.
PHP REST API
↓
JSON Response
↓
Axios
↓
ApiResponse<T>
↓
TypeScript
↓
User / Student / Product / Login Data
↓
React Native State
↓
UI Component
Generic API responses allow one common response structure to be reused for many different API resources. This reduces duplicate TypeScript code while maintaining strong type checking.
ApiResponse<T> allows different data types to be supplied.any can reduce TypeScript type safety.ApiResponse<Student[]> represents a response containing student arrays.Question: What does T represent in ApiResponse<T>?