Error Handling
Catching Problems Before They Crash
Open interactive version (quiz + challenge)Real-world analogy
Error handling is like a safety net under a trapeze artist. The artist (your code) performs amazing feats, but sometimes things go wrong mid-air. Without a safety net (try/catch), they crash to the ground (app crashes). With it, they bounce back and the show goes on!
What is it?
Error handling is the practice of anticipating, detecting, and gracefully responding to errors in your code. Instead of letting your app crash, you catch errors and either recover from them, show helpful messages, or log them for debugging. In NestJS, exception filters provide a structured way to handle HTTP errors.
Real-world relevance
Every production API must handle errors properly. A payment processing endpoint that crashes instead of returning a clear error message could cost thousands of dollars. Good error handling is the difference between a professional and amateur application.
Key points
- try/catch/finally — Wrap risky code in try { }. If it fails, catch(error) { } runs instead of crashing. finally { } runs no matter what — perfect for cleanup like closing connections.
- Error Types — TypeError (wrong type), ReferenceError (variable not found), SyntaxError (bad code), RangeError (out of bounds). Each tells you WHAT went wrong.
- Custom Error Classes — Extend the Error class for specific errors: class NotFoundError extends Error { }. Now you can catch different errors differently and add custom properties like statusCode.
- Async Error Handling — Async functions need try/catch around await calls. Without it, rejected promises silently fail. Always wrap await in try/catch!
- NestJS HttpException — throw new HttpException('Not found', 404) sends a proper HTTP error response. NestJS has built-in exceptions: NotFoundException, BadRequestException, UnauthorizedException, ForbiddenException.
- Exception Filters — NestJS exception filters catch errors globally or per-controller. They transform errors into clean API responses with proper status codes and messages.
- Validation Errors — Invalid user input should return 400 Bad Request with details about what went wrong. NestJS ValidationPipe does this automatically with class-validator.
- Error Logging — Always log errors with context: what happened, where, and the stack trace. In production, use structured logging (JSON format) for easy searching and alerting.
Code example
// Basic try/catch/finally
try {
const data = JSON.parse('invalid json');
} catch (error) {
console.error('Parse failed:', error.message);
} finally {
console.log('This always runs');
}
// Custom Error Class
class AppError extends Error {
constructor(
message: string,
public statusCode: number = 500,
public isOperational: boolean = true
) {
super(message);
this.name = 'AppError';
}
}
// Throw and catch custom errors
try {
throw new AppError('User not found', 404);
} catch (error) {
if (error instanceof AppError) {
console.log(error.statusCode); // 404
}
}
// Async error handling
async function fetchUser(id: string) {
try {
const user = await db.users.findUnique({ where: { id } });
if (!user) throw new AppError('User not found', 404);
return user;
} catch (error) {
console.error('Failed to fetch user:', error);
throw error; // re-throw for the caller to handle
}
}
// NestJS — Built-in exceptions
import { NotFoundException, BadRequestException } from '@nestjs/common';
@Get(':id')
async getUser(@Param('id') id: string) {
const user = await this.userService.findOne(id);
if (!user) throw new NotFoundException('User not found');
return user;
}
// NestJS — Custom Exception Filter
@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;
response.status(status).json({
statusCode: status,
message: exception instanceof Error ? exception.message : 'Internal error',
timestamp: new Date().toISOString(),
});
}
}Line-by-line walkthrough
- 1. Basic try/catch/finally
- 2. Opening try block for error handling
- 3. Declaring a variable
- 4. Catching any errors from the try block
- 5.
- 6. Finally block - runs regardless of success or failure
- 7. Printing output to the console
- 8. Closing block
- 9.
- 10. Custom Error Class
- 11. Declaring a class
- 12.
- 13.
- 14.
- 15.
- 16.
- 17.
- 18.
- 19. Closing block
- 20. Closing block
- 21.
- 22. Throw and catch custom errors
- 23. Opening try block for error handling
- 24. Throwing an error
- 25. Catching any errors from the try block
- 26. Conditional check
- 27. Printing output to the console
- 28. Closing block
- 29. Closing block
- 30.
- 31. Async error handling
- 32. Declaring a function
- 33. Opening try block for error handling
- 34. Declaring a variable
- 35. Conditional check
- 36. Returning a value
- 37. Catching any errors from the try block
- 38.
- 39. Throwing an error
- 40. Closing block
- 41. Closing block
- 42.
- 43. NestJS — Built-in exceptions
- 44. Importing required dependencies
- 45.
- 46. Decorator that adds metadata or behavior
- 47.
- 48. Declaring a variable
- 49. Conditional check
- 50. Returning a value
- 51. Closing block
- 52.
- 53. NestJS — Custom Exception Filter
- 54. Decorator that adds metadata or behavior
- 55. Exporting for use in other files
- 56. Catching any errors from the try block
- 57. Declaring a variable
- 58. Declaring a variable
- 59. Declaring a variable
- 60.
- 61.
- 62.
- 63.
- 64.
- 65.
- 66. Closing block
- 67. Closing block
Spot the bug
async function fetchData() {
const res = await fetch("/api/data");
const data = await res.json();
return data;
}Need a hint?
What happens if the fetch request fails?
Show answer
There is no try/catch, so if fetch fails, the error is unhandled and crashes the app. Fix: wrap in try/catch: try { ... } catch (error) { console.error('Failed:', error); }
Explain like I'm 5
Imagine doing a science experiment. Sometimes things go BOOM! Error handling is like wearing safety goggles. 'try' means 'let me try this experiment', 'catch' means 'oops, something went wrong', and 'finally' means 'clean up the lab no matter what'.
Fun fact
The most famous error in computing history is the Y2K bug. Programmers stored years as 2 digits (99 instead of 1999), meaning the year 2000 would appear as 00. Governments spent over $300 billion fixing it before midnight struck!
Hands-on challenge
Create a custom NotFoundError and ValidationError class extending Error. Write an async function that fetches a user by ID — throw NotFoundError if not found, ValidationError if the ID format is wrong. Handle both in the catch block!
More resources
- MDN Error Reference (MDN Web Docs)
- Node.js Error Handling Best Practices (Node.js Official)
- Error Handling in JavaScript (Web Dev Simplified)