Writing Clean and Maintainable Code in NestJS
Part 7 of 12
Effective Error Handling Without Messy Code
Error handling is critical in any application, but if done incorrectly, it can lead to spaghetti code, unnecessary complexity, and poor maintainability.
In this guide, we’ll cover:
- Why error handling is important
- Common bad practices
- The global exception filter in NestJS
- Using custom error classes
- Handling async/await errors properly
- Best practices for API error responses
1. Why Clean Error Handling Matters
A poorly handled error can:
❌ Crash your application unexpectedly
❌ Make debugging harder
❌ Create nested try/catch blocks everywhere
❌ Lead to inconsistent error messages
A well-structured approach:
✅ Keeps the code clean
✅ Prevents unnecessary crashes
✅ Standardizes error messages
✅ Improves debugging & logging
2. Common Bad Practices in Error Handling
❌ Catching Errors Without Handling Them
try {
const user = await this.userService.findUser(id);
} catch (error) {
// Do nothing
}
🚨 Problem: The error is swallowed, making debugging difficult.
❌ Using Generic Error Messages
throw new Error('Something went wrong');
🚨 Problem: No context is provided for debugging.
❌ Overusing try/catch Blocks Everywhere
try {
const user = await this.userService.findUser(id);
try {
const orders = await this.orderService.getUserOrders(user.id);
} catch (error) {
console.error(error);
}
} catch (error) {
console.error(error);
}
🚨 Problem: Creates deep nesting, making the code unreadable.
3. Using NestJS Exception Filters (Global Error Handling)
NestJS provides a built-in way to handle errors globally using exception filters.
📌 Create a Global Exception Filter
import {
ExceptionFilter,
Catch,
ArgumentsHost,
HttpException,
InternalServerErrorException,
} from '@nestjs/common';
import { Response } from 'express';
@Catch()
export class GlobalExceptionFilter implements ExceptionFilter {
catch(exception: unknown, host: ArgumentsHost) {
const ctx = host.switchToHttp();
const response = ctx.getResponse<Response>();
if (exception instanceof HttpException) {
const status = exception.getStatus();
const message = exception.getResponse();
response.status(status).json({ success: false, message });
} else {
console.error(exception);
response
.status(500)
.json({ success: false, message: 'Internal server error' });
}
}
}
📌 Apply It in main.ts
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { GlobalExceptionFilter } from './filters/global-exception.filter';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.useGlobalFilters(new GlobalExceptionFilter());
await app.listen(3000);
}
bootstrap();
✅ Now, all uncaught exceptions will be handled globally, reducing unnecessary try/catch blocks!
4. Creating Custom Error Classes
Instead of using throw new Error()
, define specific error classes.
📌 Create CustomException.ts
import { HttpException, HttpStatus } from '@nestjs/common';
export class NotFoundException extends HttpException {
constructor(resource: string) {
super(`${resource} not found`, HttpStatus.NOT_FOUND);
}
}
export class ValidationException extends HttpException {
constructor(errors: string[]) {
super({ message: 'Validation failed', errors }, HttpStatus.BAD_REQUEST);
}
}
📌 Use Them in Your Service
if (!user) {
throw new NotFoundException('User');
}
if (!isValid(data)) {
throw new ValidationException(['Email is required', 'Password too short']);
}
✅ This improves clarity and standardizes error messages.
5. Handling Async/Await Errors Properly
❌ Bad: Using try/catch Everywhere
try {
const user = await this.userService.findUser(id);
return user;
} catch (error) {
console.error(error);
throw new InternalServerErrorException();
}
✅ Good: Letting NestJS Handle It
async getUser(id: string) {
const user = await this.userService.findUser(id);
if (!user) throw new NotFoundException('User');
return user;
}
🚀 NestJS automatically catches unhandled exceptions, so unnecessary try/catch is avoided!
6. Best Practices for API Error Responses
✅ Consistent Error Response Structure
All errors should return structured JSON responses:
{
"success": false,
"message": "Validation failed",
"errors": ["Email is required", "Password too short"]
}
✅ Improves API documentation
✅ Easier for frontend teams to handle errors
7. Summary: Writing Clean Error Handling in NestJS
Key Takeaways
✅ Use global exception filters to handle errors centrally
✅ Create custom error classes instead of generic errors
✅ Avoid deep try/catch nesting—let NestJS handle async errors
✅ Standardize API error responses for better debugging