# 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 @@BadRequest@, 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)

    # Handle all errors

    All errors are intercepted by the .

    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. 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

    # Advanced example

    Here is another example of GlobalErrorHandlerMiddleware implementation. This example show you, how you can handler any kind of error and transform the error to a proper JSON response.

    import {Err, IMiddleware, IResponseError, Middleware, Req, Res} from "@tsed/common";
    import {Env} from "@tsed/core";
    import {Constant} from "@tsed/di";
    import {Exception} from "@tsed/exceptions";
    
    const toHTML = (message = "") => message.replace(/\n/gi, "<br />");
    
    function getErrors(error: any) {
      return [error, error.origin]
        .filter(Boolean)
        .reduce((errs, {errors}: IResponseError) => {
          return [
            ...errs,
            ...errors || []
          ];
        }, []);
    }
    
    function getHeaders(error: any) {
      return [error, error.origin]
        .filter(Boolean)
        .reduce((obj, {headers}: IResponseError) => {
          return {
            ...obj,
            ...headers || {}
          };
        }, {});
    }
    
    @Middleware()
    export class GlobalErrorHandlerMiddleware implements IMiddleware {
      @Constant("env")
      env: Env;
    
      use(@Err() error: any, @Req() request: Req, @Res() response: Res): any {
        if (typeof error === "string") {
          response.status(404).send(toHTML(error));
    
          return;
        }
    
        if (error instanceof Exception || error.status) {
          this.handleException(error, request, response);
    
          return;
        }
    
        this.handleError(error, request, response);
    
        return;
      }
    
      protected handleError(error: any, request: Req, response: Res) {
        const logger = request.ctx.logger;
        const err = this.mapError(error);
    
        logger.error({
          error: err
        });
    
        response
          .set(getHeaders(error))
          .status(err.status).json(this.env === Env.PROD ? "InternalServerError" : err);
      }
    
      protected handleException(error: any, request: Req, response: Res) {
        const logger = request.ctx.logger;
        const err = this.mapError(error);
        logger.error({
          error: err
        });
    
        response
          .set(getHeaders(error))
          .status(error.status)
          .json(err);
      }
    
      protected mapError(error: any) {
        return {
          message: error.message,
          stack: this.env === Env.DEV ? error.stack : undefined,
          status: error.status || 500,
          origin: {
            ...error.origin || {},
            errors: undefined
          },
          errors: getErrors(error)
        };
      }
    }
    
    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
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91

    Here is the original :

    import {Constant} from "@tsed/di";
    import {Exception} from "@tsed/exceptions";
    import {Err, IMiddlewareError, IResponseError, Middleware, Req, Res} from "../../mvc";
    
    /**
     * @middleware
     */
    @Middleware()
    export class GlobalErrorHandlerMiddleware implements IMiddlewareError {
      @Constant("errors.headerName", "errors")
      protected headerName: string;
    
      use(@Err() error: any, @Req() request: Req, @Res() response: Res): any {
        const logger = request.ctx.logger;
    
        const toHTML = (message = "") => message.replace(/\n/gi, "<br />");
    
        if (error instanceof Exception || error.status) {
          logger.error({
            error: {
              message: error.message,
              stack: error.stack,
              status: error.status,
              origin: error.origin
            }
          });
    
          this.setHeaders(response, error, error.origin);
    
          response.status(error.status).send(toHTML(error.message));
    
          return;
        }
    
        if (typeof error === "string") {
          response.status(404).send(toHTML(error));
    
          return;
        }
    
        logger.error({
          error: {
            status: 500,
            message: error.message,
            stack: error.stack,
            origin: error.origin
          }
        });
    
        this.setHeaders(response, error, error.origin);
    
        response.status(error.status || 500).send("Internal Error");
    
        return;
      }
    
      setHeaders(response: Res, ...args: IResponseError[]) {
        let hErrors: any = [];
    
        args
          .filter(o => !!o)
          .forEach(({headers, errors}: IResponseError) => {
            if (headers) {
              response.set(headers);
            }
    
            if (errors) {
              hErrors = hErrors.concat(errors);
            }
          });
    
        if (hErrors.length) {
          response.set(this.headerName, JSON.stringify(hErrors));
        }
      }
    }
    
    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
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76