Chapter 7.4☕ 15 min read

Error Handling

Every HTTP request can fail. Handle errors gracefully with catchError, user-friendly messages, and a smart global/local strategy.

01Types of HTTP Errors

HTTP errors come in three flavors:

Client errors (4xx) — your fault, bad input:

400 Bad Request      — invalid data format
401 Unauthorized     — not logged in
403 Forbidden        — logged in but no permission
404 Not Found        — resource doesn't exist
422 Validation Error — server rejected the data

Server errors (5xx) — server's fault:

500 Internal Server Error   — server crashed
502 Bad Gateway             — upstream server failed
503 Service Unavailable     — server overloaded

Network errors — the road is closed:

0   — no internet, timeout, CORS issue (no status code at all)

"4xx = tumhari galti, 5xx = server ki galti, network = rasta hi band hai."

02HttpErrorResponse

When an HTTP call fails, Angular wraps the error in an HttpErrorResponse object with detailed properties.

import { HttpErrorResponse } from '@angular/common/http';

this.http.get('/api/biryani').subscribe({
  next: (data) => console.log('Success:', data),
  error: (err: HttpErrorResponse) => {
    console.log('Status:', err.status);         // 404, 500, etc.
    console.log('Message:', err.message);        // "Http failure response..."
    console.log('StatusText:', err.statusText);   // "Not Found", "OK"
    console.log('Error body:', err.error);        // Server's response body
    console.log('URL:', err.url);                 // The URL that failed
  }
});

"HttpErrorResponse = error ka report card — sab details mein."

Key properties:

  • error.status — HTTP status code (number)
  • error.statusText — status text ("Not Found")
  • error.message — human-readable message
  • error.error — server's response body (validation errors, error details)
  • error.url — the URL that failed
  • error.headers — response headers
03catchError Operator

catchError is the RxJS operator that catches errors in the Observable pipeline and lets you return a fallback.

import { catchError, of } from 'rxjs';

this.http.get<Biryani[]>('/api/biryani').pipe(
  catchError((error: HttpErrorResponse) => {
    // Log the error for debugging
    console.error('API Error:', error);

    // Handle specific status codes
    if (error.status === 404) {
      console.log('No biryanis found — showing empty state');
    }
    if (error.status === 500) {
      console.log('Server down — showing retry button');
    }
    if (error.status === 0) {
      console.log('Network error — check internet');
    }

    // Return a safe default value — component won't crash
    return of([]); // Returns Observable that emits empty array
  })
).subscribe(data => {
  this.biryanis = data; // data will be [] if error occurred
});

"catchError = jugaad — error aaya toh koi default value de do."

IMPORTANT: catchError MUST return an Observable. Use of() for a default value or throwError(() => error) to re-throw.

04Global vs Local Error Handling

You can handle errors at two levels — both work together.

Global error handling (interceptor):

// Handles ALL requests — common cases
export const errorInterceptor: HttpInterceptorFn = (req, next) => {
  return next(req).pipe(
    catchError(error => {
      if (error.status === 401) inject(Router).navigate(['/login']);
      if (error.status === 0) showToast('Network error');
      return throwError(() => error); // Re-throw for local handling
    })
  );
};

Local error handling (component):

// Handles SPECIFIC request — unique cases
this.biryaniService.getById(id).pipe(
  catchError(error => {
    if (error.status === 404) {
      this.showNotFoundMessage = true;
    }
    return of(null);
  })
).subscribe(biryani => {
  if (biryani) this.selectedBiryani = biryani;
});

"Global = police, Local = security guard — dono ka kaam alag."

Division of responsibility:

  • Global interceptor: 401 redirect, network error toast, logging
  • Local component: 404 empty state, specific validation error handling
  • Global handles the common cases; local handles the specific ones
05User-Friendly Error Messages

Never show raw error messages to users. Map them to friendly, relatable messages.

// ❌ Raw error — don't show this
"HttpErrorResponse: status 500, message: Internal Server Error"

// ✅ Friendly error — show this
"Server mein kuch problem hai, thodi der baad try karo"
export function getErrorMessage(error: HttpErrorResponse): string {
  switch (error.status) {
    case 400: return 'Galat data bheja hai — form check karo';
    case 401: return 'Pehle login karo bhai';
    case 403: return 'Isko dekhne ki permission nahi hai tumhare paas';
    case 404: return 'Ye biryani exist nahi karti bhai';
    case 422: return 'Server ne data reject kar diya — fields check karo';
    case 500: return 'Server mein kuch problem hai, thodi der baad try karo';
    case 502: return 'Upstream server down hai — kuch der ruko';
    case 503: return 'Server busy hai — thodi der mein aana';
    case 0:   return 'Internet connection check karo — network nahi chal raha';
    default:  return `Kuch to gadbad hai (Error: ${error.status})`;
  }
}

"Error message bhi Hyderabadi hona chahiye — user connect kare."

User-friendly errors improve UX dramatically. A 500 error with a friendly message feels much better than a cryptic technical error.

Key Takeaways

  • ✅ 3 types of errors: 4xx (client), 5xx (server), 0 (network)
  • ✅ HttpErrorResponse has status, statusText, message, error (body), url
  • ✅ catchError catches errors in the pipe — MUST return an Observable (of() or throwError)
  • ✅ Global handling (interceptor) + Local handling (component) work together
  • ✅ Always map raw errors to user-friendly messages — never show HttpErrorResponse to users