# 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 his 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 promise.

# Configuration

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

import {ServerLoader, ServerSettings} from "@tsed/common";
import Path = require("path");

const rootDir = Path.resolve(__dirname);

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

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

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 ,
  • Endpoint Middleware, this middleware can be used on a controller method,
  • Error middleware, this middleware can be use to handle error.

Note

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

# Global middleware

Global middleware are generally used handle request 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 "ts-httpexceptions";

@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 {ServerLoader, ServerSettings} from "@tsed/common";
import {GlobalAcceptMimeMiddleware} from "./GlobalAcceptMimeMiddleware";

const rootDir = __dirname;

@ServerSettings({
  rootDir,
  componentsScan: [
    `${rootDir}/middlewares/**/**.js`
  ],
  acceptMimes: ["application/json"]  // add your custom configuration here
})
export class Server extends ServerLoader {
  $beforeRoutesInits() {
    this.use(GlobalAcceptMimeMiddleware);
  }

  // or
  $afterRoutesInit() {
    this.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

# Endpoint middleware

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

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

@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 use on different context:

Middleware associated to a controller or endpoint as a same constraint that an endpoint. It'll be played only when the url request match with the path associated to the controller and his endpoint method.

When a request is sent to the server all middlewares added in the , 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 a the decorator is used on the endpoint.

Note (2)

SendResponse middleware send a response only when a data is return 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 match 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 ServerLoader (logger, express middleware, etc...),
  • MdlwCtrlBefore,
  • MdlwCtrlBeforeEach
  • MdlwBefore,
  • MdlwCtrl,
  • MyCtrl.endpointA,
  • MdlwAfter,
  • SendResponse, (but nothing data is returned by the endpointA)
  • MdlwCtrlBeforeEach
  • MdlwCtrl,
  • MyCtrl.endpointB,
  • MdlwAfter,
  • SendResponse, send a response because endpointB return a data,
  • MdlwCtrlAfter, but this middleware will not be called because a response is sent.
  • Middleware added in ServerLoader (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 throw by your application.

import {Err, Middleware, Req, Res} from "@tsed/common";
import {Exception} from "ts-httpexceptions";
import {$log} from "ts-log-debug";

@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:

All middlewares provided by Ts.ED can be overridden. You can find the complete list here.

TIP

By default, the server import automatically you 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 {ServerLoader, ServerSettings} from "@tsed/common";
import "./src/other/directory/CustomMiddleware";

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

# Provided middlewares