Exception Handling Overview

exception-handling-overview

Handling exceptions effectively is crucial for writing robust, maintainable, and secure applications, especially in a fullstack architecture like Next.js (frontend) and NestJS (backend).

🌐 GENERAL CONCEPTS

✅ What is Exception Handling?

Exception handling is the process of catching errors at runtime and responding to them gracefully without crashing the app.

🔥 CORE THINGS YOU NEED TO KNOW

1. Types of Errors

Type Description Example
Syntax Error Code can’t run Missing bracket if (...) {
Runtime Error Code crashes while running Null reference
Logic Error Code runs but wrong result Wrong conditional check

📦 NESTJS – BACKEND EXCEPTION HANDLING

NestJS is built on Express (or optionally Fastify), and has powerful tools for error handling.

🔹 1. Use Built-in Exceptions

NestJS provides a set of HTTP exception classes.

import { BadRequestException, NotFoundException } from '@nestjs/common';

throw new BadRequestException('Invalid input');
Exception HTTP Code
BadRequestException 400
UnauthorizedException 401
ForbiddenException 403
NotFoundException 404
InternalServerErrorException 500

🔹 2. Use Filters (@Catch) for Custom Handling

import {
  ExceptionFilter, Catch, ArgumentsHost, HttpException, Logger,
} from '@nestjs/common';

@Catch()
export class AllExceptionsFilter implements ExceptionFilter {
  catch(exception: unknown, host: ArgumentsHost) {
    const ctx = host.switchToHttp();
    const response = ctx.getResponse();

    const status = exception instanceof HttpException
      ? exception.getStatus()
      : 500;

    const message = exception instanceof HttpException
      ? exception.getResponse()
      : 'Internal server error';

    Logger.error(`[Exception]: ${JSON.stringify(message)}`);

    response.status(status).json({
      statusCode: status,
      message,
      timestamp: new Date().toISOString(),
    });
  }
}

Then use it globally:

// main.ts
app.useGlobalFilters(new AllExceptionsFilter());

🔹 3. Best Practices for NestJS

  • Throw, don’t return errors: throw new BadRequestException(...)
  • ✅ Centralize error formatting with global filter
  • 🎯 Use try/catch in services if working with external APIs or DB

⚛️ NEXT.JS – FRONTEND EXCEPTION HANDLING

🔹 1. Handle Client-Side Errors

try {
  const res = await fetch('/api/data');
  if (!res.ok) throw new Error('Failed to fetch');
} catch (err) {
  console.error(err.message);
}

🔹 2. Handle API Route Errors (e.g., /api/xyz)

export default async function handler(req, res) {
  try {
    // logic here
    res.status(200).json({ data: 'ok' });
  } catch (err) {
    console.error('API Error:', err);
    res.status(500).json({ error: 'Internal Server Error' });
  }
}

🔹 3. Use Error Boundaries for UI Crashes

// ErrorBoundary.tsx
class ErrorBoundary extends React.Component {
  state = { hasError: false };

  static getDerivedStateFromError(error) {
    return { hasError: true };
  }

  componentDidCatch(error, errorInfo) {
    console.error('Error caught in boundary:', error, errorInfo);
  }

  render() {
    if (this.state.hasError) {
      return <p>Something went wrong.p>;
    }
    return this.props.children;
  }
}

Then wrap your component:

<ErrorBoundary>
  <MyComponent />
ErrorBoundary>

🚨 CATEGORIES OF EXCEPTIONS TO HANDLE

Category Examples Layer
Validation Missing fields, wrong formats Backend
Auth JWT expired, unauthorized Both
External API Timeout, invalid key Backend
Server Crash Unhandled exception Backend
UI Error Render crash Frontend
Network Fetch fail, offline Frontend

⚙️ TOOLING TIPS

  • ✅ Use Zod or class-validator for schema/DTO validation
  • ✅ Integrate Sentry for real-time exception logging
  • ✅ Use Axios interceptors (client + server) to standardize error format
  • ✅ Write unit tests to simulate exceptions

🔐 SECURITY ADVICE

  • ❌ Never expose raw errors or stack traces to users
  • ✅ Mask sensitive error messages (e.g., DB error → “Something went wrong”)
  • ✅ Log full details internally with request context

✅ SUMMARY CHECKLIST

Area What to Ensure
✅ Throw exceptions properly in NestJS Use built-in HttpException classes
✅ Format all errors in consistent structure Use ExceptionFilter
✅ Handle both client-side and API fetch errors in Next.js Try/catch, .ok checks
✅ Prevent UI crashes Use
✅ Add observability Use logs, alerts, tools like Sentry
✅ Protect from info leaks Sanitize messages shown to user

⚙️ PART 1: ERROR HANDLING PATTERN OVERVIEW

We’ll standardize errors across the entire stack using the following pattern:

🧱 Standard Error Structure

Every error from backend should follow a consistent shape:

interface AppErrorResponse {
  statusCode: number;
  error: string; // e.g., "Bad Request"
  message: string | string[];
  timestamp: string;
  path?: string;
}

This will ensure the frontend can expect and format errors consistently.

🧠 PART 2: NESTJS CUSTOM EXCEPTION PATTERN

✅ 1. Base App Exception Class

Create a base exception to extend:

// src/common/exceptions/app.exception.ts
import { HttpException, HttpStatus } from '@nestjs/common';

export class AppException extends HttpException {
  constructor(message: string, statusCode: number = HttpStatus.INTERNAL_SERVER_ERROR, error = 'Application Error') {
    super(
      {
        statusCode,
        message,
        error,
        timestamp: new Date().toISOString(),
      },
      statusCode,
    );
  }
}

✅ 2. Custom Exception Examples

// src/common/exceptions/invalid-input.exception.ts
import { AppException } from './app.exception';
import { HttpStatus } from '@nestjs/common';

export class InvalidInputException extends AppException {
  constructor(message = 'Invalid input provided') {
    super(message, HttpStatus.BAD_REQUEST, 'Bad Request');
  }
}

// src/common/exceptions/resource-not-found.exception.ts
import { AppException } from './app.exception';
import { HttpStatus } from '@nestjs/common';

export class ResourceNotFoundException extends AppException {
  constructor(resource = 'Resource') {
    super(`${resource} not found`, HttpStatus.NOT_FOUND, 'Not Found');
  }
}

You can now throw meaningful errors:

if (!user) throw new ResourceNotFoundException('User');

✅ 3. Global Exception Filter (Formatter)

// src/common/filters/global-exception.filter.ts
import {
  ExceptionFilter,
  Catch,
  ArgumentsHost,
  HttpException,
  HttpStatus,
} from '@nestjs/common';

@Catch()
export class GlobalExceptionFilter implements ExceptionFilter {
  catch(exception: unknown, host: ArgumentsHost) {
    const ctx = host.switchToHttp();
    const response = ctx.getResponse();
    const request = ctx.getRequest();

    const status =
      exception instanceof HttpException
        ? exception.getStatus()
        : HttpStatus.INTERNAL_SERVER_ERROR;

    const errorResponse =
      exception instanceof HttpException
        ? exception.getResponse()
        : {
            statusCode: HttpStatus.INTERNAL_SERVER_ERROR,
            message: 'Internal server error',
            error: 'Internal Server Error',
          };

    const finalError =
      typeof errorResponse === 'string'
        ? {
            statusCode: status,
            message: errorResponse,
            error: 'Error',
          }
        : errorResponse;

    response.status(status).json({
      ...finalError,
      timestamp: new Date().toISOString(),
      path: request.url,
    });
  }
}

➕ Register Globally

// main.ts
import { GlobalExceptionFilter } from './common/filters/global-exception.filter';
app.useGlobalFilters(new GlobalExceptionFilter());

🧑‍🎨 PART 3: NEXT.JS FRONTEND PATTERN

✅ 1. Shared Error Interface

// types/ErrorResponse.ts
export interface AppErrorResponse {
  statusCode: number;
  message: string | string[];
  error: string;
  timestamp: string;
  path?: string;
}

✅ 2. Fetch Wrapper

// lib/fetcher.ts
import { AppErrorResponse } from '@/types/ErrorResponse';

export async function safeFetch<T>(url: string, options?: RequestInit): Promise<T> {
  const res = await fetch(url, options);

  if (!res.ok) {
    const err: AppErrorResponse = await res.json();
    throw err;
  }

  return res.json();
}

✅ 3. Example Usage

import { useEffect, useState } from 'react';
import { safeFetch } from '@/lib/fetcher';
import { AppErrorResponse } from '@/types/ErrorResponse';

export default function UserDetails() {
  const [error, setError] = useState<AppErrorResponse | null>(null);
  const [user, setUser] = useState<any>(null);

  useEffect(() => {
    safeFetch('/api/user/me')
      .then(setUser)
      .catch(setError);
  }, []);

  if (error) return <p className="text-red-600">⚠️ {error.message}p>;

  return <pre>{JSON.stringify(user, null, 2)}pre>;
}

📦 BONUS: DTO Validation Exception Integration

Use class-validator and map ValidationException to our structure:

// In validation pipe
app.useGlobalPipes(new ValidationPipe({
  exceptionFactory: (errors) => {
    const messages = errors.map(err => `${err.property} - ${Object.values(err.constraints).join(', ')}`);
    return new AppException(messages, 400, 'Validation Failed');
  }
}));

✅ Summary

Component Role
AppException Base for custom exceptions
GlobalExceptionFilter Standardize structure
safeFetch() Ensure frontend receives predictable errors
AppErrorResponse Shared error interface
class-validatorAppException Hook DTO validation into custom errors
Total
0
Shares
Leave a Reply

Your email address will not be published. Required fields are marked *

Previous Post
mempersiapkan-lingkungan-pengembangan-elixir

Mempersiapkan Lingkungan Pengembangan Elixir

Next Post
how-to-build-an-android/ios-app-to-scan,-edit,-and-save-documents-with-flutter-and-html5

How to Build an Android/iOS App to Scan, Edit, and Save Documents with Flutter and HTML5

Related Posts