In a React Native application using TypeScript, API responses can be strongly typed so that the application knows exactly what data is returned by the REST API.
A typed API response means we define the expected structure of data returned by an API using TypeScript.
interface Student {
id: number;
name: string;
email: string;
}
TypeScript can then use this structure when processing API data.
Suppose the PHP API returns:
{
"id": 1,
"name": "Rahul",
"email": "rahul@example.com"
}
We can define:
interface Student {
id: number;
name: string;
email: string;
}
A REST API often returns a structure like:
{
"success": true,
"message": "Student fetched successfully",
"data": {
"id": 1,
"name": "Rahul"
}
}
We can create separate interfaces for this response.
interface Student {
id: number;
name: string;
email: string;
}
This interface represents the student object inside the API response.
interface StudentResponse {
success: boolean;
message: string;
data: Student;
}
Here, data contains one Student object.
Axios supports TypeScript generics for typing response data.
const response =
await api.get<StudentResponse>(
"/students/1"
);
TypeScript now knows the expected structure of
response.data.
const response =
await api.get<StudentResponse>(
"/students/1"
);
console.log(response.data.success);
console.log(response.data.message);
console.log(response.data.data.name);
TypeScript can provide suggestions for these properties.
A list endpoint usually returns an array of records.
{
"success": true,
"message": "Students fetched successfully",
"data": [
{
"id": 1,
"name": "Rahul"
},
{
"id": 2,
"name": "Amit"
}
]
}
interface StudentListResponse {
success: boolean;
message: string;
data: Student[];
}
Student[] means that the data property contains an array
of Student objects.
const response =
await api.get<StudentListResponse>(
"/students"
);
const students = response.data.data;
The students variable is understood as
Student[].
<FlatList
data={students}
keyExtractor={item => item.id.toString()}
renderItem={({ item }) => (
<Text>{item.name}</Text>
)}
/>
TypeScript knows that each FlatList item is a Student.
APIs can return nested objects.
{
"success": true,
"data": {
"student": {
"id": 1,
"name": "Rahul"
}
}
}
Each nested level can be represented with a TypeScript interface.
interface Student {
id: number;
name: string;
}
interface StudentData {
student: Student;
}
interface StudentResponse {
success: boolean;
data: StudentData;
}
Authentication APIs can also have typed responses.
interface LoginUser {
id: number;
name: string;
email: string;
}
interface LoginResponse {
success: boolean;
message: string;
token: string;
user: LoginUser;
}
const response =
await api.post<LoginResponse>(
"/login",
{
email: "user@example.com",
password: "123456"
}
);
const token = response.data.token;
const [student, setStudent] =
useState<Student | null>(null);
The state can contain either a Student object or null.
setStudent(response.data.data);
const [loading, setLoading] =
useState<boolean>(false);
const [students, setStudents] =
useState<Student[]>([]);
TypeScript can type both API data and UI state.
Error responses can also have a standard structure.
{
"success": false,
"message": "Validation failed",
"errors": {
"email": [
"Email is required"
]
}
}
We can describe it using:
interface ApiErrorResponse {
success: boolean;
message: string;
errors?: {
[key: string]: string[];
};
}
Pagination responses normally contain both records and metadata.
interface PaginationMeta {
page: number;
limit: number;
total: number;
totalPages: number;
}
interface StudentListResponse {
success: boolean;
message: string;
data: Student[];
meta: PaginationMeta;
}
const response =
await api.get<StudentListResponse>(
"/students?page=1&limit=10"
);
const students = response.data.data;
const currentPage = response.data.meta.page;
const totalPages = response.data.meta.totalPages;
TypeScript understands the structure of the complete response.
Search APIs can use the same response interface.
const response =
await api.get<StudentListResponse>(
"/students",
{
params: {
search: "Rahul"
}
}
);
The returned data remains strongly typed as Student objects.
A POST API can return the newly created student.
interface CreateStudentResponse {
success: boolean;
message: string;
data: Student;
}
const response =
await api.post<CreateStudentResponse>(
"/students",
studentData
);
console.log(response.data.data.id);
interface UpdateStudentResponse {
success: boolean;
message: string;
data: Student;
}
const response =
await api.put<UpdateStudentResponse>(
"/students/1",
studentData
);
console.log(response.data.message);
A DELETE endpoint can also return a standard response.
interface DeleteResponse {
success: boolean;
message: string;
}
const response =
await api.delete<DeleteResponse>(
"/students/1"
);
console.log(response.data.message);
If many API responses use the same structure, a generic interface can reduce repetition.
interface ApiResponse<T> {
success: boolean;
message: string;
data: T;
}
The T represents the type of data returned by the API.
For one student:
type StudentResponse =
ApiResponse<Student>;
For multiple students:
type StudentListResponse =
ApiResponse<Student[]>;
This allows the same response structure to be reused with different data types.
interface Student {
id: number;
name: string;
email: string;
}
interface StudentListResponse {
success: boolean;
message: string;
data: Student[];
}
const loadStudents = async () => {
try {
const response =
await api.get<StudentListResponse>(
"/students"
);
setStudents(response.data.data);
} catch (error) {
console.log("Failed to load students");
}
};
import React, { useEffect, useState } from "react";
import { View, Text, FlatList } from "react-native";
import api from "./api";
interface Student {
id: number;
name: string;
email: string;
}
interface StudentListResponse {
success: boolean;
message: string;
data: Student[];
}
const StudentList = () => {
const [students, setStudents] =
useState<Student[]>([]);
useEffect(() => {
const loadStudents = async () => {
try {
const response =
await api.get<StudentListResponse>(
"/students"
);
setStudents(response.data.data);
} catch (error) {
console.log(error);
}
};
loadStudents();
}, []);
return (
<View>
<FlatList
data={students}
keyExtractor={item =>
item.id.toString()
}
renderItem={({ item }) => (
<View>
<Text>{item.name}</Text>
<Text>{item.email}</Text>
</View>
)}
/>
</View>
);
};
export default StudentList;
PHP + MySQL
↓
REST API
↓
JSON Response
↓
Axios
↓
TypeScript Interface
↓
Typed API Response
↓
React Native State
↓
FlatList / UI
For example, the PHP API can return:
{
"success": true,
"message": "Students fetched successfully",
"data": [
{
"id": 1,
"name": "Rahul",
"email": "rahul@example.com"
}
]
}
React Native can receive this response through Axios and use a TypeScript interface to describe its structure.
interface Student {
id: number;
name: string;
email: string;
}
interface StudentListResponse {
success: boolean;
message: string;
data: Student[];
}
const response =
await api.get<StudentListResponse>(
"/students"
);
This creates a clear and type-safe connection between the PHP REST API and the React Native application.
Student[] when the API returns multiple students.Question: Which Axios syntax is used to specify the TypeScript type of an API response?