Skip to content

NestJS Day 3: What are Providers, Modules, Middleware?

NestJS Day 3

Abstract

This is a concise, summarized approach to learn NestJS. For more in-depth knowledge about NestJS, visit the official NestJS documentation.

Github repo for day 3

HAR file

1. Providers

Providers are the fundamental concept in NestJS. They handle business logic and can be injected as dependencies.

Basic Service Provider

users.service.ts
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

users.controller.ts
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

database.provider.ts
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

users.module.ts
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

cats.module.ts
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

config.module.ts
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

database.module.ts
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

logger.middleware.ts
import { Request, Response, NextFunction } from "express";
// Simple function middleware
export 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

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) {
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

app.module.ts
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

rate-limit.middleware.ts
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

app.module.ts
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 via MiddlewareConsumer
  • Dependency Injection: NestJS automatically resolves and injects dependencies