Error Handling in NestJS for Robust APIs

Nestjs is offering a powerful framework with out-of-the-box handling of error handling concepts. It contains a comprehensive exception handling mechanism that catches thrown exceptions and automatically converts them into client-friendly responses.

Error Handling in NestJS for Robust APIs
Photo by Gareth Harrison / Unsplash

Error handling is an essential component of any robust API. It ensures that when things go awry during API request processing, the errors aren't just caught but are also turned into meaningful HTTP responses for clients. Properly managed API error handling enables a smoother and more consistent user experience, offering clients actionable insights instead of generic status messages.

The Importance of Error Handling in API Development

Firstly, error handling within API development is not just a best practice; it's a requisite for creating APIs that are stable, secure, and user-friendly. It governs the behavior of an API when it encounters unexpected conditions and helps maintain a reliable communication process between client and server.

When discussing NestJS error handling, we enter a landscape that's well-structured and addressed systematically, thanks to NestJS's built-in mechanisms. Before delving deep into NestJS, let's set the stage with a basic example from a pure Node.js environment.

What usual Express Node.js API Error Handling Looks Like

Imagine we're creating a simple `/users/:id` endpoint using Express, a popular Node.js web application framework. This is what a basic error handling scenario might look like:

app.get('/users/:id', async (req, res) => {
  const { id } = req.params;
  const user = await getUserById(id);

  if (!user) {
    res.status(404).json({ error: 'User not found' });
  } else {
    res.json(user);
  }
});

typical error handling in express

This simplistic version gets straight to the point—if a user with the given ID exists, they are returned with a `200 OK` status code; if not, a `404 Not Found` response is issued, accompanied by a relevant error message. However, when your API scales, maintaining this straightforward protocol becomes convoluted due to the increased complexity and variety of endpoints and error scenarios.

NestJS: Simplifying API Error Handling

NestJS enters as a savior here, offering a powerful framework with out-of-the-box handling of these concepts. It contains a comprehensive exception handling mechanism that catches thrown exceptions and automatically converts them into client-friendly responses.

Within a NestJS controller, fetching a user might take on this form:

@Controller('users')
export class UserController {
  constructor(private readonly userService: UserService) {}

  @Get(':id')
  async getUser(@Param('id') id: string) {
    return this.userService.getUser(id);
  }
}

Fetching user

Meanwhile, the corresponding service might look like:

@Injectable()
export class UserService {
  constructor(private readonly userRepository: UserRepository) {}

  async getUser(id: string) {
    const user = await this.userRepository.findOne(id);

    if (!user) {
      throw new NotFoundException('User not found');
    }

    return user;
  }
}

Querying user from a repository

In this scenario, NestJS handles the thrown `NotFoundException` and crafts an appropriate response for the client, which is clean, intuitive, and shields the controller from the intricacies of error management.

Taking Control with Exception Filters in NestJS

NestJS doesn't limit you with its defaults; it provides exception filters, powerful components that allow you to tailor the process further. Exception filters offer the flexibility to change the response format or capture particular exception types and treat them differently.


Here's how you can write an exception filter in NestJS:

@Catch(HttpException)
export class HttpExceptionFilter implements ExceptionFilter {
  catch(exception: HttpException, host: ArgumentsHost) {
    const ctx = host.switchToHttp();
    const response = ctx.getResponse<Response>();
    const request = ctx.getRequest<Request>();
    const status = exception.getStatus();
    
    response
      .status(status)
      .json({
        statusCode: status,
        timestamp: new Date().toISOString(),
        path: request.url,
        message: exception.getResponse()['message'],
      });
  }
}

Example of HttpExceptionFilter

After implementing your custom filter, integrate it within your NestJS application in the `main.ts` file, using the `useGlobalFilters` method to apply it across the application:


async function bootstrap() {
  const app = await NestFactory.create(AppModule);

  // ...
  
  app.useGlobalFilters(new HttpExceptionFilter());

  // ...

  await app.listen(3000);
}

bootstrap();

Apply HttpExceptionFilter globally

or by utilising @UseFilters decorator like this:

@Controller('users')
export class UserController {
  constructor(private readonly userService: UserService) {}

  @Get(':id')
  @UseFilters(HttpExceptionFilter) // filter decorator
  async getUser(@Param('id') id: string) {
    // 
  }
}

Use HttpExceptionFilter with decorator


Wrapping Up with NestJS Error Handling

By utilizing NestJS's built-in features and customizations, developers can short-circuit annoying boilerplate code and maintain focus on creating resilient APIs with user-friendly error responses. This creates a unified experience across endpoints and eases maintenance and scaling by following NestJS conventions.

Stay Connected for More NestJS Insights

Remember, error handling in NestJS or in any other API context is not just about catching exceptions; it's about providing meaningful feedback to clients that can guide their next actions. Never underestimate the power of a well-informed HTTP 400 or HTTP 500 response!

If you like the article consider subscribing to a newsletter!