# Middlewares

is similar to the Express middleware with the difference that it's a class and you can use the IoC to inject other services on its constructor.

All middlewares decorated by have one method named use(). This method can use all parameters decorators as you could see with the Controllers and return a promise.

# Configuration

To begin, you must add the middlewares folder on the componentsScan attribute in your server settings as follow :

import {Configuration} from "@tsed/common";

const rootDir = __dirname;

@Configuration({
  rootDir,
  mount: {
    "/rest": `${rootDir}/controllers/**/**.ts`
  },
  componentsScan: [
    `${rootDir}/services/**/**.ts`,
    `${rootDir}/middlewares/**/**.ts`
  ]
})
export class Server {

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

Then, create a new file in your middlewares folder. Create a new Class definition then add the decorator.

import {Middleware, Req} from "@tsed/common";

@Middleware()
export class CustomMiddleware {
  use(@Req() req: Req) {
    console.log("ID", req.id);
  }
}
1
2
3
4
5
6
7
8

You have different usecases to declare and use a middleware as following:

  • Global Middleware, this middleware can be used on the Server,
  • Endpoint Middleware, this middleware can be used on a controller method,
  • Error middleware, this middleware can be used to handle errors.

Note

Global middleware and endpoint middleware are similar, except that the Endpoint middleware can retrieve endpoint information that is executed.

# Global middleware

Global middleware are generally used to handle requests before or after controllers. For example the GlobalAcceptMimesMiddleware is used to check the mime type set in the request headers and throw an error when the mime don't match with server configuration.

import {Constant, IMiddleware, Middleware, Req} from "@tsed/common";
import {NotAcceptable} from "@tsed/exceptions";

@Middleware()
export default class GlobalAcceptMimesMiddleware implements IMiddleware {
  @Constant("acceptMimes")
  acceptMimes: string[];

