Middlewares

Middleware is a function, which is called before route handler. Middleware functions have access to request and response objects, so they can modify them. They can also be something like a barrier - if middleware function does not call next(), the request will never be handled by route handler.

The simplest example:

import { Middleware, NestMiddleware } from '@nestjs/common';

@Middleware()
export class LoggingMiddleware implements NestMiddleware {
    resolve(): (req, res, next) => void {
        return (req, res, next) => {
            console.log('Request...');
            next();
        }
    }
}

Let's build a dummy authorization middleware (for explanation purposes - just by username). We will use X-Access-Token HTTP header to provide username (weird idea, but it doesn't matter here).

import { HttpException } from '@nestjs/core';
import { Middleware, NestMiddleware } from '@nestjs/common';
import { UsersService } from './users.service';

@Middleware()
export class AuthMiddleware implements NestMiddleware {
    constructor(private usersService: UsersService) {}

    resolve(): (req, res, next) => void {
        return async (req, res, next) => {
            const userName = req.headers["x-access-token"];
            const users = await this.usersService.getAllUsers();

            const user = users.find((user) => user.name === userName);
            if (!user) {
                throw new HttpException('User not found.', 401);
            }
            req.user = user;
            next();
        }
    }
}

Some facts about middlewares:

  • you should use @Middleware() annotation to tell Nest, that this class is a middleware,
  • you can use NestMiddleware interface, which forces on you to implement resolve() method,
  • middlewares (same as components) can inject dependencies through their constructor (dependencies have to be a part of module),
  • middlewares must have resolve() method, which must return another function (higher order function). Why? Because there is a lot of third-party, ready to use Express Middlewares (and more), which you could simply - thanks to this solution - use in Nest.

Okey, we already have prepared middleware, but we are not using it anywhere. Let's set it up:

import { Module, MiddlewaresConsumer } from '@nestjs/common';

@Module({
    controllers: [ UsersController ],
    components: [ UsersService ],
    exports: [ UsersService ],
})
export class UsersModule {
    configure(consumer: MiddlewaresConsumer) {
        consumer.apply(AuthMiddleware).forRoutes(UsersController);
    }
}

As shown, Modules can have additional method - configure(). This method receives as a parameter MiddlewaresConsumer, an object, which helps us to configure middlewares.

This object has apply() method, which receives infinite count of middlewares (method uses spread operator, so it is possible to pass multiple classes separated by comma). apply() method returns object, which has two methods:

  • forRoutes() - we use this method to pass infinite count of Controllers or objects (with path and method properties) separated by comma,
  • with() - we use this method to pass custom arguments to resolve() method of middleware.

How does it works?

When you pass UsersController in forRoutes method, Nest will setup middleware for each route in controller:

GET: users
GET: users/:id 
POST: users

But it is also possible to directly define for which path middleware should be used, just like that:

consumer.apply(AuthMiddleware).forRoutes({ 
    path: '*', method: RequestMethod.ALL 
});

Pass arguments to middleware

Sometimes the behaviour of the middleware depends on custom values - e.g. array of users roles. We can pass additional arguments to middleware using with() method.

Example:

const roles = ['admin', 'user'];
const options = {};
consumer.apply(AuthMiddleware)
    .with(roles, options)
    .forRoutes(UsersController);
@Middleware()
export class AuthMiddleware implements NestMiddleware 
    resolve(roles: string[], options: object) {
        return async (req, res, next) => {
            console.log(roles); // ['admin', 'user'] in this case
            next();
        }
    }
}

Chaining

You can simply chain apply() calls:

consumer.apply(AuthMiddleware, PassportMidleware)
       .forRoutes(UsersController, OrdersController, ClientController);
       .apply(...)
       .forRoutes(...);

Ordering

Middlewares are called in the same order as they are placed in array. Middlewares configured in sub-module are called after the parent module configuration.

results matching ""

    No results matching ""