NestJS Day 3: What are Providers, Modules, Middleware?
Abstract
This is a concise, summarized approach to learn NestJS. For more in-depth knowledge about NestJS, visit the official NestJS documentation.
1. Providers
Providers are the fundamental concept in NestJS. They handle business logic and can be injected as dependencies.
Basic Service Provider
import { Injectable } from "@nestjs/common";
// @Injectable() decorator makes this class a provider@Injectable()export class UsersService { private users = [ { id: 1, name: "John Doe", email: "john@example.com" }, { id: 2, name: "Jane Smith", email: "jane@example.com" }, ];
// Get all users findAll() { return this.users; }
// Find user by ID findById(id: number) { return this.users.find((user) => user.id === id); }
// Create new user create(userData: { name: string; email: string }) { const newUser = { id: this.users.length + 1, ...userData, }; this.users.push(newUser); return newUser; }}
Using Providers in Controllers
import { Controller, Get, Post, Body, Param } from "@nestjs/common";import { UsersService } from "./users.service";
@Controller("users")export class UsersController { // Dependency injection - NestJS automatically provides the service constructor(private readonly usersService: UsersService) {}
@Get() getAllUsers() { return this.usersService.findAll(); }
@Get(":id") getUser(@Param("id") id: string) { return this.usersService.findById(+id); }
@Post() createUser(@Body() userData: { name: string; email: string }) { return this.usersService.create(userData); }}
Custom Providers
export const DatabaseProvider = { provide: "DATABASE_CONNECTION", // Token for injection useFactory: () => { // Mock database connection return { host: "localhost", port: 5432, database: "nestjs_app", connect: () => console.log("Database connected"), }; },};
// Using custom provider@Injectable()export class UsersService { constructor( @Inject("DATABASE_CONNECTION") private db: any // Inject using token ) {}
async findAll() { // Use database connection await this.db.connect(); return this.users; }}
2. Modules
Modules organize application structure and manage dependencies between providers.
Basic Module
import { Module } from "@nestjs/common";import { UsersController } from "./users.controller";import { UsersService } from "./users.service";
@Module({ controllers: [UsersController], // Controllers in this module providers: [UsersService], // Providers available in this module exports: [UsersService], // Make service available to other modules})export class UsersModule {}
Feature Module with Imports
import { Module } from "@nestjs/common";import { UsersModule } from "../users/users.module";import { CatsController } from "./cats.controller";import { CatsService } from "./cats.service";
@Module({ imports: [UsersModule], // Import other modules controllers: [CatsController], providers: [CatsService], exports: [CatsService],})export class CatsModule {}
// cats.service.ts@Injectable()export class CatsService { constructor( private usersService: UsersService // Can use exported service from UsersModule ) {}
createCat(userId: number, catData: any) { const user = this.usersService.findById(userId); // Create cat logic here return { ...catData, owner: user }; }}
Global Module
import { Global, Module } from "@nestjs/common";import { ConfigService } from "./config.service";
// @Global() makes this module available everywhere without importing@Global()@Module({ providers: [ConfigService], exports: [ConfigService],})export class ConfigModule {}
Dynamic Module
import { DynamicModule, Module } from "@nestjs/common";
@Module({})export class DatabaseModule { // Factory method to create dynamic module static forRoot(options: { host: string; port: number }): DynamicModule { return { module: DatabaseModule, providers: [ { provide: "DB_OPTIONS", useValue: options, }, { provide: "DB_CONNECTION", useFactory: (dbOptions) => { // Create connection using options return createConnection(dbOptions); }, inject: ["DB_OPTIONS"], }, ], exports: ["DB_CONNECTION"], }; }}
// Usage in app.module.ts@Module({ imports: [DatabaseModule.forRoot({ host: "localhost", port: 5432 })],})export class AppModule {}
3. Middleware
Middleware functions execute before route handlers and can modify request/response objects.
Function Middleware
import { Request, Response, NextFunction } from "express";
// Simple function middlewareexport function logger(req: Request, res: Response, next: NextFunction) { console.log(`${new Date().toISOString()} - ${req.method} ${req.url}`); next(); // Call next() to pass control to next middleware}
Class Middleware
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) { const token = req.headers.authorization;
if (!token) { return res.status(401).json({ message: "No token provided" }); }
// Validate token (simplified) if (token === "Bearer valid-token") { req["user"] = { id: 1, name: "John Doe" }; // Add user to request next(); } else { return res.status(401).json({ message: "Invalid token" }); } }}
Applying Middleware
import { Module, NestModule, MiddlewareConsumer } from "@nestjs/common";import { logger } from "./middleware/logger.middleware";import { AuthMiddleware } from "./middleware/auth.middleware";
@Module({ imports: [UsersModule, CatsModule],})export class AppModule implements NestModule { configure(consumer: MiddlewareConsumer) { // Apply logger to all routes consumer.apply(logger).forRoutes("*");
// Apply auth middleware to specific routes consumer .apply(AuthMiddleware) .forRoutes( { path: "users", method: RequestMethod.POST }, { path: "cats", method: RequestMethod.ALL } );
// Exclude specific routes consumer .apply(AuthMiddleware) .exclude({ path: "users", method: RequestMethod.GET }) .forRoutes("users"); }}
Middleware with Dependencies
import { Injectable, NestMiddleware } from "@nestjs/common";import { ConfigService } from "./config.service";
@Injectable()export class RateLimitMiddleware implements NestMiddleware { private requests = new Map();
constructor(private configService: ConfigService) {} // Inject dependencies
use(req: Request, res: Response, next: NextFunction) { const ip = req.ip; const limit = this.configService.get("RATE_LIMIT", 100);
const currentRequests = this.requests.get(ip) || 0;
if (currentRequests >= limit) { return res.status(429).json({ message: "Too many requests" }); }
this.requests.set(ip, currentRequests + 1);
// Reset after 1 minute setTimeout(() => { this.requests.delete(ip); }, 60000);
next(); }}
Complete Example: Putting It All Together
import { Module, NestModule, MiddlewareConsumer } from "@nestjs/common";import { UsersModule } from "./users/users.module";import { CatsModule } from "./cats/cats.module";import { ConfigModule } from "./config/config.module";import { DatabaseModule } from "./database/database.module";import { logger } from "./middleware/logger.middleware";import { AuthMiddleware } from "./middleware/auth.middleware";
@Module({ imports: [ ConfigModule, // Global configuration DatabaseModule.forRoot({ // Dynamic database module host: "localhost", port: 5432, }), UsersModule, // Feature modules CatsModule, ],})export class AppModule implements NestModule { configure(consumer: MiddlewareConsumer) { consumer .apply(logger) // Log all requests .forRoutes("*");
consumer .apply(AuthMiddleware) // Protect specific routes .forRoutes("cats"); }}
Key Takeaways
- Providers: Use
@Injectable()
for services, inject dependencies via constructor - Modules: Organize code with
@Module()
, use imports/exports for sharing - Middleware: Implement
NestMiddleware
interface, apply viaMiddlewareConsumer
- Dependency Injection: NestJS automatically resolves and injects dependencies