API requests can fail for many reasons. The server may return an error, the internet connection may be unavailable, the user may not be authenticated, or the requested resource may not exist.
API error handling means detecting API problems and responding to them correctly in the React Native application.
React Native
↓
API Request
↓
PHP REST API
↓
Success / Error
↓
React Native handles result
↓
Show appropriate message
Common API problems include:
HTTP status codes help the React Native application understand what happened with the request.
200 → Success
201 → Created
400 → Bad Request
401 → Unauthorized
403 → Forbidden
404 → Not Found
422 → Validation Error
500 → Server Error
Fetch provides the response.ok property to indicate whether the HTTP response is in the successful range.
const response = await fetch(
API_URL
);
if (!response.ok) {
console.log(
"Request failed:",
response.status
);
}
const response = await fetch(
API_URL
);
console.log(
response.status
);
The status can be used to perform different actions for different errors.
HTTP 400 generally means that the server could not process the request because the request was invalid.
if (response.status === 400) {
Alert.alert(
"Bad Request",
"Please check the submitted data."
);
return;
}
HTTP 401 commonly means that authentication is missing, invalid, or expired.
if (response.status === 401) {
Alert.alert(
"Session Expired",
"Please login again."
);
return;
}
The application may then navigate the user to the login screen.
HTTP 403 means the server understood the request but the authenticated user does not have permission to perform the operation.
if (response.status === 403) {
Alert.alert(
"Access Denied",
"You do not have permission."
);
return;
}
HTTP 404 indicates that the requested resource could not be found.
if (response.status === 404) {
Alert.alert(
"Not Found",
"The requested data was not found."
);
return;
}
HTTP 422 is commonly used when the request format is understandable but the submitted data fails validation.
if (response.status === 422) {
const result =
await response.json();
console.log(
result.errors
);
return;
}
HTTP 500 indicates a server-side problem.
if (response.status === 500) {
Alert.alert(
"Server Error",
"Something went wrong on the server."
);
return;
}
Detailed server errors should normally be logged on the server rather than exposed to the mobile user.
A PHP REST API can return structured JSON when an error occurs.
{
"success": false,
"message": "Student not found"
}
React Native can read the message and display it to the user.
{
"success": false,
"message": "Validation failed",
"errors": {
"name": "Name is required",
"email": "Invalid email"
}
}
The errors object can contain field-specific validation messages.
try/catch can handle errors such as network failures or exceptions thrown by your own code.
try {
const response = await fetch(
API_URL
);
console.log(response);
} catch (error) {
console.log(
error.message
);
}
If the device cannot reach the server, Fetch may reject the promise and the catch block can handle the error.
try {
const response =
await fetch(API_URL);
} catch (error) {
Alert.alert(
"Network Error",
"Please check your internet connection."
);
}
try {
const response = await fetch(
API_URL
);
if (!response.ok) {
throw new Error(
`HTTP ${response.status}`
);
}
const result =
await response.json();
console.log(result);
} catch (error) {
console.log(
error.message
);
}
const result =
await response.json();
if (!response.ok) {
throw new Error(
result.message ||
"API request failed"
);
}
The API's message can provide a more useful explanation than a generic error message.
async function handleResponse(
response
) {
const result =
await response.json();
if (!response.ok) {
throw new Error(
result.message ||
"API request failed"
);
}
return result;
}
async function loadStudents() {
setLoading(true);
setError("");
try {
const response =
await fetch(API_URL);
if (!response.ok) {
const result =
await response.json();
throw new Error(
result.message ||
"Request failed"
);
}
const result =
await response.json();
setStudents(
result.data || []
);
} catch (error) {
setError(
error.message
);
} finally {
setLoading(false);
}
}
{error !== "" && (
<View>
<Text>
{error}
</Text>
</View>
)}
A clear message helps users understand what they should do next.
A retry button can call the same API function again.
<Button
title="Try Again"
onPress={loadStudents}
/>
Retry functionality is especially useful for temporary network problems.
API Request
↓
HTTP 401
↓
Token Invalid / Expired
↓
Clear Local Session
↓
Go to Login Screen
The exact authentication flow depends on how the application's token system is designed.
The PHP API should avoid returning sensitive technical details such as database credentials, SQL queries, secret keys, or stack traces.
A safer response is:
{
"success": false,
"message": "Something went wrong"
}
Detailed information can be written to server logs for debugging.
async function addStudent() {
try {
const response =
await fetch(API_URL, {
method: "POST",
headers: {
"Content-Type":
"application/json"
},
body: JSON.stringify({
name,
email
})
});
const result =
await response.json();
if (!response.ok) {
throw new Error(
result.message ||
"Unable to add student"
);
}
Alert.alert(
"Success",
result.message
);
} catch (error) {
Alert.alert(
"Error",
error.message
);
}
}
async function deleteStudent(id) {
try {
const response =
await fetch(
`${API_URL}?id=${id}`,
{
method: "DELETE"
}
);
const result =
await response.json();
if (!response.ok) {
throw new Error(
result.message ||
"Delete failed"
);
}
Alert.alert(
"Success",
result.message
);
} catch (error) {
Alert.alert(
"Error",
error.message
);
}
}
Larger React Native applications should avoid repeating the same error handling code in every screen.
async function apiRequest(
url,
options = {}
) {
try {
const response =
await fetch(url, options);
const result =
await response.json();
if (!response.ok) {
throw new Error(
result.message ||
"API request failed"
);
}
return result;
} catch (error) {
throw error;
}
}
const API_URL =
"https://example.com/api/students.php";
async function loadStudents() {
setLoading(true);
setError("");
try {
const response =
await fetch(API_URL);
let result;
try {
result =
await response.json();
} catch (jsonError) {
throw new Error(
"Invalid server response"
);
}
if (!response.ok) {
if (response.status === 401) {
throw new Error(
"Please login again."
);
}
if (response.status === 403) {
throw new Error(
"You do not have permission."
);
}
if (response.status === 404) {
throw new Error(
"Data was not found."
);
}
if (response.status === 422) {
throw new Error(
result.message ||
"Validation failed."
);
}
if (response.status >= 500) {
throw new Error(
"Server error. Please try again later."
);
}
throw new Error(
result.message ||
"API request failed"
);
}
if (!result.success) {
throw new Error(
result.message ||
"Unable to load data"
);
}
setStudents(
result.data || []
);
} catch (error) {
setError(
error.message ||
"Network error"
);
} finally {
setLoading(false);
}
}
React Native
↓
Fetch Request
↓
API Server
↓
HTTP Response
↓
┌──────┴─────────┐
↓ ↓
Success Error
↓ ↓
200/201 Check Status
↓ ↓
Show Data 400/401/403
404/422/500
↓
Read JSON Error
↓
Show Message
↓
Retry / Login
↓
Update UI
Proper error handling makes a React Native REST API application more reliable, secure, and user-friendly.
Question: Which HTTP status code commonly indicates that a user is not authenticated?