DEV Community

Anton Bahurinsky
Anton Bahurinsky

Posted on

NestJS: stop handling errors like this!

I see people making this mistake all the time.

Let's say you've got an API server written in NestJS, and you need an endpoint for fetching a single product by its ID. So, in your product.service.ts you would typically write:

async findOne(id: number) {
  try {
    return await this.productRepository.findOneOrFail(id);
  } catch (err) {
    throw new NotFoundException();
  }
}
Enter fullscreen mode Exit fullscreen mode

(I'm using TypeORM here, but the same principle can be applied to other libraries as well.)

So what we've got here? You attempt to query a product by its ID, and if there is no product with this ID, you just throw the 404. And in your product.controller.ts you simply write:

@Get(':id')
findOne(@Param('id') id: string) {
  return this.productService.findOne(+id);
}
Enter fullscreen mode Exit fullscreen mode

And everything indeed works fine.

So what's the problem with this?

The thing is that the above code would work well for REST APIs. But what if tomorrow you will need to fetch that product via GraphQL or WebSockets? The NotFoundException() and its fellow HTTP-related exceptions will not be suitable for that any more. You will definitely need different error handling.

Therefore, by throwing HTTP-related errors (exceptions) from services you simply make your code less reusable.

What to do instead?

As we have implicitly mentioned above, the HTTP-related exceptions are suited only for REST APIs. But so are controllers!

You see, NestJS controllers are used only during REST API development, while services can have wider use. This makes controllers the perfect place for throwing HTTP-related exceptions.

Thus, in the simplest scenario, your product.service.ts code (fragment) would look just like this:

findOne(id: number) {
  return this.productRepository.findOneOrFail(id);
}
Enter fullscreen mode Exit fullscreen mode

And in the product.controller.ts you now handle the "not found" error:

@Get(':id')
async findOne(@Param('id') id: string) {
  try {
    return await this.productService.findOne(+id);
  } catch (err) {
    throw new NotFoundException();
  }
}
Enter fullscreen mode Exit fullscreen mode

Of course, if you need more sophisticated error handling, you can define custom error classes to throw from services and handle in controllers (for REST) or resolvers (for GraphQL).

Conclusion

Don't throw HTTP-related exceptions from services, throw them from controllers!

Have you been making this mistake before reading this article? Comment down below!

And, of course, leave your reaction to this article, share it with your friends via social media and follow me on this platform!

Don't stop coding,
don't stop growing,
stand with Ukraine! πŸ‡ΊπŸ‡¦

Oldest comments (6)

Collapse
 
mattcobley profile image
Matt Cobley

I'd say you've got a bigger problem: why are you catching an exception and then just throwing a new, more generic one? You're making the calling layer still manage an exception from the service layer, but you're not logging or doing anything else with it in the service layer to warrant catching it in the first place. I'd remove the try/ catch block entirely and just have the calling layer (the controller) decide what to do with it unless you have a good reason to obscure the real error. Sure, you want to control what is returned to the client, but your do that on the calling layer - in this case the controller. Hopefully that makes sense and is helpful?

Collapse
 
antoncodes profile image
Anton Bahurinsky

If I understand you correctly, the error to be shown to the client is indeed thrown by the calling layer (the controller), which is the main point of the article.

I can agree with you that more sophisticated error handling is usually needed. But in this case it is omitted for the sake of simplicity, to not to clutter up the primary idea.

Collapse
 
bishoymelek profile image
bishoymelek

I am sorry but what's the plus you are adding while passing id as a param?

Collapse
 
antoncodes profile image
Anton Bahurinsky

You mean passing the product ID as a URL parameter? Well, the ID could indeed have been passed differently, e.g. as a query parameter or even within a body (not recommended though, especially for GET requests). That's up to developer's choice.
However, by passing the product ID as a URL parameter, we get somewhat neater final URLS, for example:
GET https://mywebapp.com/product/12345
(as opposed to:
GET https://mywebapp.com/product?id=12345)

Collapse
 
techemmy profile image
Emmanuel Temitope

It's for type-casting. @Param('id') id will return a string. Adding a plus converts the string to a number.

Collapse
 
fazalerabbi profile image
Fazal E Rabbi

Thank you for this post. What I understand from your post is, handle exception in controller instead of service. I am in favour of handling exception in service because, when ever we need to call service method anywhere in application we need to handle exception. So have to handle exception multiple time. That why handling exception in service is help-full. Please correct me if I am wrong.
Thank you