Lesson 03-NestJS Modules and Middleware

Modules

Modules in NestJS v10 are the core mechanism for organizing and managing applications.

Feature Modules

Feature modules are organized around specific functionalities or business domains. For example, a user management module can include all user-related controllers and services.

Creating a Feature Module
Use the Nest CLI to create a feature module:

nest generate module users

This generates a UsersModule in the src/modules/users/users.module.ts file.

// src/modules/users/users.module.ts
import { Module } from '@nestjs/common';
import { UsersController } from './users.controller';
import { UsersService } from './users.service';

@Module({
  controllers: [UsersController],
  providers: [UsersService],
})
export class UsersModule {}

Shared Modules

Shared modules contain services and other providers that can be reused across multiple feature modules.

Creating a Shared Module
Use the Nest CLI to create a shared module:

nest generate module common

This generates a CommonModule in the src/modules/common/common.module.ts file.

// src/modules/common/common.module.ts
import { Module } from '@nestjs/common';
import { CommonService } from './common.service';

@Module({
  providers: [CommonService],
  exports: [CommonService],
})
export class CommonModule {}

Module Re-exporting

To allow other modules to access providers from a specific module, use the exports property.

// src/modules/common/common.module.ts
import { Module } from '@nestjs/common';
import { CommonService } from './common.service';

@Module({
  providers: [CommonService],
  exports: [CommonService],
})
export class CommonModule {}

// src/modules/users/users.module.ts
import { Module } from '@nestjs/common';
import { UsersModule } from './users/users.module';
import { CommonModule } from './common/common.module';
import { UsersController } from './users/users.controller';
import { UsersService } from './users/users.service';

@Module({
  imports: [CommonModule], // Import shared module
  controllers: [UsersController],
  providers: [UsersService],
})
export class UsersModule {}

Dependency Injection

Modules enable dependency injection by importing other modules.

// src/modules/users/users.module.ts
import { Module } from '@nestjs/common';
import { UsersModule } from './users/users.module';
import { CommonModule } from './common/common.module';
import { UsersController } from './users/users.controller';
import { UsersService } from './users/users.service';

@Module({
  imports: [CommonModule], // Import shared module
  controllers: [UsersController],
  providers: [UsersService],
})
export class UsersModule {}

Global Modules

Global modules do not need to be imported into every module that uses them; they can be set as global.

// src/modules/common/common.module.ts
import { Module } from '@nestjs/common';
import { CommonService } from './common.service';

@Module({
  providers: [CommonService],
  exports: [CommonService],
  global: true, // Set as global
})
export class CommonModule {}

// src/app.module.ts
import { Module } from '@nestjs/common';
import { UsersModule } from './modules/users/users.module';
import { CommonModule } from './modules/common/common.module';

@Module({
  imports: [CommonModule, UsersModule], // No need to import CommonModule again
})
export class AppModule {}

Dynamic Modules

Dynamic modules allow modules to be created dynamically at runtime, which is particularly useful for configuration modules.

Creating a Dynamic Module

// src/modules/config/config.module.ts
import { Module, Global } from '@nestjs/common';
import { ConfigService } from './config.service';

@Global()
@Module({
  providers: [ConfigService],
  exports: [ConfigService],
})
export class ConfigModule {
  static register(options: any): DynamicModule {
    return {
      module: ConfigModule,
      providers: [
        {
          provide: ConfigService,
          useValue: options,
        },
      ],
      exports: [ConfigService],
    };
  }
}

Using a Dynamic Module

// src/app.module.ts
import { Module } from '@nestjs/common';
import { ConfigModule } from './modules/config/config.module';
import { UsersModule } from './modules/users/users.module';

@Module({
  imports: [
    ConfigModule.register({ environment: process.env.NODE_ENV }), // Dynamic registration
    UsersModule,
  ],
})
export class AppModule {}

Shared Module

// src/modules/common/common.module.ts
import { Module } from '@nestjs/common';
import { CommonService } from './common.service';

@Module({
  providers: [CommonService],
  exports: [CommonService],
})
export class CommonModule {}

Feature Module

// src/modules/users/users.module.ts
import { Module } from '@nestjs/common';
import { UsersController } from './users.controller';
import { UsersService } from './users.service';
import { CommonModule } from '../common/common.module';

