Skip to main content

Client Error Handling

When the server returns an error for a call, Axios rejects the promise with an error containing the HTTP status and data. This guide covers how to handle these errors effectively.

🎯 Basic Error Handling

Handle errors with try/catch:

import { rpc } from './rpc-client';
import { AxiosError } from 'axios';

try {
const { data: user } = await rpc.user.queries.getUser({ id: 'missing' });
} catch (error) {
if (error instanceof AxiosError) {
console.error('Status:', error.response?.status);
console.error('Data:', error.response?.data);
// { statusCode: 404, message: 'User not found' }
}
}

📋 Error Sources

Errors can come from different sources:

SourceTypeExample
Nest HttpExceptionAxiosErrorBadRequestException → 400
Network errorsAxiosErrorConnection timeout, DNS failure
Request cancellationAxiosErrorRequest aborted

🔍 Error Type Checking

Check error types for appropriate handling:

import { AxiosError } from 'axios';

async function getUserSafely(id: string) {
try {
const { data: user } = await rpc.user.queries.getUser({ id });
return { success: true, user };
} catch (error) {
if (error instanceof AxiosError) {
if (error.response) {
// Server responded with error
const status = error.response.status;
const data = error.response.data;

switch (status) {
case 400:
return { success: false, error: data.message || 'Bad request' };
case 401:
return { success: false, error: 'Unauthorized' };
case 403:
return { success: false, error: 'Forbidden' };
case 404:
return { success: false, error: 'Not found' };
case 500:
return { success: false, error: 'Server error' };
default:
return { success: false, error: 'Unknown error' };
}
} else if (error.request) {
// Request made but no response
return { success: false, error: 'Network error' };
}
}
return { success: false, error: 'Unknown error' };
}
}

🎨 React Error Handling

Handle errors in React components:

import { useState } from 'react';
import { rpc } from './rpc-client';
import { AxiosError } from 'axios';

function UserProfile({ userId }: { userId: string }) {
const [user, setUser] = useState(null);
const [error, setError] = useState<string | null>(null);
const [loading, setLoading] = useState(true);

useEffect(() => {
async function loadUser() {
try {
setLoading(true);
setError(null);
const { data } = await rpc.user.queries.getUser({ id: userId });
setUser(data);
} catch (err) {
if (err instanceof AxiosError) {
if (err.response?.status === 404) {
setError('User not found');
} else {
setError(err.response?.data?.message || 'Failed to load user');
}
} else {
setError('An unexpected error occurred');
}
} finally {
setLoading(false);
}
}

loadUser();
}, [userId]);

if (loading) return <div>Loading...</div>;
if (error) return <div>Error: {error}</div>;
if (!user) return <div>No user found</div>;

return <div>{user.name}</div>;
}

🔧 Axios Interceptors

Use interceptors for global error handling:

import axios, { AxiosError } from 'axios';
import { RpcClient } from '@nestjs-rpc/client';
import type { Manifest } from '../server/nest-rpc.config';

const axiosInstance = axios.create();

// Response interceptor for error handling
axiosInstance.interceptors.response.use(
(response) => response,
(error: AxiosError) => {
if (error.response?.status === 401) {
// Handle unauthorized - redirect to login
window.location.href = '/login';
} else if (error.response?.status === 403) {
// Handle forbidden
console.error('Access forbidden');
} else if (error.response?.status >= 500) {
// Handle server errors
console.error('Server error:', error.response?.data);
}
return Promise.reject(error);
}
);

const client = new RpcClient<Manifest>({
baseUrl: 'http://localhost:3000',
axiosInstance,
});

export const rpc = client.routers();

🎯 Error Helper Functions

Create reusable error handling utilities:

import { AxiosError } from 'axios';

export function getErrorMessage(error: unknown): string {
if (error instanceof AxiosError) {
if (error.response) {
return error.response.data?.message || 'An error occurred';
} else if (error.request) {
return 'Network error. Please check your connection.';
}
}
return 'An unexpected error occurred';
}

export function getErrorStatus(error: unknown): number | null {
if (error instanceof AxiosError && error.response) {
return error.response.status;
}
return null;
}

// Usage
try {
await rpc.user.queries.getUser({ id: '1' });
} catch (error) {
const message = getErrorMessage(error);
const status = getErrorStatus(error);
console.error(`Error ${status}: ${message}`);
}

🔄 Retry Logic

Implement retry logic for transient errors:

async function retryRequest<T>(
fn: () => Promise<T>,
maxRetries = 3,
delay = 1000
): Promise<T> {
for (let i = 0; i < maxRetries; i++) {
try {
return await fn();
} catch (error) {
if (i === maxRetries - 1) throw error;

// Only retry on network errors or 5xx errors
if (error instanceof AxiosError) {
const status = error.response?.status;
if (status && status >= 500) {
await new Promise(resolve => setTimeout(resolve, delay * (i + 1)));
continue;
}
}
throw error;
}
}
throw new Error('Max retries exceeded');
}

// Usage
const { data } = await retryRequest(() =>
rpc.user.queries.getUser({ id: '1' })
);

📚 Further Reading