  use(@Req() request: Req) {
    this.acceptMimes
      .forEach((mime) => {
        if (!request.accepts(mime)) {
          throw new NotAcceptable(mime);
        }
      });
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

TIP

You can register your global middleware on server level:

import {Configuration, Inject, PlatformApplication} from "@tsed/common";
import {GlobalAcceptMimeMiddleware} from "./GlobalAcceptMimeMiddleware";

const rootDir = __dirname;

@Configuration({
  rootDir,
  componentsScan: [
    `${rootDir}/middlewares/**/**.js`
  ],
  acceptMimes: ["application/json"]  // add your custom configuration here
})
export class Server {
  @Inject()
  app: PlatformApplication;

  $beforeRoutesInits() {
    this.app.use(GlobalAcceptMimeMiddleware);
  }

  // or
  $afterRoutesInit() {
    this.app.use(GlobalAcceptMimeMiddleware); // But maybe is too late ;)
  }
}
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

# Endpoint middleware

Endpoint middleware is not really different from global middleware, but its goal is to handle a request before or after endpoint. It knows which endpoint is executed by using the decorator.

import {EndpointInfo, IMiddleware, Middleware, Req} from "@tsed/common";
import {NotAcceptable} from "@tsed/exceptions";

@Middleware()
export class AcceptMimesMiddleware implements IMiddleware {
  use(@Req() request: Req, @EndpointInfo() endpoint: EndpointInfo) {

    // get the parameters stored for the current endpoint or on the controller.
    const mimes = endpoint.get(AcceptMimesMiddleware) || [];

    mimes.forEach((mime: string) => {
      if (!request.accepts(mime)) {
        throw new NotAcceptable(mime);
      }
    });
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

TIP

Endpoint middleware must be used on class controller or endpoint method with the following decorators:

  • or routes decorators: , , , and
import {Controller, Get, UseBefore} from "@tsed/common";
import {AcceptMimesMiddleware} from "./AcceptMimesMiddleware";

@Controller("/test")
@UseBefore(AcceptMimesMiddleware) // global to the controller
class MyCtrl {
  @Get("/")
  @UseBefore(AcceptMimesMiddleware) // only to this endpoint
  getContent() {
  }
}
1
2
3
4
5
6
7
8
9
10
11

# call sequences

As you see in the previous section, a middleware can be used on different contexts:

A middleware added to a controller or endpoint level has the same constraint as the endpoint method itself. It'll be played only when the url request matches with the path associated to the controller and its endpoint method.

When a request is sent to the server all middlewares added in the Server, Controller or Endpoint with decorators will be called while a response isn't sent by one of the handlers/middlewares in the stack.

Note (1)

Render middleware is called only when the decorator is used on the endpoint.

Note (2)

SendResponse middleware send a response only when data is returned by the endpoint method or if the endpoint is the latest called endpoint for the resolved route.

TIP

The middlewares shown in the Endpoints box will be replayed as many times as it has endpoint that matches the request url.

WARNING

Only middlewares shown in the Endpoints box can use decorator to retrieve endpoint context execution.

For example:

import {Controller, Get, Next, Use, UseAfter, UseBefore, UseBeforeEach} from "@tsed/common";

@Controller("/")
@UseAfter(MdlwCtrlAfter)
@UseBefore(MdlwCtrlBefore)
@UseBeforeEach(MdlwCtrlBeforeEach)
@Use(MdlwCtrl)
export class MyCtrl {

  @Get("/")
  @UseBefore(MdlwBefore)
  @Use(Mdlw)
  @UseAfter(MdlwAfter)
  endpointA(@Next() next: Next) {
    console.log("EndpointA");
    next();
  }

  @Get("/")
  endpointB() {
    console.log("EndpointB");

    return {};
  }
}
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

According to the call sequence scheme, the stack calls will be there:

  • Middlewares added in Server (logger, express middleware, etc...),
  • MdlwCtrlBefore,
  • MdlwCtrlBeforeEach
  • MdlwBefore,
  • MdlwCtrl,
  • MyCtrl.endpointA,
  • MdlwAfter,
  • SendResponse, (but no data is returned by the endpointA)
  • MdlwCtrlBeforeEach
  • MdlwCtrl,
  • MyCtrl.endpointB,
  • MdlwAfter,
  • SendResponse, send a response because endpointB returns data,
  • MdlwCtrlAfter, but this middleware will not be called because a response is sent.
  • Middleware added in Server (not called too).

# Handle error

Express allows you to handle any error when your middleware have 4 parameters like this:

function (error, req, res, next){}
1

Ts.ED has the same mechanism with decorator. The following example is the GlobalErrorHandlerMiddleware used by Ts.ED to handle all errors thrown by your application.

import {Err, Middleware, Req, Res} from "@tsed/common";
import {Exception} from "@tsed/exceptions";
import {$log} from "@tsed/logger";

@Middleware()
export class GlobalErrorHandlerMiddleware {
  use(
    @Err() error: any,
    @Req() request: Req,
    @Res() response: Res
  ): any {

    if (response.headersSent) {
      throw error;
    }

    const toHTML = (message = "") => message.replace(/\n/gi, "<br />");

    if (error instanceof Exception) {
      $log.error("" + error);
      response.status(error.status).send(toHTML(error.message));

      return;
    }

    if (typeof error === "string") {
      response.status(404).send(toHTML(error));

      return;
    }

    $log.error("" + error);
    response.status(error.status || 500).send("Internal Error");

    return;
  }
}
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

# Specifics parameters decorators

In addition, you have this specifics parameters decorators for the middlewares:

Signature Example Description
useMethod(@Err() err: any) {} Inject the Express.Err service. (Decorator for middleware).
useMethod(@ResponseData() data: any) Provide the data returned by the previous middlewares.
useMethod(@EndpointInfo() endpoint: Endpoint) Provide the endpoint settings.

# Override existing middlewares

The decorator gives you the ability to override some internal Ts.ED middlewares.

import {OriginalMiddleware, OverrideProvider} from "@tsed/common";

@OverrideProvider(OriginalMiddleware)
export class CustomMiddleware extends OriginalMiddleware {
  public use() {
    // Do something
    return super.use();
  }
}
1
2
3
4
5
6
7
8
9

Here are some examples to override the middleware provided by Ts.ED:

TIP

Ts.ED have is own . Override this middleware is not necessary, since you can create you own middleware and register it, in your server before the original GlobalErrorHandlerMiddleware. For more details, see our Exceptions documentation page.

TIP

By default, the server imports automatically your middlewares matching with this rules ${rootDir}/middlewares/**/*.ts (See componentScan configuration).

.
├── src
│   ├── controllers
│   ├── services
│   ├── middlewares
│   └── Server.ts
└── package.json
1
2
3
4
5
6
7

If not, just import your middleware in your server or edit the componentScan configuration.

import {Configuration} from "@tsed/common";
import "./src/other/directory/CustomMiddleware";

@Configuration({
    ...
})
export class Server {
 
}
1
2
3
4
5
6
7
8
9

# Provided middlewares