ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [Nest.js] Life Cycle - 1 Middleware
    Nest.js 2021. 10. 1. 14:48

     

    1. What is Middleware?

    Nest.js documentation


    Middleware is a function which is called before the route handler.
    Middleware functions have access to the request and response objects, and the next() middleware function.Nest middleware are equivalent to Express middleware.Nest Middleware fully supports Dependency Injection.

     

    A middleware class implements NestMiddleware interface, and its abstract use method.

    We can access represent Request, Response objects very easily.

    Also, it has next function to finish this middleware and send a request steam to the next step, such as another middleware or guards.

    If we don’t call next method, it never move forward.

     

    2. What Middleware does ?

    1. execute any code
    2. make changes to the request and the response objects
    3. end the request-response cycle
    4. call the next middleware function in the stack
    5. if the current middleware function does not end the request-response cycle,
        it must call next() to pass control to the next middleware function
        Otherwise, the request will be left hanging.

    OMG, it execute any code? seriously? then, why do we need other components ?

    I feel like we only need middleware and controller to handle request-response ! Ha!

     

    3. Why is Nest.js Middleware different from other components?

    Middleware is called only before the route handler is called. 
    We have access to the response object, 
    but we don't have the result of the route handler.

    I think it is used for Security things!!!

    Registration
    In the module, very flexible way of choosing relevant routes (with wildcards, by method,...)
    In main.ts, Globally with app.use()

    Examples
    middleware that is out there. (link : http://expressjs.com/en/resources/middleware.html )
    We can use npm to install Third-party middleware
    There are lots of libraries, 
    e.g. cors, body-parser, cookie-parser ,morgan, helmet

    Conclusion

    The registration of middleware is very flexible, 
    for example: apply to all routes but one etc. 
    But since they are registered in the module, 
    we might not realize it applies to our controller when we're looking at its methods. 
    It's also great that we can make use of all the express middleware libraries that are out there.

    Middleware Implementation

    1. Middleware type

      1.1 Class-based middleware

    import { Injectable, NestMiddleware } from '@nestjs/common';
    import { NextFunction } from 'express';
    
    @Injectable() // 1
    export class LoggerMiddleware implements NestMiddleware { // 2
      use(req: Request, res: Response, next: NextFunction) {
        console.log('Middleware working here'); // 3
        console.log('req', req); // 3
        console.log('res', res); // 3
        next(); // 4
      }
    }

    // 1. @Injectable() - it is injected dependency by constructor
    // 2. implements NestMiddleware - class middleware have to implement "NestMiddleware" interface

    export interface NestMiddleware<TRequest = any, TResponse = any> {
        use(req: TRequest, res: TResponse, next: () => void): any;
    }


    // 3. will check results later
    // 4. next() - if we don't use next(), we CANNOT exit from this middleware and the request is left hanging.

     

      1.2 Functional middleware

    the above middleware class we've been using is quite simple. 
    It has no members, no additional methods, and no dependencies. 
    Why can't we just define it in a simple function instead of a class?  
    Let's transform the logger middleware from 1.1class-based into 1.2functional middleware
    import { Request, Response, NextFunction } from 'express';
    
    export function functionalLogger(req: Request, res: Response, next: NextFunction) { // 1
      console.log('Functional Middleware working here');
      console.log('req', req); 
      console.log('res', res); 
      
      next();
     };

    // 1. logger(req: Request, res: Response, next: NextFunction)
           - parameters are the same with the above #1.1 use(..) parameters.

     

    2 Middleware Binding

      2.1 Global middleware

    If we want to bind middleware to every registered route at once, 
    we can use the use() method that is supplied by the INestApplication instance

      2.1.1 Applying in main.ts

    import { Logger, ValidationPipe } from '@nestjs/common';
    import { NestFactory } from '@nestjs/core';
    import { AppModule } from './app.module';
    import * as config from 'config';
    import { hookLogger, winstonLogger } from './logger/winston.logger';
    
    async function bootstrap() {
      const logger = new Logger();
      const app = await NestFactory.create(AppModule);
    
      const serverConfig = config.get('server');
      const port = serverConfig.port;
    
      app.use(logger);
    
      // Global Pipe
      app.useGlobalPipes(
        ...
      );
    
      await app.listen(port);
    
      ...
    }
    bootstrap();

    // 1. app.use(middleware) 

     

      2.2 Module middleware

    we set them up using the configure() method of the module class. 
    Modules that include middleware have to implement the NestModule interface.
    Let's set up the LoggerMiddleware at the AppModule level.
    // app.module.ts
    
    import { MiddlewareConsumer, Module, NestModule, RequestMethod } from '@nestjs/common';
    import { BoardsModule } from './boards/boards.module';
    import { TypeOrmModule } from '@nestjs/typeorm';
    import { TypeORMConfig } from './configs/typeorm.config';
    import { AuthModule } from './auth/auth.module';
    import { LoggerMiddleware } from './middleware/logger.middleware';
    
    @Module({
      imports: [
        TypeOrmModule.forRoot(TypeORMConfig),
        BoardsModule,
        AuthModule
      ],
      controllers: [],
      providers: [],
    })
    export class AppModule implements NestModule {
      configure(consumer: MiddlewareConsumer) { 
        consumer 
          .apply(LoggerMiddleware)
          .exclude(
            { path: 'boards', method: RequestMethod.POST },
            { path: 'boards', method: RequestMethod.PUT },
            'boards/(.*)', 
          )
          .forRoutes('boards');
      }
    }

     

    2. 3 Middleware Interfaces

    What the hecks are NestModule, MiddlewareConsumer, and so on...
    for module middleware binding?
    Let's go much deeper side for it  !!!

       2.3.1 NestModule Interface

    import { MiddlewareConsumer } from '../middleware/middleware-consumer.interface';
    export interface NestModule {
        configure(consumer: MiddlewareConsumer): any;
    }

     2.3.2 MiddlewareConsumer

    The MiddlewareConsumer is a helper class. 
    It provides several built-in methods to manage middleware. 

    All of them can be simply chained in the fluent style.
    import { Type } from '../type.interface';
    import { MiddlewareConfigProxy } from './middleware-config-proxy.interface';
    
    export interface MiddlewareConsumer {
        apply(...middleware: (Type<any> | Function)[]): MiddlewareConfigProxy;
    }

      2.3.3 MiddlewareConfigProxy

    The exclude method can exclude routes from the currently processed middleware.

    The forRoutes() method can take 
    a single string, multiple strings, a RouteInfo object, a controller class and even multiple controller classes.
    In most cases we'll probably just pass a list of controllers separated by commas. 
    If you pass a class, Nest would attach middleware to every path defined within this controller.
    Also, the asterisk is used as a wildcard and will match any combination of charaters.
    e.g., forRoutes({ path: 'ab*cd', method: RequestMethod.ALL });
    import { Type } from '../type.interface';
    import { RouteInfo } from './middleware-configuration.interface';
    import { MiddlewareConsumer } from './middleware-consumer.interface';
    export interface MiddlewareConfigProxy {
        exclude(...routes: (string | RouteInfo)[]): MiddlewareConfigProxy;
        forRoutes(...routes: (string | Type<any> | RouteInfo)[]): MiddlewareConsumer;
    }

    Example - Helmet, Global Middleware

     

    1. Helmet

    Helmet can help protect your app from some well-known web vulnerabilities by setting HTTP headers appropriately.
    Generally, Helmet is just a collection of 15 smaller middleware functions
    that set security-related HTTP headers.

    Each middleware's name is listed below.
    1. helmet.contentSecurityPolicy(options) - default
    2. helmet.crossOriginEmbedderPolicy()
    3. helmet.crossOriginOpenerPolicy()
    4. helmet.crossOriginResourcePolicy()
    5. helmet.expectCt(options) - default
    6. helmet.referrerPolicy(options) -default
    7. helmet.hsts(options) - defulat
    8. helmet.noSniff() - default
    9. helmet.originAgentCluster() 
    10. helmet.dnsPrefetchControl(options) - default
    11. helmet.ieNoOpen() - default
    12. helmet.frameguard(options) - default
    13. helmet.permittedCrossDomainPolicies(options) - default
    14. helmet.hidePoweredBy() - default
    15. helmet.xssFilter() - default

    more details of the above middlewares - https://github.com/helmetjs/helmet#reference

     

    2. Helmet binding

    The top-level helmet() is a wrapper around 15 smaller middlewares, 11 of which are enabled by default.

    // This...
    app.use(helmet());
    
    // ...is equivalent to this:
    app.use(helmet.contentSecurityPolicy());
    app.use(helmet.dnsPrefetchControl());
    app.use(helmet.expectCt());
    app.use(helmet.frameguard());
    app.use(helmet.hidePoweredBy());
    app.use(helmet.hsts());
    app.use(helmet.ieNoOpen());
    app.use(helmet.noSniff());
    app.use(helmet.permittedCrossDomainPolicies());
    app.use(helmet.referrerPolicy());
    app.use(helmet.xssFilter());

     

    3. Implementation

    - Response header before helmet used

    // as we can see "X-Powered-By" in response header, it might provide the information of software what we use.
       Therefore, a hacker will use the information to attack the server.

     

    - module installation

    $ npm install helmet --save

     

    - global binding of a helmet middleware to hide "X-Powered-By" inforamation, helmet.hidePoweredBy()

    ...
    import * as helmet from 'helmet';
    
    async function bootstrap() {
      const logger = new Logger();
      const app = await NestFactory.create(AppModule);
    
      const serverConfig = config.get('server');
      const port = serverConfig.port;
    
      app.use(helmet.hidePoweredBy()); // 1
      
      ...
    
      await app.listen(port);
    
      ...
    }
    bootstrap();

    // 1. app.use(helmet.hidePoweredBy()) - setting up one of the middlewares in helmet to hide the information.

     

    - Response header after helmet.hidePoweredBy() used

    // SEEE!!!!??? There is no information, so our project is in safe !(Not really)

     

    - global binding of entire helmet in main.ts

    ...
    import * as helmet from 'helmet';
    
    async function bootstrap() {
      const logger = new Logger();
      const app = await NestFactory.create(AppModule);
    
      const serverConfig = config.get('server');
      const port = serverConfig.port;
    
      app.use(helmet()); // 1
      
      ...
    
      await app.listen(port);
    
      ...
    }
    bootstrap();

    // 1. app.use(helmet()) - setting up the 11 middlewares as defualt

     

    - Response header after helmet() used

    // Wow, There are some information related to security 

     

     

     

    'Nest.js' 카테고리의 다른 글

    [Nest.js] Life Cycle - 2 Guards  (0) 2021.10.07
    [Nest.js] Life Cycle - advanced approach  (0) 2021.09.29
    [Nest.js] simple project  (0) 2021.09.27
    [Nest.js] 16 - Log with Winston and WebHook  (0) 2021.09.25
    [Nest.js] 15 - Configuration  (0) 2021.09.22
Designed by Tistory.