# Exceptions

Ts.ED http exceptions provide classes to throw standard HTTP exceptions. These exceptions can be used on Controller, Middleware or injectable Service. Emitted exceptions will be handle by the and formatted to an Express response with the right status code and headers.

An other thing. This module can be used with a pure Express application.

# Installation

npm install @tsed/exceptions
// or
yarn add @tsed/exceptions
1
2
3

# Throwing standard exceptions

Here is two example to throw exceptions based on this package in Ts.ED context or Express.js context:

    # Custom exception

    It's possible to create your own exception by creating a class which inherit from or one of the built-in exception like .

    Example:

    import {BadRequest} from "@tsed/exceptions";
    
    export class IDFormatException extends BadRequest {
      constructor() {
        super("ID format is not valid");
      }
    }
    
    1
    2
    3
    4
    5
    6
    7

    Since IDFormatException extends the , it will work seamlessly with the built-in exception handler, and therefore we can use it inside a controller method.

    import {Controller, Get, Inject, PathParams} from "@tsed/common";
    import {CalendarsService} from "../services/CalendarsService";
    import {IDFormatException} from "../errors/IDFormatException";
    
    @Controller("/calendars")
    export class CalendarCtrl {
      @Inject()
      calendarsService: CalendarsService;
    
      @Get("/:id")
      async get(@PathParams("id") id: number) {
        if (isNaN(+id)) {
          throw new IDFormatException();
        }
      }
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16

    # Built-in exceptions

    Ts.ED provides a set of standard exceptions that inherit from the base . These are exposed from the @tsed/exceptions package, and represent many of the most common HTTP exceptions:

    # Redirections (3xx)

    # Client errors (4xx)

    # Server errors (5xx)

    # Exception filter

    All errors are intercepted by the since v5.64.0+, and by before.

    By default, all HTTP Exceptions are automatically sent to the client, and technical errors are sent as Internal Server Error.

    The default format is an HTML content, but it couldn't be useful for your consumers.

    # with Catch decorator v5.64.0+

    The new Platform API introduce a new way to catch an exception with the decorator and to let you control the exact flow of control and the content of the response sent back to the client.

    Let's create an exception filter that is responsible for catching exceptions which are an instance of the class, and implementing custom response logic for them.

    To do this, we'll need to access the underlying platform Request and Response objects by using the decorator. We'll access the Request object, so we can pull out the original url and include that in the logging information. We'll use the Response object to take direct control of the response that is sent, using the response.body() method.

    import {Catch, PlatformContext, ExceptionFilterMethods, ResponseErrorObject} from "@tsed/common";
    import {Exception} from "@tsed/exceptions";
    
    @Catch(Exception)
    export class HttpExceptionFilter implements ExceptionFilterMethods {
      catch(exception: Exception, ctx: PlatformContext) {
        const {response, logger} = ctx;
        const error = this.mapError(exception);
        const headers = this.getHeaders(exception);
    
        logger.error({
          error
        });
    
        response
          .setHeaders(headers)
          .status(error.status)
          .body(error);
      }
    
      mapError(error: any) {
        return {
          name: error.origin?.name || error.name,
          message: error.message,
          status: error.status || 500,
          errors: this.getErrors(error)
        };
      }
    
      protected getErrors(error: any) {
        return [error, error.origin].filter(Boolean).reduce((errs, {errors}: ResponseErrorObject) => {
          return [...errs, ...(errors || [])];
        }, []);
      }
    
      protected getHeaders(error: any) {
        return [error, error.origin].filter(Boolean).reduce((obj, {headers}: ResponseErrorObject) => {
          return {
            ...obj,
            ...(headers || {})
          };
        }, {});
      }
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44

    note

    All exception filters should implement the generic ExceptionFilterMethods<T> interface. This requires you to provide the catch(exception: T, ctx: Context) method with its indicated signature. T indicates the type of the exception.

    The @Catch(Exception) decorator binds the required metadata to the exception filter, telling Ts.ED that this particular filter is looking for exceptions of type and nothing else. The decorator may take a single parameter, or a comma-separated list. This lets you set up the filter for several types of exceptions at once.

    If you want to catch all errors, just use the decorator with the Error class:

    import {Catch, PlatformContext, ExceptionFilterMethods, ResponseErrorObject} from "@tsed/common";
    import {Exception} from "@tsed/exceptions";
    
    @Catch(Error)
    export class ErrorFilter implements ExceptionFilterMethods {
      catch(exception: Exception, ctx: PlatformContext) {
        const {response, logger} = ctx;
        const error = this.mapError(exception);
        const headers = this.getHeaders(exception);
    
        logger.error({
          error
        });
    
        response
          .setHeaders(headers)
          .status(error.status || 500)
          .body(error);
      }
    
      mapError(error: any) {
        return {
          name: error.origin?.name || error.name,
          message: error.message,
          status: error.status || 500,
          errors: this.getErrors(error)
        };
      }
    
      protected getErrors(error: any) {
        return [error, error.origin].filter(Boolean).reduce((errs, {errors}: ResponseErrorObject) => {
          return [...errs, ...(errors || [])];
        }, []);
      }
    
      protected getHeaders(error: any) {
        return [error, error.origin].filter(Boolean).reduce((obj, {headers}: ResponseErrorObject) => {
          return {
            ...obj,
            ...(headers || {})
          };
        }, {});
      }
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44

    # With legacy GlobalErrorHandlerMiddleware deprecated

    You can register your own exception handler and change the response sent to your consumers.

    import {Err, Middleware, Req, Res} from "@tsed/common";
    
    @Middleware()
    export class GlobalErrorHandlerMiddleware {
      use(@Err() error: any, @Req() request: Req, @Res() response: Res) {
        response.status(error.status || 500).json({
          request_id: request.id,
          status: response.status,
          error_message: error.message
        });
      }
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12

    Then you just have adding this middleware in your Server.ts as following:

    import {PlatformApplication} from "@tsed/common";
    import {Configuration, Inject} from "@tsed/di";
    import {GlobalErrorHandlerMiddleware} from "./middlewares/GlobalErrorHandlerMiddleware";
    
    @Configuration({})
    export class Server {
      @Inject()
      app: PlatformApplication;
    
      $afterRoutesInit() {
        this.app.use(GlobalErrorHandlerMiddleware);
      }
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13

    # Migrate to Exception filter

    Exception filter and GlobalErrorHandlerMiddleware can be used at the same time excepted if you register your own middleware via the $afterRoutesInit hook.

    To facilitate your migration, remove the line where you add you custom middleware in the server:

    $afterRoutesInit() {
      this.app.use(CustomGlobalErrorHandlerMiddleware); // remove this
    }
    
    1
    2
    3

    Then, use the decorator over your custom middleware:

    import {OverrideProvider} from "@tsed/di";
    import {GlobalErrorHandlerMiddleware} from "@tsed/common";
    
    @OverrideProvider(GlobalErrorHandlerMiddleware)
    export class CustomGlobalErrorHandlerMiddleware extends GlobalErrorHandlerMiddleware {
    
    }
    
    1
    2
    3
    4
    5
    6
    7

    Now you are able to create your own exception filter. Start with the HttpException example:

    import {Catch, PlatformContext, ExceptionFilterMethods, ResponseErrorObject} from "@tsed/common";
    import {Exception} from "@tsed/exceptions";
    
    @Catch(Exception)
    export class HttpExceptionFilter implements ExceptionFilterMethods {
      catch(exception: Exception, ctx: PlatformContext) {
        const {response, logger} = ctx;
        const error = this.mapError(exception);
        const headers = this.getHeaders(exception);
    
        logger.error({
          error
        });
    
        response
          .setHeaders(headers)
          .status(error.status)
          .body(error);
      }
    
      mapError(error: any) {
        return {
          name: error.origin?.name || error.name,
          message: error.message,
          status: error.status || 500,
          errors: this.getErrors(error)
        };
      }
    
      protected getErrors(error: any) {
        return [error, error.origin].filter(Boolean).reduce((errs, {errors}: ResponseErrorObject) => {
          return [...errs, ...(errors || [])];
        }, []);
      }
    
      protected getHeaders(error: any) {
        return [error, error.origin].filter(Boolean).reduce((obj, {headers}: ResponseErrorObject) => {
          return {
            ...obj,
            ...(headers || {})
          };
        }, {});
      }
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44

    Then try with another error type and finally, remove your custom middleware.