@Module({
  imports: [CommonModule],
  controllers: [UsersController],
  providers: [UsersService],
})
export class UsersModule {}

Main Module

// src/app.module.ts
import { Module } from '@nestjs/common';
import { UsersModule } from './modules/users/users.module';
import { CommonModule } from './modules/common/common.module';

@Module({
  imports: [CommonModule, UsersModule],
})
export class AppModule {}

Middleware

Middleware in NestJS v10 is used to handle specific stages of the HTTP request lifecycle.

Dependency Injection

Middleware can inject dependencies via the constructor, and these dependencies must be part of a module.

// src/middlewares/logging.middleware.ts
import { Injectable, NestMiddleware } from '@nestjs/common';
import { Request, Response, NextFunction } from 'express';

@Injectable()
export class LoggingMiddleware implements NestMiddleware {
  constructor(private readonly appService: AppService) {}

  use(req: Request, res: Response, next: NextFunction) {
    console.log(`Request... URL: ${req.url}`);
    next();
  }
}

// src/app.service.ts
import { Injectable } from '@nestjs/common';

@Injectable()
export class AppService {
  getHello(): string {
    return 'Hello World!';
  }
}

// src/app.module.ts
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { LoggingMiddleware } from './middlewares/logging.middleware';

@Module({
  imports: [],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule {
  configure(consumer: MiddlewareConsumer) {
    consumer.apply(LoggingMiddleware).forRoutes('*');
  }
}

Applying Middleware

Middleware must be applied in the configure method of a module.

// src/app.module.ts
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { LoggingMiddleware } from './middlewares/logging.middleware';

@Module({
  imports: [],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule {
  configure(consumer: MiddlewareConsumer) {
    consumer.apply(LoggingMiddleware).forRoutes('*');
  }
}

Route Wildcards

Wildcards like * can be used to specify a group of routes.

// src/app.module.ts
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { LoggingMiddleware } from './middlewares/logging.middleware';

@Module({
  imports: [],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule {
  configure(consumer: MiddlewareConsumer) {
    consumer.apply(LoggingMiddleware).forRoutes('api/*'); // Applies to all routes starting with api
  }
}

Middleware Consumer

The middleware consumer specifies which routes the middleware applies to.

// src/app.module.ts
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { LoggingMiddleware } from './middlewares/logging.middleware';

@Module({
  imports: [],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule {
  configure(consumer: MiddlewareConsumer) {
    consumer.apply(LoggingMiddleware).forRoutes('api/*'); // Applies to all routes starting with api
  }
}

Excluding Routes

Use the exclude method to exclude specific routes.

// src/app.module.ts
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { LoggingMiddleware } from './middlewares/logging.middleware';

@Module({
  imports: [],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule {
  configure(consumer: MiddlewareConsumer) {
    consumer
      .apply(LoggingMiddleware)
      .forRoutes('api/*')
      .exclude('api/exclude-me') // Exclude this route
      .forRoutes('api/another-route');
  }
}

Functional Middleware

You can create specialized functional middleware for specific tasks, such as authentication.

// src/middlewares/auth.middleware.ts
import { Injectable, NestMiddleware } from '@nestjs/common';
import { Request, Response, NextFunction } from 'express';

@Injectable()
export class AuthMiddleware implements NestMiddleware {
  use(req: Request, res: Response, next: NextFunction) {
    if (!req.headers.authorization) {
      res.status(401).send('Unauthorized');
    } else {
      next();
    }
  }
}

Multiple Middleware

Multiple middleware can be applied to a single route.

// src/app.module.ts
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { LoggingMiddleware } from './middlewares/logging.middleware';
import { AuthMiddleware } from './middlewares/auth.middleware';

@Module({
  imports: [],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule {
  configure(consumer: MiddlewareConsumer) {
    consumer.apply(LoggingMiddleware, AuthMiddleware).forRoutes('api/protected-route');
  }
}

Global Middleware

Global middleware can be applied to the entire application.

// src/main.ts
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { LoggingMiddleware } from './middlewares/logging.middleware';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  app.useGlobalMiddlewares(new LoggingMiddleware());
  await app.listen(3000);
}
bootstrap();
Share your love