Skip to content

NestJS Error Handling

This lesson steps back from individual exception classes and filters to look at error handling holistically, how to structure error handling consistently across an entire NestJS application.

A Layered Error Handling Strategy

Consistent error handling means every layer, pipes, guards, services, has a clear responsibility for what it validates or rejects, and a single global exception filter ensures every error, no matter where it's thrown, reaches the client in the same predictable shape.

{
  "statusCode": 404,
  "errorCode": "CAT_NOT_FOUND",
  "message": "Cat with id 42 not found",
  "timestamp": "2026-07-15T09:00:00.000Z",
  "path": "/cats/42"
}

Standardizing on one response shape, applied by a global exception filter, means API consumers can write one error-parsing function instead of handling several inconsistent shapes.

Where Each Kind of Error Belongs

Malformed input        -> ValidationPipe / custom pipe -> 400
Missing/invalid auth   -> Guard                        -> 401
Insufficient permission-> Guard                         -> 403
Resource not found     -> Service (throws)              -> 404
Business rule violated -> Service (throws)              -> 409 / 422
Unexpected failure     -> Global exception filter       -> 500
  • Input shape problems belong in pipes, caught before the handler ever runs.
  • Authentication and authorization belong in guards, not scattered checks inside services.
  • Domain-specific failures (not found, conflicts) belong in the service layer, closest to the business logic.
  • A global exception filter is the final safety net for anything unexpected.

Error Handling Layer Cheatsheet

Which layer should be responsible for each kind of failure.

Failure Type Responsible Layer Status
Malformed request body Pipe (ValidationPipe) 400
Missing credentials Guard 401
Insufficient role/permission Guard 403
Resource doesn't exist Service 404
Duplicate/conflicting resource Service 409
Unexpected/unknown error Global exception filter 500

Pairing Error Handling With Structured Logging

Every error caught by a global exception filter should be logged with enough context to debug it later, request path, method, user id (if available), and the original error/stack trace, while the response sent to the client stays generic for unexpected 500-level failures.

Avoid Silently Swallowing Errors

A try/catch that logs nothing and returns a generic success response hides real failures from both users and your own monitoring. Either let the error propagate to the exception layer, or handle it explicitly and deliberately, never silently.

Common Mistakes

  • Handling the same kind of error differently across different controllers or services.
  • Wrapping every operation in a broad try/catch that hides the real failure and returns a fake success.
  • Logging errors only to the console instead of a structured, searchable logging system in production.
  • Not distinguishing between expected failures (404, 409) and truly unexpected ones (500) in monitoring dashboards.

Key Takeaways

  • Each layer, pipes, guards, services, should own a clear category of error handling.
  • A single global exception filter guarantees one consistent error response shape.
  • Structured logging alongside error handling is essential for debugging production issues.
  • Never silently swallow errors, either propagate them or handle them explicitly.

Pro Tip

Write your global exception filter and its response shape before building out most features, retrofitting a consistent error format across dozens of already-written endpoints is far more work than establishing the convention on day one.