Close Menu
    DevStackTipsDevStackTips
    • Home
    • News & Updates
      1. Tech & Work
      2. View All

      From Data To Decisions: UX Strategies For Real-Time Dashboards

      September 13, 2025

      Honeycomb launches AI observability suite for developers

      September 13, 2025

      Low-Code vs No-Code Platforms for Node.js: What CTOs Must Know Before Investing

      September 12, 2025

      ServiceNow unveils Zurich AI platform

      September 12, 2025

      Building personal apps with open source and AI

      September 12, 2025

      What Can We Actually Do With corner-shape?

      September 12, 2025

      Craft, Clarity, and Care: The Story and Work of Mengchu Yao

      September 12, 2025

      Distribution Release: Q4OS 6.1

      September 12, 2025
    • Development
      1. Algorithms & Data Structures
      2. Artificial Intelligence
      3. Back-End Development
      4. Databases
      5. Front-End Development
      6. Libraries & Frameworks
      7. Machine Learning
      8. Security
      9. Software Engineering
      10. Tools & IDEs
      11. Web Design
      12. Web Development
      13. Web Security
      14. Programming Languages
        • PHP
        • JavaScript
      Featured

      Optimizely Mission Control – Part III

      September 14, 2025
      Recent

      Optimizely Mission Control – Part III

      September 14, 2025

      Learning from PHP Log to File Example

      September 13, 2025

      Online EMI Calculator using PHP – Calculate Loan EMI, Interest, and Amortization Schedule

      September 13, 2025
    • Operating Systems
      1. Windows
      2. Linux
      3. macOS
      Featured

      sudo vs sudo-rs: What You Need to Know About the Rust Takeover of Classic Sudo Command

      September 14, 2025
      Recent

      sudo vs sudo-rs: What You Need to Know About the Rust Takeover of Classic Sudo Command

      September 14, 2025

      Dmitry — The Deep Magic

      September 13, 2025

      Right way to record and share our Terminal sessions

      September 13, 2025
    • Learning Resources
      • Books
      • Cheatsheets
      • Tutorials & Guides
    Home»Development»The NestJS Handbook – Learn to Use Nest with Code Examples

    The NestJS Handbook – Learn to Use Nest with Code Examples

    June 13, 2025

    NestJS is a progressive Node.js framework for building efficient, reliable, and scalable server-side applications. Combining the best ideas from OOP (Object-Oriented Programming), FP (Functional Programming), and FRP (Functional Reactive Programming), it gives you a fully-architected, batteries-included platform on top of Express (or Fastify).

    If you’re coming from Angular, you’ll feel right at home with its module/controller/service structure and powerful dependency-injection system.

    In this article we’ll cover both theory – why NestJS exists, how it’s structured, and when to reach for it –and practice, with bite-sized code snippets demonstrating how to bootstrap a project, define routes, inject dependencies, and more. Let’s start by understanding what NestJS is and where it came from.

    Table of Contents

    1. What is NestJS?

      • 1.1 History and Philosophy
    2. Why Choose NestJS?

      • 2.1 Benefits and Use Cases

      • 2.2 Comparison with Other Frameworks

    3. Getting Started

      • 3.1 Installing the CLI

      • 3.2 Creating Your First Project

      • 3.3 Project Structure Overview

    4. Core NestJS Building Blocks

      • 4.1 Modules

      • 4.2 Controllers

      • 4.3 Providers (Services)

    5. Dependency Injection

      • 5.1 How DI Works in NestJS

      • 5.2 Custom Providers and Factory Providers

    6. Routing & Middleware

      • 6.1 Defining Routes

      • 6.2 Applying Middleware

    7. Request Lifecycle & Pipes

      • 7.1 What Are Pipes?

      • 7.2 Built-In vs. Custom Pipes

    8. Guards & Authorization

      • 8.1 Implementing Guards

      • 8.2 Role-Based Access Control

    9. Exception Filters

      • 9.1 Handling Errors Gracefully

      • 9.2 Creating Custom Filters

    10. Interceptors & Logging

      • 10.1 Transforming Responses

      • 10.2 Logging and Performance Metrics

    11. Database Integration

      • 11.1 TypeORM with NestJS

      • 11.2 Mongoose (MongoDB)

      • 11.3 Prisma

    12. Configuration Management

      • 12.1 @nestjs/config Module

      • 12.2 Environment Variables

    13. Authentication

      • 13.1 JWT Strategy

      • 13.2 OAuth2 / Social Login

    14. Conclusion & Further Resources

      • Summary

      • Official Docs and Community Links

    1. What is NestJS?

    NestJS is a framework for building server-side applications in Node.js. It’s written in TypeScript (but supports plain JavaScript as well). At its core, it:

    • Wraps a mature HTTP server library (Express or Fastify)

    • Standardizes application architecture around modules, controllers, and providers

    • Leverages TypeScript’s type system for compile-time safety and clear APIs

    • Offers built-in support for things like validation, configuration, and testing

    Rather than stitching together middleware by hand, NestJS encourages a declarative, layered approach. You define modules to group related functionality, controllers to handle incoming requests, and providers (often called “services”) for your business logic. Behind the scenes, NestJS resolves dependencies via an IoC container, so you can focus on writing clean, reusable classes.

    To start up a project, run the following commands:

    <span class="hljs-comment"># Install the Nest CLI globally</span>
    npm install -g @nestjs/cli
    
    <span class="hljs-comment"># Create a new project called 'my-app'</span>
    nest new my-app
    
    <span class="hljs-built_in">cd</span> my-app
    npm run start:dev
    

    Once it’s running, you have a ready-to-go HTTP server with hot reloading, strict typing, and a sensible folder layout.

    1.1 History and Philosophy

    NestJS first appeared in 2017, created by Kamil Myśliwiec. Its goal was to bring the architectural patterns of Angular to the backend world, providing:

    1. Consistency: A single, opinionated way to structure applications.

    2. Scalability: Clear boundaries (modules) make it easier to grow teams and codebases.

    3. Testability: Built-in support for Jest and clear separation of concerns.

    4. Extensibility: A pluggable module system makes it easy to integrate ORMs, WebSockets, GraphQL, microservices, and more.

    Under the hood, NestJS embraces these principles:

    • Modularity: Everything lives in a module (AppModule, UsersModule, and so on), which can import other modules or export providers.

    • Dependency Injection: Services can be injected into controllers (and even into other services), which fosters loose coupling.

    • Decorators and Metadata: With TypeScript decorators (@Module(), @Controller(), @Injectable()), NestJS reads metadata at runtime to wire everything together.

    Here’s a tiny example showing the interplay of these pieces:

    <span class="hljs-comment">// users.service.ts</span>
    <span class="hljs-keyword">import</span> { Injectable } <span class="hljs-keyword">from</span> <span class="hljs-string">'@nestjs/common'</span>;
    
    <span class="hljs-meta">@Injectable</span>()
    <span class="hljs-keyword">export</span> <span class="hljs-keyword">class</span> UsersService {
      <span class="hljs-keyword">private</span> users = [{ id: <span class="hljs-number">1</span>, name: <span class="hljs-string">'Alice'</span> }];
      findAll() {
        <span class="hljs-keyword">return</span> <span class="hljs-built_in">this</span>.users;
      }
    }
    
    <span class="hljs-comment">// users.controller.ts</span>
    <span class="hljs-keyword">import</span> { Controller, Get } <span class="hljs-keyword">from</span> <span class="hljs-string">'@nestjs/common'</span>;
    <span class="hljs-keyword">import</span> { UsersService } <span class="hljs-keyword">from</span> <span class="hljs-string">'./users.service'</span>;
    
    <span class="hljs-meta">@Controller</span>(<span class="hljs-string">'users'</span>)
    <span class="hljs-keyword">export</span> <span class="hljs-keyword">class</span> UsersController {
      <span class="hljs-keyword">constructor</span>(<span class="hljs-params"><span class="hljs-keyword">private</span> <span class="hljs-keyword">readonly</span> usersService: UsersService</span>) {}
    
      <span class="hljs-meta">@Get</span>()
      getUsers() {
        <span class="hljs-keyword">return</span> <span class="hljs-built_in">this</span>.usersService.findAll();
      }
    }
    
    <span class="hljs-comment">// users.module.ts</span>
    <span class="hljs-keyword">import</span> { Module } <span class="hljs-keyword">from</span> <span class="hljs-string">'@nestjs/common'</span>;
    <span class="hljs-keyword">import</span> { UsersController } <span class="hljs-keyword">from</span> <span class="hljs-string">'./users.controller'</span>;
    <span class="hljs-keyword">import</span> { UsersService } <span class="hljs-keyword">from</span> <span class="hljs-string">'./users.service'</span>;
    
    <span class="hljs-meta">@Module</span>({
      controllers: [UsersController],
      providers: [UsersService],
    })
    <span class="hljs-keyword">export</span> <span class="hljs-keyword">class</span> UsersModule {}
    
    • The @Module decorator groups controller + service

    • The controller injects the service via its constructor

    • A simple GET /users route returns an array of user objects

    With that foundation laid, in the next section we’ll explore why you’d choose NestJS, comparing it to other popular Node frameworks and outlining common real-world use cases.

    2. Why Choose NestJS?

    NestJS isn’t just another Node.js framework – it brings a structured, enterprise-grade approach to building backend services. In this section we’ll cover benefits and real-world use cases, then compare NestJS to other popular Node frameworks so you can see where it fits best.

    2.1 Benefits and Use Cases

    1. Strong architectural patterns

      • Modularity: You break your app into focused modules (AuthModule, ProductsModule, and so on), each responsible for a slice of functionality.

      • Separation of concerns: Controllers handle HTTP, services encapsulate business logic, modules wire everything up.

      • Scalability: Growing teams map naturally onto modules—new features rarely touch existing code.

    2. Built-in dependency injection (DI)

      • DI makes testing and swapping implementations trivial.

      • You can easily mock a service in a unit test:

        <span class="hljs-comment">// products.controller.spec.ts</span>
        <span class="hljs-keyword">import</span> { Test, TestingModule } <span class="hljs-keyword">from</span> <span class="hljs-string">'@nestjs/testing'</span>;
        <span class="hljs-keyword">import</span> { ProductsController } <span class="hljs-keyword">from</span> <span class="hljs-string">'./products.controller'</span>;
        <span class="hljs-keyword">import</span> { ProductsService } <span class="hljs-keyword">from</span> <span class="hljs-string">'./products.service'</span>;
    
        describe(<span class="hljs-string">'ProductsController'</span>, <span class="hljs-function">() =></span> {
          <span class="hljs-keyword">let</span> controller: ProductsController;
          <span class="hljs-keyword">const</span> mockService = { findAll: <span class="hljs-function">() =></span> [<span class="hljs-string">'apple'</span>, <span class="hljs-string">'banana'</span>] };
    
          beforeEach(<span class="hljs-keyword">async</span> () => {
            <span class="hljs-keyword">const</span> <span class="hljs-keyword">module</span>: TestingModule = await Test.createTestingModule({
              controllers: [ProductsController],
              providers: [
                { provide: ProductsService, useValue: mockService },
              ],
            }).compile();
    
            controller = <span class="hljs-built_in">module</span>.get<ProductsController>(ProductsController);
          });
    
          it(<span class="hljs-string">'returns a list of products'</span>, <span class="hljs-function">() =></span> {
            expect(controller.getAll()).toEqual([<span class="hljs-string">'apple'</span>, <span class="hljs-string">'banana'</span>]);
          });
        });
    
    1. TypeScript-first

      • Full type safety at compile time.

      • Leverage interfaces and decorators (@Body(), @Param()) to validate and transform data.

    2. Rich ecosystem and extensibility

      • Official integrations for WebSockets, GraphQL, microservices (RabbitMQ, Kafka), and more.

      • Hundreds of community modules (for example @nestjs/swagger for OpenAPI docs).

    3. Production-grade tooling

      • CLI generates boilerplate (nest g module, nest g service).

      • Support for hot-reload in development (npm run start:dev).

      • Built-in testing setup with Jest.

    Real-World Use Cases:

    • Enterprise APIs with strict module boundaries and RBAC.

    • Microservices architectures, where each service is a self-contained NestJS app.

    • Real-time applications (chat, live dashboards) using Nest’s WebSocket gateways.

    • GraphQL backends with code-first schemas.

    • Event-driven systems connecting to message brokers.

    2.2 Comparison with Other Frameworks

    FeatureExpressKoaNestJS
    ArchitectureMinimal, unopinionatedMinimal, middleware-basedOpinionated modules/controllers/services
    Dependency InjectionManual wiringManual wiringBuilt-in, reflect-metadata
    TypeScript SupportVia DefinitelyTypedVia DefinitelyTypedFirst-class, decorators
    CLI ToolingNone (3rd-party)None@nestjs/cli generates code
    TestingUser-configuredUser-configuredJest + DI makes mocking easy
    EcosystemMiddleware libraryMiddleware libraryOfficial microservices, GraphQL, Swagger modules
    Learning CurveLowLowMedium (learning Nest idioms)
    • Express is great if you want minimal layers and full control, but you’ll end up hand-rolling a lot (DI, validation, folder structure).

    • Koa offers a more modern middleware approach, but still leaves architecture decisions to you.

    • NestJS provides the full stack: structure, DI, validation, testing, and official integrations, which is ideal if you value consistency, type safety, and out-of-the-box best practices.

    When to choose NestJS:

    NextJS is great for various use cases. It’s particularly effective if you’re building a large-scale API or microservice suite, if you want a solid architecture from day one, and if you prefer TypeScript and DI to keep code testable and maintainable.

    With these advantages in mind, you’ll find that NestJS can dramatically speed up development, especially on projects that need robust structure and clear boundaries.

    In the next section, we’ll dive into getting started: installing the CLI, creating a project, and exploring the generated folder layout.

    3. Getting Started

    Let’s jump into the basics: installing the CLI, scaffolding a new project, and exploring the default folder layout.

    3.1 Installing the CLI

    Nest ships with an official command-line tool that helps you generate modules, controllers, services, and more. Under the hood it uses Yeoman templates to keep everything consistent.

    <span class="hljs-comment"># Install the CLI globally (requires npm ≥ 6)</span>
    npm install -g @nestjs/cli
    

    Once installed, you can run nest --help to see available commands:

    nest --<span class="hljs-built_in">help</span>
    Usage: nest <<span class="hljs-built_in">command</span>> [options]
    
    Commands:
      new <name>       Scaffold a new project
      generate|g <schematic> [options]  Generate artifacts (modules, controllers, ...)
      build            Build project with webpack
      ...
    
    Options:
      -v, --version    Show version number
      -h, --<span class="hljs-built_in">help</span>       Show <span class="hljs-built_in">help</span>
    

    3.2 Creating Your First Project

    Scaffolding a new app is a single command. The CLI will ask whether to use npm or yarn, and whether to enable strict TypeScript settings.

    <span class="hljs-comment"># Create a new Nest app in the "my-nest-app" folder</span>
    nest new my-nest-app
    

    After answering the prompts, you’ll have:

    <span class="hljs-built_in">cd</span> my-nest-app
    npm run start:dev
    

    This launches a development server on http://localhost:3000 with automatic reload on file changes.

    3.3 Project Structure Overview

    By default, you’ll see something like:

    my-nest-app/
    ├── src/
    │   ├── app.controller.ts      <span class="hljs-comment"># example controller</span>
    │   ├── app.controller.spec.ts <span class="hljs-comment"># unit test for controller</span>
    │   ├── app.module.ts          <span class="hljs-comment"># root application module</span>
    │   ├── app.service.ts         <span class="hljs-comment"># example provider</span>
    │   └── main.ts                <span class="hljs-comment"># entry point (bootstraps Nest)</span>
    ├── <span class="hljs-built_in">test</span>/                      <span class="hljs-comment"># end-to-end tests</span>
    ├── node_modules/
    ├── package.json
    ├── tsconfig.json
    └── nest-cli.json             <span class="hljs-comment"># CLI configuration</span>
    
    • src/main.ts
      The “bootstrap” script. It creates a Nest application instance and starts listening on a port:

        <span class="hljs-keyword">import</span> { NestFactory } <span class="hljs-keyword">from</span> <span class="hljs-string">'@nestjs/core'</span>;
        <span class="hljs-keyword">import</span> { AppModule } <span class="hljs-keyword">from</span> <span class="hljs-string">'./app.module'</span>;
      
        <span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">bootstrap</span>(<span class="hljs-params"></span>) </span>{
          <span class="hljs-keyword">const</span> app = <span class="hljs-keyword">await</span> NestFactory.create(AppModule);
          <span class="hljs-keyword">await</span> app.listen(<span class="hljs-number">3000</span>);
          <span class="hljs-built_in">console</span>.log(<span class="hljs-string">`🚀 Application is running on: <span class="hljs-subst">${<span class="hljs-keyword">await</span> app.getUrl()}</span>`</span>);
        }
        bootstrap();
      
    • src/app.module.ts
      The root module. It ties together controllers and providers:

        <span class="hljs-keyword">import</span> { Module } <span class="hljs-keyword">from</span> <span class="hljs-string">'@nestjs/common'</span>;
        <span class="hljs-keyword">import</span> { AppController } <span class="hljs-keyword">from</span> <span class="hljs-string">'./app.controller'</span>;
        <span class="hljs-keyword">import</span> { AppService } <span class="hljs-keyword">from</span> <span class="hljs-string">'./app.service'</span>;
      
        <span class="hljs-meta">@Module</span>({
          imports: [],                 <span class="hljs-comment">// other modules to import</span>
          controllers: [AppController],
          providers: [AppService],
        })
        <span class="hljs-keyword">export</span> <span class="hljs-keyword">class</span> AppModule {}
      
    • src/app.controller.ts / app.service.ts
      A simple example that shows dependency injection in action:

        <span class="hljs-comment">// app.controller.ts</span>
        <span class="hljs-keyword">import</span> { Controller, Get } <span class="hljs-keyword">from</span> <span class="hljs-string">'@nestjs/common'</span>;
        <span class="hljs-keyword">import</span> { AppService } <span class="hljs-keyword">from</span> <span class="hljs-string">'./app.service'</span>;
      
        <span class="hljs-meta">@Controller</span>()
        <span class="hljs-keyword">export</span> <span class="hljs-keyword">class</span> AppController {
          <span class="hljs-keyword">constructor</span>(<span class="hljs-params"><span class="hljs-keyword">private</span> <span class="hljs-keyword">readonly</span> appService: AppService</span>) {}
      
          <span class="hljs-meta">@Get</span>()
          getHello(): <span class="hljs-built_in">string</span> {
            <span class="hljs-keyword">return</span> <span class="hljs-built_in">this</span>.appService.getHello();
          }
        }
      
        <span class="hljs-comment">// app.service.ts</span>
        <span class="hljs-keyword">import</span> { Injectable } <span class="hljs-keyword">from</span> <span class="hljs-string">'@nestjs/common'</span>;
      
        <span class="hljs-meta">@Injectable</span>()
        <span class="hljs-keyword">export</span> <span class="hljs-keyword">class</span> AppService {
          getHello(): <span class="hljs-built_in">string</span> {
            <span class="hljs-keyword">return</span> <span class="hljs-string">'Hello, NestJS!'</span>;
          }
        }
      

    With this scaffold in place, you have a minimal – but fully functional – NestJS application. From here, you can generate new modules, controllers, and services:

    <span class="hljs-comment"># Generate a new module, controller, and service for "tasks"</span>
    nest g module tasks
    nest g controller tasks
    nest g service tasks
    

    Each command will drop a new .ts file in the appropriate folder and update your module’s metadata. In the next section, we’ll dive into core Nest building blocks like modules, controllers, and providers in more detail.

    4. Core NestJS Building Blocks

    At the heart of every NestJS application are three pillars: Modules, Controllers, and Providers (often called Services). Let’s see what each one does, and how they fit together in theory and in practice.

    4.1 Modules

    A Module is a logical boundary – a container that groups related components (controllers, providers, and even other modules). Every NestJS app has at least one root module (usually AppModule), and you create feature modules (UsersModule, AuthModule, and so on) to organize code by domain.

    @Module() Decorator

    • imports: other modules to use

    • controllers: controllers that handle incoming requests

    • providers: services or values available via DI

    • exports: providers that should be visible to importing modules

    Here’s an example:

    <span class="hljs-comment">// cats.module.ts</span>
    <span class="hljs-keyword">import</span> { Module } <span class="hljs-keyword">from</span> <span class="hljs-string">'@nestjs/common'</span>;
    <span class="hljs-keyword">import</span> { CatsController } <span class="hljs-keyword">from</span> <span class="hljs-string">'./cats.controller'</span>;
    <span class="hljs-keyword">import</span> { CatsService } <span class="hljs-keyword">from</span> <span class="hljs-string">'./cats.service'</span>;
    
    <span class="hljs-meta">@Module</span>({
      imports: [],            <span class="hljs-comment">// e.g. TypeOrmModule.forFeature([Cat])</span>
      controllers: [CatsController],
      providers: [CatsService],
      <span class="hljs-built_in">exports</span>: [CatsService], <span class="hljs-comment">// makes CatsService available to other modules</span>
    })
    <span class="hljs-keyword">export</span> <span class="hljs-keyword">class</span> CatsModule {}
    

    Then in your root module:

    <span class="hljs-comment">// app.module.ts</span>
    <span class="hljs-keyword">import</span> { Module } <span class="hljs-keyword">from</span> <span class="hljs-string">'@nestjs/common'</span>;
    <span class="hljs-keyword">import</span> { CatsModule } <span class="hljs-keyword">from</span> <span class="hljs-string">'./cats/cats.module'</span>;
    
    <span class="hljs-meta">@Module</span>({
      imports: [CatsModule],
    })
    <span class="hljs-keyword">export</span> <span class="hljs-keyword">class</span> AppModule {}
    

    Now anything that injects CatsService will resolve to the one defined inside CatsModule.

    4.2 Controllers

    A Controller maps incoming HTTP requests to handler methods. It’s responsible for extracting request data (query parameters, body, headers) and returning a response. Controllers should remain thin – delegating business logic to providers.

    • @Controller(path?): Defines a route prefix

    • @Get, @Post, @Put, @Delete, and so on: Define method-level routes

    • @Param(), @Query(), @Body(), @Headers(), @Req(), @Res(): Decorators to extract request details

    Here’s an example:

    <span class="hljs-comment">// cats.controller.ts</span>
    <span class="hljs-keyword">import</span> { Controller, Get, Post, Body, Param } <span class="hljs-keyword">from</span> <span class="hljs-string">'@nestjs/common'</span>;
    <span class="hljs-keyword">import</span> { CatsService } <span class="hljs-keyword">from</span> <span class="hljs-string">'./cats.service'</span>;
    <span class="hljs-keyword">import</span> { CreateCatDto } <span class="hljs-keyword">from</span> <span class="hljs-string">'./dto/create-cat.dto'</span>;
    
    <span class="hljs-meta">@Controller</span>(<span class="hljs-string">'cats'</span>)                  <span class="hljs-comment">// prefix: /cats</span>
    <span class="hljs-keyword">export</span> <span class="hljs-keyword">class</span> CatsController {
      <span class="hljs-keyword">constructor</span>(<span class="hljs-params"><span class="hljs-keyword">private</span> <span class="hljs-keyword">readonly</span> catsService: CatsService</span>) {}
    
      <span class="hljs-meta">@Get</span>()
      findAll() {
        <span class="hljs-keyword">return</span> <span class="hljs-built_in">this</span>.catsService.findAll();  <span class="hljs-comment">// GET /cats</span>
      }
    
      <span class="hljs-meta">@Get</span>(<span class="hljs-string">':id'</span>)
      findOne(<span class="hljs-meta">@Param</span>(<span class="hljs-string">'id'</span>) id: <span class="hljs-built_in">string</span>) {
        <span class="hljs-keyword">return</span> <span class="hljs-built_in">this</span>.catsService.findOne(+id);  <span class="hljs-comment">// GET /cats/1</span>
      }
    
      <span class="hljs-meta">@Post</span>()
      create(<span class="hljs-meta">@Body</span>() createCatDto: CreateCatDto) {
        <span class="hljs-keyword">return</span> <span class="hljs-built_in">this</span>.catsService.create(createCatDto);  <span class="hljs-comment">// POST /cats</span>
      }
    }
    
    <span class="hljs-comment">// dto/create-cat.dto.ts</span>
    <span class="hljs-keyword">export</span> <span class="hljs-keyword">class</span> CreateCatDto {
      <span class="hljs-keyword">readonly</span> name: <span class="hljs-built_in">string</span>;
      <span class="hljs-keyword">readonly</span> age: <span class="hljs-built_in">number</span>;
      <span class="hljs-keyword">readonly</span> breed?: <span class="hljs-built_in">string</span>;
    }
    

    4.3 Providers (Services)

    Providers are classes annotated with @Injectable() that contain your business logic or data access. Anything you want to inject elsewhere must be a provider. You can provide plain values, factory functions, or classes.

    • @Injectable(): Marks a class as available for DI

    • Scope: Default is singleton, but you can change to request or transient

    • Custom Providers: Use useClass, useValue, useFactory, or useExisting for more control

    Here’s an example:

    <span class="hljs-comment">// cats.service.ts</span>
    <span class="hljs-keyword">import</span> { Injectable, NotFoundException } <span class="hljs-keyword">from</span> <span class="hljs-string">'@nestjs/common'</span>;
    <span class="hljs-keyword">import</span> { CreateCatDto } <span class="hljs-keyword">from</span> <span class="hljs-string">'./dto/create-cat.dto'</span>;
    
    <span class="hljs-meta">@Injectable</span>()
    <span class="hljs-keyword">export</span> <span class="hljs-keyword">class</span> CatsService {
      <span class="hljs-keyword">private</span> cats = [];
    
      create(dto: CreateCatDto) {
        <span class="hljs-keyword">const</span> newCat = { id: <span class="hljs-built_in">Date</span>.now(), ...dto };
        <span class="hljs-built_in">this</span>.cats.push(newCat);
        <span class="hljs-keyword">return</span> newCat;
      }
    
      findAll() {
        <span class="hljs-keyword">return</span> <span class="hljs-built_in">this</span>.cats;
      }
    
      findOne(id: <span class="hljs-built_in">number</span>) {
        <span class="hljs-keyword">const</span> cat = <span class="hljs-built_in">this</span>.cats.find(<span class="hljs-function"><span class="hljs-params">c</span> =></span> c.id === id);
        <span class="hljs-keyword">if</span> (!cat) {
          <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> NotFoundException(<span class="hljs-string">`Cat #<span class="hljs-subst">${id}</span> not found`</span>);
        }
        <span class="hljs-keyword">return</span> cat;
      }
    }
    

    Injecting a Custom Value:

    <span class="hljs-comment">// logger.provider.ts</span>
    <span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> LOGGER = {
      provide: <span class="hljs-string">'LOGGER'</span>,
      useValue: <span class="hljs-built_in">console</span>,
    };
    
    <span class="hljs-comment">// app.module.ts</span>
    <span class="hljs-keyword">import</span> { Module } <span class="hljs-keyword">from</span> <span class="hljs-string">'@nestjs/common'</span>;
    <span class="hljs-keyword">import</span> { LOGGER } <span class="hljs-keyword">from</span> <span class="hljs-string">'./logger.provider'</span>;
    
    <span class="hljs-meta">@Module</span>({
      providers: [LOGGER],
      <span class="hljs-built_in">exports</span>: [LOGGER],
    })
    <span class="hljs-keyword">export</span> <span class="hljs-keyword">class</span> AppModule {}
    
    <span class="hljs-comment">// some.service.ts</span>
    <span class="hljs-keyword">import</span> { Inject, Injectable } <span class="hljs-keyword">from</span> <span class="hljs-string">'@nestjs/common'</span>;
    
    <span class="hljs-meta">@Injectable</span>()
    <span class="hljs-keyword">export</span> <span class="hljs-keyword">class</span> SomeService {
      <span class="hljs-keyword">constructor</span>(<span class="hljs-params"><span class="hljs-meta">@Inject</span>(<span class="hljs-string">'LOGGER'</span>) <span class="hljs-keyword">private</span> <span class="hljs-keyword">readonly</span> logger: Console</span>) {}
    
      logMessage(msg: <span class="hljs-built_in">string</span>) {
        <span class="hljs-built_in">this</span>.logger.log(<span class="hljs-string">`Custom log: <span class="hljs-subst">${msg}</span>`</span>);
      }
    }
    

    With modules wiring up controllers and providers, NestJS gives you a scalable, testable foundation. In the next section, we’ll explore Dependency Injection in depth – how it works under the hood and how to create custom providers and factory-based injections.

    5. Dependency Injection

    Nest’s built-in Dependency Injection (DI) system is the heart of how components (controllers, services, and so on) talk to each other in a loosely-coupled, testable way.

    5.1 How DI Works in NestJS

    When your application boots, Nest builds a module-based IoC container. Each @Injectable() provider is registered in the container under a token (by default, its class). When a class declares a dependency in its constructor, Nest looks up that token and injects the matching instance.

    • Singleton scope: One instance per application (default)

    • Request scope: New instance per incoming request

    • Transient scope: New instance every time it’s injected

    Here’s an example:

    <span class="hljs-comment">// cats.service.ts</span>
    <span class="hljs-meta">@Injectable</span>()
    <span class="hljs-keyword">export</span> <span class="hljs-keyword">class</span> CatsService {
      <span class="hljs-comment">// ...</span>
    }
    
    <span class="hljs-comment">// cats.controller.ts</span>
    <span class="hljs-meta">@Controller</span>(<span class="hljs-string">'cats'</span>)
    <span class="hljs-keyword">export</span> <span class="hljs-keyword">class</span> CatsController {
      <span class="hljs-keyword">constructor</span>(<span class="hljs-params"><span class="hljs-keyword">private</span> <span class="hljs-keyword">readonly</span> catsService: CatsService</span>) {}
      <span class="hljs-comment">// Nest sees CatsService in the constructor,</span>
      <span class="hljs-comment">// finds its singleton instance, and injects it.</span>
    }
    

    Behind the scenes, Nest collects metadata from decorators (@Injectable(), @Controller()) and builds a graph of providers. When you call NestFactory.create(AppModule), it resolves that graph and wires everything together.

    5.2 Custom Providers and Factory Providers

    Sometimes you need to inject non-class values (APIs, constants) or run logic at registration time. Nest lets you define custom providers using the provide syntax.

    useValue

    Inject a plain value or object:

    <span class="hljs-comment">// config.constant.ts</span>
    <span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> APP_NAME = {
      provide: <span class="hljs-string">'APP_NAME'</span>,
      useValue: <span class="hljs-string">'MyAwesomeApp'</span>,
    };
    
    <span class="hljs-comment">// app.module.ts</span>
    <span class="hljs-meta">@Module</span>({
      providers: [APP_NAME],
      <span class="hljs-built_in">exports</span>: [<span class="hljs-string">'APP_NAME'</span>],
    })
    <span class="hljs-keyword">export</span> <span class="hljs-keyword">class</span> AppModule {}
    
    <span class="hljs-comment">// some.service.ts</span>
    <span class="hljs-meta">@Injectable</span>()
    <span class="hljs-keyword">export</span> <span class="hljs-keyword">class</span> SomeService {
      <span class="hljs-keyword">constructor</span>(<span class="hljs-params"><span class="hljs-meta">@Inject</span>(<span class="hljs-string">'APP_NAME'</span>) <span class="hljs-keyword">private</span> <span class="hljs-keyword">readonly</span> name: <span class="hljs-built_in">string</span></span>) {}
    
      whoAmI() {
        <span class="hljs-keyword">return</span> <span class="hljs-string">`Running in <span class="hljs-subst">${<span class="hljs-built_in">this</span>.name}</span>`</span>;
      }
    }
    

    useClass

    Swap implementations easily (useful for testing or feature flags):

    <span class="hljs-comment">// logger.interface.ts</span>
    <span class="hljs-keyword">export</span> <span class="hljs-keyword">interface</span> Logger {
      log(msg: <span class="hljs-built_in">string</span>): <span class="hljs-built_in">void</span>;
    }
    
    <span class="hljs-comment">// console-logger.ts</span>
    <span class="hljs-meta">@Injectable</span>()
    <span class="hljs-keyword">export</span> <span class="hljs-keyword">class</span> ConsoleLogger <span class="hljs-keyword">implements</span> Logger {
      log(msg: <span class="hljs-built_in">string</span>) { <span class="hljs-built_in">console</span>.log(msg); }
    }
    
    <span class="hljs-comment">// file-logger.ts</span>
    <span class="hljs-meta">@Injectable</span>()
    <span class="hljs-keyword">export</span> <span class="hljs-keyword">class</span> FileLogger <span class="hljs-keyword">implements</span> Logger {
      log(msg: <span class="hljs-built_in">string</span>) { <span class="hljs-comment">/* write to file */</span> }
    }
    
    <span class="hljs-comment">// app.module.ts</span>
    <span class="hljs-meta">@Module</span>({
      providers: [
        { provide: <span class="hljs-string">'Logger'</span>, useClass: FileLogger }, 
      ],
    })
    <span class="hljs-keyword">export</span> <span class="hljs-keyword">class</span> AppModule {}
    
    <span class="hljs-comment">// any.service.ts</span>
    <span class="hljs-meta">@Injectable</span>()
    <span class="hljs-keyword">export</span> <span class="hljs-keyword">class</span> AnyService {
      <span class="hljs-keyword">constructor</span>(<span class="hljs-params"><span class="hljs-meta">@Inject</span>(<span class="hljs-string">'Logger'</span>) <span class="hljs-keyword">private</span> <span class="hljs-keyword">readonly</span> logger: Logger</span>) {}
    }
    

    useFactory

    Run arbitrary factory logic (for example, async initialization, dynamic config):

    <span class="hljs-comment">// database.provider.ts</span>
    <span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> DATABASE = {
      provide: <span class="hljs-string">'DATABASE'</span>,
      useFactory: <span class="hljs-keyword">async</span> (configService: ConfigService) => {
        <span class="hljs-keyword">const</span> opts = configService.getDbOptions();
        <span class="hljs-keyword">const</span> connection = <span class="hljs-keyword">await</span> createConnection(opts);
        <span class="hljs-keyword">return</span> connection;
      },
      inject: [ConfigService],
    };
    
    <span class="hljs-comment">// app.module.ts</span>
    <span class="hljs-meta">@Module</span>({
      imports: [ConfigModule],
      providers: [DATABASE],
      <span class="hljs-built_in">exports</span>: [<span class="hljs-string">'DATABASE'</span>],
    })
    <span class="hljs-keyword">export</span> <span class="hljs-keyword">class</span> AppModule {}
    
    <span class="hljs-comment">// users.service.ts</span>
    <span class="hljs-meta">@Injectable</span>()
    <span class="hljs-keyword">export</span> <span class="hljs-keyword">class</span> UsersService {
      <span class="hljs-keyword">constructor</span>(<span class="hljs-params"><span class="hljs-meta">@Inject</span>(<span class="hljs-string">'DATABASE'</span>) <span class="hljs-keyword">private</span> <span class="hljs-keyword">readonly</span> db: Connection</span>) {}
    }
    

    With custom providers and the factory pattern, you can integrate external libraries, toggle implementations, or perform async setup – all while retaining the clear, testable structure NestJS provides.

    In the next section we’ll look at Routing and Middleware, showing how to define route handlers, apply global or per-route middleware, and extend your HTTP pipeline.

    6. Routing & Middleware

    Routing in NestJS is built on top of your controllers and decorators, while middleware lets you hook into the request/response pipeline for cross-cutting concerns like logging, authentication checks, or CORS.

    6.1 Defining Routes

    First, a bit of theory:

    • @Controller(path?) sets a URL prefix for all routes in that class.

    • @Get, @Post, @Put, @Delete, etc. define HTTP-method handlers.

    • @Param(), @Query(), @Body(), @Headers(), @Req(), @Res() extract parts of the incoming request.

    You can combine route decorators and parameter decorators to build expressive, type-safe endpoints.

    Here’s an example:

    <span class="hljs-comment">// products.controller.ts</span>
    <span class="hljs-keyword">import</span> { Controller, Get, Post, Param, Query, Body } <span class="hljs-keyword">from</span> <span class="hljs-string">'@nestjs/common'</span>;
    <span class="hljs-keyword">import</span> { ProductsService } <span class="hljs-keyword">from</span> <span class="hljs-string">'./products.service'</span>;
    <span class="hljs-keyword">import</span> { CreateProductDto } <span class="hljs-keyword">from</span> <span class="hljs-string">'./dto/create-product.dto'</span>;
    
    <span class="hljs-meta">@Controller</span>(<span class="hljs-string">'products'</span>)                <span class="hljs-comment">// all routes here start with /products</span>
    <span class="hljs-keyword">export</span> <span class="hljs-keyword">class</span> ProductsController {
      <span class="hljs-keyword">constructor</span>(<span class="hljs-params"><span class="hljs-keyword">private</span> <span class="hljs-keyword">readonly</span> productsService: ProductsService</span>) {}
    
      <span class="hljs-meta">@Get</span>()                              <span class="hljs-comment">// GET /products</span>
      findAll(
        <span class="hljs-meta">@Query</span>(<span class="hljs-string">'limit'</span>) limit = <span class="hljs-string">'10'</span>,     <span class="hljs-comment">// optional query ?limit=20</span>
      ) {
        <span class="hljs-keyword">return</span> <span class="hljs-built_in">this</span>.productsService.findAll(+limit);
      }
    
      <span class="hljs-meta">@Get</span>(<span class="hljs-string">':id'</span>)                         <span class="hljs-comment">// GET /products/123</span>
      findOne(<span class="hljs-meta">@Param</span>(<span class="hljs-string">'id'</span>) id: <span class="hljs-built_in">string</span>) {
        <span class="hljs-keyword">return</span> <span class="hljs-built_in">this</span>.productsService.findOne(+id);
      }
    
      <span class="hljs-meta">@Post</span>()                             <span class="hljs-comment">// POST /products</span>
      create(<span class="hljs-meta">@Body</span>() dto: CreateProductDto) {
        <span class="hljs-keyword">return</span> <span class="hljs-built_in">this</span>.productsService.create(dto);
      }
    }
    

    You can also nest controllers by importing a feature module, and use @Patch, @Put, @Delete, @Head, and so on for full RESTful coverage.

    6.2 Applying Middleware

    Middleware are functions that run before your routes handle a request. They’re useful for logging, body-parsing (though Nest provides built-ins), authentication guards at a lower level, rate limiting, and so on.

    You can implement them either as a functional middleware or a class implementing NestMiddleware.

    Here’s an example (Functional Middleware):

    <span class="hljs-comment">// logger.middleware.ts</span>
    <span class="hljs-keyword">import</span> { Request, Response, NextFunction } <span class="hljs-keyword">from</span> <span class="hljs-string">'express'</span>;
    
    <span class="hljs-keyword">export</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">logger</span>(<span class="hljs-params">req: Request, res: Response, next: NextFunction</span>) </span>{
      <span class="hljs-built_in">console</span>.log(<span class="hljs-string">`[<span class="hljs-subst">${<span class="hljs-keyword">new</span> <span class="hljs-built_in">Date</span>().toISOString()}</span>] <span class="hljs-subst">${req.method}</span> <span class="hljs-subst">${req.url}</span>`</span>);
      next();
    }
    
    <span class="hljs-comment">// app.module.ts</span>
    <span class="hljs-keyword">import</span> { Module, NestModule, MiddlewareConsumer } <span class="hljs-keyword">from</span> <span class="hljs-string">'@nestjs/common'</span>;
    <span class="hljs-keyword">import</span> { logger } <span class="hljs-keyword">from</span> <span class="hljs-string">'./logger.middleware'</span>;
    <span class="hljs-keyword">import</span> { ProductsModule } <span class="hljs-keyword">from</span> <span class="hljs-string">'./products/products.module'</span>;
    
    <span class="hljs-meta">@Module</span>({
      imports: [ProductsModule],
    })
    <span class="hljs-keyword">export</span> <span class="hljs-keyword">class</span> AppModule <span class="hljs-keyword">implements</span> NestModule {
      configure(consumer: MiddlewareConsumer) {
        consumer
          .apply(logger)                 <span class="hljs-comment">// apply logger</span>
          .forRoutes(<span class="hljs-string">'products'</span>);        <span class="hljs-comment">// only for /products routes</span>
      }
    }
    

    And here’s another example (Class-based Middleware):

    <span class="hljs-comment">// auth.middleware.ts</span>
    <span class="hljs-keyword">import</span> { Injectable, NestMiddleware } <span class="hljs-keyword">from</span> <span class="hljs-string">'@nestjs/common'</span>;
    <span class="hljs-keyword">import</span> { Request, Response, NextFunction } <span class="hljs-keyword">from</span> <span class="hljs-string">'express'</span>;
    
    <span class="hljs-meta">@Injectable</span>()
    <span class="hljs-keyword">export</span> <span class="hljs-keyword">class</span> AuthMiddleware <span class="hljs-keyword">implements</span> NestMiddleware {
      use(req: Request, res: Response, next: NextFunction) {
        <span class="hljs-keyword">if</span> (!req.headers.authorization) {
          <span class="hljs-keyword">return</span> res.status(<span class="hljs-number">401</span>).send(<span class="hljs-string">'Unauthorized'</span>);
        }
        <span class="hljs-comment">// validate token...</span>
        next();
      }
    }
    
    <span class="hljs-comment">// security.module.ts</span>
    <span class="hljs-keyword">import</span> { Module, NestModule, MiddlewareConsumer } <span class="hljs-keyword">from</span> <span class="hljs-string">'@nestjs/common'</span>;
    <span class="hljs-keyword">import</span> { AuthMiddleware } <span class="hljs-keyword">from</span> <span class="hljs-string">'./auth.middleware'</span>;
    <span class="hljs-keyword">import</span> { UsersController } <span class="hljs-keyword">from</span> <span class="hljs-string">'./users.controller'</span>;
    
    <span class="hljs-meta">@Module</span>({
      controllers: [UsersController],
    })
    <span class="hljs-keyword">export</span> <span class="hljs-keyword">class</span> SecurityModule <span class="hljs-keyword">implements</span> NestModule {
      configure(consumer: MiddlewareConsumer) {
        consumer
          .apply(AuthMiddleware)
          .forRoutes(UsersController);    <span class="hljs-comment">// apply to all routes in UsersController</span>
      }
    }
    

    Tip: Global middleware can be applied in your main.ts before the app.listen() call via app.use(logger) if you want it on every request.

    With routing and middleware set up, you have full control over how requests flow through your application. Next up, we’ll dive into Request Lifecycle and Pipes, exploring how data transforms and validations happen as part of each request.

    7. Request Lifecycle & Pipes

    NestJS processes each incoming request through a defined “lifecycle” of steps – routing to the correct handler, applying pipes, guards, interceptors, and finally invoking your controller method. Pipes sit between the incoming request and your handler, transforming or validating data before it reaches your business logic.

    7.1 What Are Pipes?

    A Pipe is a class annotated with @Injectable() that implements the PipeTransform interface. It has a single method:

    transform(value: <span class="hljs-built_in">any</span>, metadata: ArgumentMetadata): <span class="hljs-built_in">any</span>
    
    • Transformation: Convert input data (for example, a string "123") into the desired type (number 123).

    • Validation: Check that incoming data meets certain rules and throw an exception (usually a BadRequestException) if it doesn’t.

    By default, pipes run after middleware and before guards/interceptors, for each decorated parameter (@Body(), @Param(), and so on).

    Here’s how it works:
    Nest ships with a handy global validation pipe that integrates with class-validator:

    <span class="hljs-comment">// main.ts</span>
    <span class="hljs-keyword">import</span> { ValidationPipe } <span class="hljs-keyword">from</span> <span class="hljs-string">'@nestjs/common'</span>;
    <span class="hljs-keyword">import</span> { NestFactory }    <span class="hljs-keyword">from</span> <span class="hljs-string">'@nestjs/core'</span>;
    <span class="hljs-keyword">import</span> { AppModule }      <span class="hljs-keyword">from</span> <span class="hljs-string">'./app.module'</span>;
    
    <span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">bootstrap</span>(<span class="hljs-params"></span>) </span>{
      <span class="hljs-keyword">const</span> app = <span class="hljs-keyword">await</span> NestFactory.create(AppModule);
      <span class="hljs-comment">// Automatically validate and strip unknown properties</span>
      app.useGlobalPipes(<span class="hljs-keyword">new</span> ValidationPipe({ whitelist: <span class="hljs-literal">true</span>, forbidNonWhitelisted: <span class="hljs-literal">true</span> }));
      <span class="hljs-keyword">await</span> app.listen(<span class="hljs-number">3000</span>);
    }
    bootstrap();
    

    With this in place, any DTO annotated with validation decorators will be checked before your handler runs:

    <span class="hljs-comment">// dto/create-user.dto.ts</span>
    <span class="hljs-keyword">import</span> { IsEmail, IsString, MinLength } <span class="hljs-keyword">from</span> <span class="hljs-string">'class-validator'</span>;
    
    <span class="hljs-keyword">export</span> <span class="hljs-keyword">class</span> CreateUserDto {
      <span class="hljs-meta">@IsEmail</span>()           <span class="hljs-comment">// must be a valid email</span>
      email: <span class="hljs-built_in">string</span>;
    
      <span class="hljs-meta">@IsString</span>()          <span class="hljs-comment">// must be a string</span>
      <span class="hljs-meta">@MinLength</span>(<span class="hljs-number">8</span>)        <span class="hljs-comment">// at least 8 characters</span>
      password: <span class="hljs-built_in">string</span>;
    }
    
    <span class="hljs-comment">// users.controller.ts</span>
    <span class="hljs-meta">@Post</span>()
    createUser(<span class="hljs-meta">@Body</span>() dto: CreateUserDto) {
      <span class="hljs-comment">// If body.email isn't an email, or password is shorter,</span>
      <span class="hljs-comment">// Nest throws a 400 Bad Request with details.</span>
      <span class="hljs-keyword">return</span> <span class="hljs-built_in">this</span>.usersService.create(dto);
    }
    

    7.2 Built-In vs. Custom Pipes

    Built-In Pipes

    Nest provides several out-of-the-box pipes:

    • ValidationPipe: Integrates with class-validator for DTO validation (shown above).

    • ParseIntPipe: Converts a route parameter to number or throws BadRequestException.

    • ParseBoolPipe, ParseUUIDPipe, ParseFloatPipe, and so on.

    <span class="hljs-meta">@Get</span>(<span class="hljs-string">':id'</span>)
    getById(<span class="hljs-meta">@Param</span>(<span class="hljs-string">'id'</span>, ParseIntPipe) id: <span class="hljs-built_in">number</span>) {
      <span class="hljs-comment">// id is guaranteed to be a number here</span>
      <span class="hljs-keyword">return</span> <span class="hljs-built_in">this</span>.itemsService.findOne(id);
    }
    

    Custom Pipes

    You can write your own to handle any transformation or validation logic:

    <span class="hljs-keyword">import</span> { PipeTransform, Injectable, BadRequestException } <span class="hljs-keyword">from</span> <span class="hljs-string">'@nestjs/common'</span>;
    
    <span class="hljs-meta">@Injectable</span>()
    <span class="hljs-keyword">export</span> <span class="hljs-keyword">class</span> ParsePositiveIntPipe <span class="hljs-keyword">implements</span> PipeTransform<<span class="hljs-built_in">string</span>, <span class="hljs-built_in">number</span>> {
      transform(value: <span class="hljs-built_in">string</span>): <span class="hljs-built_in">number</span> {
        <span class="hljs-keyword">const</span> val = <span class="hljs-built_in">parseInt</span>(value, <span class="hljs-number">10</span>);
        <span class="hljs-keyword">if</span> (<span class="hljs-built_in">isNaN</span>(val) || val <= <span class="hljs-number">0</span>) {
          <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> BadRequestException(<span class="hljs-string">`"<span class="hljs-subst">${value}</span>" is not a positive integer`</span>);
        }
        <span class="hljs-keyword">return</span> val;
      }
    }
    

    Use it just like a built-in pipe:

    <span class="hljs-meta">@Get</span>(<span class="hljs-string">'order/:orderId'</span>)
    getOrder(
      <span class="hljs-meta">@Param</span>(<span class="hljs-string">'orderId'</span>, ParsePositiveIntPipe) orderId: <span class="hljs-built_in">number</span>
    ) {
      <span class="hljs-comment">// orderId is a validated, positive integer</span>
      <span class="hljs-keyword">return</span> <span class="hljs-built_in">this</span>.ordersService.findById(orderId);
    }
    

    With pipes you ensure that every piece of data entering your handlers is correctly typed and valid, keeping your business logic clean and focused. In the next section, we’ll explore Guards and Authorization to control access to your endpoints.

    8. Guards & Authorization

    Guards sit in the request lifecycle after pipes and before interceptors/controllers. They determine whether a given request should be allowed to proceed based on custom logic. This is ideal for authentication, role checks, or feature flags.

    8.1 Implementing Guards

    A Guard is a class that implements the CanActivate interface, with a single method:

    canActivate(context: ExecutionContext): <span class="hljs-built_in">boolean</span> | <span class="hljs-built_in">Promise</span><<span class="hljs-built_in">boolean</span>> | Observable<<span class="hljs-built_in">boolean</span>>;
    
    • ExecutionContext gives you access to the underlying request/response and route metadata.

    • If canActivate returns true, the request continues. Returning false or throwing an exception (for example, UnauthorizedException) blocks it.

    You register guards either globally, at the controller level, or on individual routes with the @UseGuards() decorator.

    Here’s how guards work:

    1. Creating a simple auth guard:
    <span class="hljs-comment">// auth.guard.ts</span>
    <span class="hljs-keyword">import</span> { Injectable, CanActivate, ExecutionContext, UnauthorizedException } <span class="hljs-keyword">from</span> <span class="hljs-string">'@nestjs/common'</span>;
    
    <span class="hljs-meta">@Injectable</span>()
    <span class="hljs-keyword">export</span> <span class="hljs-keyword">class</span> AuthGuard <span class="hljs-keyword">implements</span> CanActivate {
      canActivate(context: ExecutionContext): <span class="hljs-built_in">boolean</span> {
        <span class="hljs-keyword">const</span> req = context.switchToHttp().getRequest();
        <span class="hljs-keyword">const</span> authHeader = req.headers.authorization;
        <span class="hljs-keyword">if</span> (!authHeader || !authHeader.startsWith(<span class="hljs-string">'Bearer '</span>)) {
          <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> UnauthorizedException(<span class="hljs-string">'Missing or invalid authorization header'</span>);
        }
        <span class="hljs-comment">// Basic token check (replace with real validation)</span>
        <span class="hljs-keyword">const</span> token = authHeader.split(<span class="hljs-string">' '</span>)[<span class="hljs-number">1</span>];
        <span class="hljs-keyword">if</span> (token !== <span class="hljs-string">'valid-token'</span>) {
          <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> UnauthorizedException(<span class="hljs-string">'Invalid token'</span>);
        }
        <span class="hljs-comment">// Attach user info if needed:</span>
        req.user = { id: <span class="hljs-number">1</span>, name: <span class="hljs-string">'Alice'</span> };
        <span class="hljs-keyword">return</span> <span class="hljs-literal">true</span>;
      }
    }
    
    1. Applying the guard
    • Globally (in main.ts):

        <span class="hljs-keyword">import</span> { NestFactory } <span class="hljs-keyword">from</span> <span class="hljs-string">'@nestjs/core'</span>;
        <span class="hljs-keyword">import</span> { AppModule } <span class="hljs-keyword">from</span> <span class="hljs-string">'./app.module'</span>;
        <span class="hljs-keyword">import</span> { AuthGuard } <span class="hljs-keyword">from</span> <span class="hljs-string">'./auth.guard'</span>;
      
        <span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">bootstrap</span>(<span class="hljs-params"></span>) </span>{
          <span class="hljs-keyword">const</span> app = <span class="hljs-keyword">await</span> NestFactory.create(AppModule);
          <span class="hljs-comment">// every incoming request passes through AuthGuard</span>
          app.useGlobalGuards(<span class="hljs-keyword">new</span> AuthGuard());
          <span class="hljs-keyword">await</span> app.listen(<span class="hljs-number">3000</span>);
        }
        bootstrap();
      
    • Controller-Level:

        <span class="hljs-keyword">import</span> { Controller, Get, UseGuards } <span class="hljs-keyword">from</span> <span class="hljs-string">'@nestjs/common'</span>;
        <span class="hljs-keyword">import</span> { AuthGuard } <span class="hljs-keyword">from</span> <span class="hljs-string">'./auth.guard'</span>;
      
        <span class="hljs-meta">@Controller</span>(<span class="hljs-string">'profile'</span>)
        <span class="hljs-meta">@UseGuards</span>(AuthGuard)       <span class="hljs-comment">// applies to all routes in this controller</span>
        <span class="hljs-keyword">export</span> <span class="hljs-keyword">class</span> ProfileController {
          <span class="hljs-meta">@Get</span>()
          getProfile(<span class="hljs-meta">@Req</span>() req) {
            <span class="hljs-keyword">return</span> req.user;
          }
        }
      
    • Route-Level:

        <span class="hljs-meta">@Get</span>(<span class="hljs-string">'admin'</span>)
        <span class="hljs-meta">@UseGuards</span>(AdminGuard, AuthGuard)  <span class="hljs-comment">// chain multiple guards</span>
        getAdminData() { <span class="hljs-comment">/* ... */</span> }
      

    8.2 Role-Based Access Control

    Beyond plain authentication, you often need authorization – ensuring a user has the correct role or permission. You can build a guard that reads metadata (for example, required roles) and verifies user claims.

    Here’s how it works:

    1. Define a roles decorator:
    <span class="hljs-comment">// roles.decorator.ts</span>
    <span class="hljs-keyword">import</span> { SetMetadata } <span class="hljs-keyword">from</span> <span class="hljs-string">'@nestjs/common'</span>;
    <span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> Roles = <span class="hljs-function">(<span class="hljs-params">...roles: <span class="hljs-built_in">string</span>[]</span>) =></span> SetMetadata(<span class="hljs-string">'roles'</span>, roles);
    
    1. Create a roles guard:
    <span class="hljs-comment">// roles.guard.ts</span>
    <span class="hljs-keyword">import</span> { Injectable, CanActivate, ExecutionContext, ForbiddenException } <span class="hljs-keyword">from</span> <span class="hljs-string">'@nestjs/common'</span>;
    <span class="hljs-keyword">import</span> { Reflector } <span class="hljs-keyword">from</span> <span class="hljs-string">'@nestjs/core'</span>;
    
    <span class="hljs-meta">@Injectable</span>()
    <span class="hljs-keyword">export</span> <span class="hljs-keyword">class</span> RolesGuard <span class="hljs-keyword">implements</span> CanActivate {
      <span class="hljs-keyword">constructor</span>(<span class="hljs-params"><span class="hljs-keyword">private</span> reflector: Reflector</span>) {}
    
      canActivate(context: ExecutionContext): <span class="hljs-built_in">boolean</span> {
        <span class="hljs-keyword">const</span> requiredRoles = <span class="hljs-built_in">this</span>.reflector.get<<span class="hljs-built_in">string</span>[]>(<span class="hljs-string">'roles'</span>, context.getHandler());
        <span class="hljs-keyword">if</span> (!requiredRoles) {
          <span class="hljs-keyword">return</span> <span class="hljs-literal">true</span>; <span class="hljs-comment">// no roles metadata => open route</span>
        }
        <span class="hljs-keyword">const</span> { user } = context.switchToHttp().getRequest();
        <span class="hljs-keyword">const</span> hasRole = requiredRoles.some(<span class="hljs-function"><span class="hljs-params">role</span> =></span> user.roles?.includes(role));
        <span class="hljs-keyword">if</span> (!hasRole) {
          <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> ForbiddenException(<span class="hljs-string">'You do not have permission (roles)'</span>);
        }
        <span class="hljs-keyword">return</span> <span class="hljs-literal">true</span>;
      }
    }
    
    1. Apply roles metadata and guard:
    <span class="hljs-meta">@Controller</span>(<span class="hljs-string">'projects'</span>)
    <span class="hljs-meta">@UseGuards</span>(AuthGuard, RolesGuard)
    <span class="hljs-keyword">export</span> <span class="hljs-keyword">class</span> ProjectsController {
      <span class="hljs-meta">@Get</span>()
      <span class="hljs-meta">@Roles</span>(<span class="hljs-string">'user'</span>, <span class="hljs-string">'admin'</span>)         <span class="hljs-comment">// route requires either 'user' or 'admin'</span>
      findAll() { <span class="hljs-comment">/* ... */</span> }
    
      <span class="hljs-meta">@Post</span>()
      <span class="hljs-meta">@Roles</span>(<span class="hljs-string">'admin'</span>)                 <span class="hljs-comment">// only 'admin' can create</span>
      create() { <span class="hljs-comment">/* ... */</span> }
    }
    

    With this setup:

    • AuthGuard ensures the request is authenticated and populates req.user.

    • RolesGuard reads the @Roles() metadata to enforce role-based access.

    Guards give you a powerful, declarative way to enforce security and authorization policies. In the next section, we’ll cover Exception Filters – how to catch and format errors centrally, keeping your controllers clean.

    9. Exception Filters

    Exception filters let you centralize error handling, transforming thrown exceptions into consistent HTTP responses or other formats. You can rely on Nest’s built-in behavior for many cases, but custom filters give you control over logging, response shape, or handling non-HTTP errors.

    9.1 Handling Errors Gracefully

    By default, if a controller or service throws an HttpException (or one of Nest’s built-in exceptions like NotFoundException, BadRequestException, and so on), Nest catches it and sends an appropriate HTTP response with status code and JSON body containing statusCode, message, and error.

    If an unexpected error (for example, a runtime error) bubbles up, Nest uses its default exception filter to return a 500 Internal Server Error with a generic message.

    Controllers/services should throw exceptions rather than return error codes manually, so the framework can format consistently.

    Here’s how it works:

    <span class="hljs-comment">// users.service.ts</span>
    <span class="hljs-keyword">import</span> { Injectable, NotFoundException } <span class="hljs-keyword">from</span> <span class="hljs-string">'@nestjs/common'</span>;
    
    <span class="hljs-meta">@Injectable</span>()
    <span class="hljs-keyword">export</span> <span class="hljs-keyword">class</span> UsersService {
      <span class="hljs-keyword">private</span> users = [{ id: <span class="hljs-number">1</span>, name: <span class="hljs-string">'Alice'</span> }];
    
      findOne(id: <span class="hljs-built_in">number</span>) {
        <span class="hljs-keyword">const</span> user = <span class="hljs-built_in">this</span>.users.find(<span class="hljs-function"><span class="hljs-params">u</span> =></span> u.id === id);
        <span class="hljs-keyword">if</span> (!user) {
          <span class="hljs-comment">// results in 404 with JSON { statusCode: 404, message: 'User #2 not found', error: 'Not Found' }</span>
          <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> NotFoundException(<span class="hljs-string">`User #<span class="hljs-subst">${id}</span> not found`</span>);
        }
        <span class="hljs-keyword">return</span> user;
      }
    }
    
    <span class="hljs-comment">// users.controller.ts</span>
    <span class="hljs-keyword">import</span> { Controller, Get, Param, ParseIntPipe } <span class="hljs-keyword">from</span> <span class="hljs-string">'@nestjs/common'</span>;
    
    <span class="hljs-meta">@Controller</span>(<span class="hljs-string">'users'</span>)
    <span class="hljs-keyword">export</span> <span class="hljs-keyword">class</span> UsersController {
      <span class="hljs-keyword">constructor</span>(<span class="hljs-params"><span class="hljs-keyword">private</span> <span class="hljs-keyword">readonly</span> usersService: UsersService</span>) {}
    
      <span class="hljs-meta">@Get</span>(<span class="hljs-string">':id'</span>)
      getUser(<span class="hljs-meta">@Param</span>(<span class="hljs-string">'id'</span>, ParseIntPipe) id: <span class="hljs-built_in">number</span>) {
        <span class="hljs-keyword">return</span> <span class="hljs-built_in">this</span>.usersService.findOne(id);
      }
    }
    

    If findOne throws, Nest’s default filter sends a structured JSON error. For unexpected errors (like a thrown Error), Nest wraps it into a 500 response.

    9.2 Creating Custom Filters

    You can implement the ExceptionFilter interface or extend BaseExceptionFilter. Just use the @Catch() decorator to target specific exception types (or leave empty to catch all).

    In catch(exception, host), you can extract context (HTTP request/response) and shape your response (for example, add metadata, custom fields, or a uniform envelope). You can also log exceptions or report to external systems here.

    You can apply filters globally, to a controller, or to an individual route.

    Here’s how it works:

    1. Simple logging filter
      Catch all exceptions, log details, then delegate to default behavior:

       <span class="hljs-comment">// logging-exception.filter.ts</span>
       <span class="hljs-keyword">import</span> {
         ExceptionFilter,
         Catch,
         ArgumentsHost,
         HttpException,
         HttpStatus,
         Logger,
       } <span class="hljs-keyword">from</span> <span class="hljs-string">'@nestjs/common'</span>;
       <span class="hljs-keyword">import</span> { BaseExceptionFilter } <span class="hljs-keyword">from</span> <span class="hljs-string">'@nestjs/core'</span>;
      
       <span class="hljs-meta">@Catch</span>() <span class="hljs-comment">// no args = catch every exception</span>
       <span class="hljs-keyword">export</span> <span class="hljs-keyword">class</span> LoggingExceptionFilter <span class="hljs-keyword">extends</span> BaseExceptionFilter {
         <span class="hljs-keyword">private</span> <span class="hljs-keyword">readonly</span> logger = <span class="hljs-keyword">new</span> Logger(LoggingExceptionFilter.name);
      
         <span class="hljs-keyword">catch</span>(exception: unknown, host: ArgumentsHost) {
           <span class="hljs-keyword">const</span> ctx = host.switchToHttp();
           <span class="hljs-keyword">const</span> req = ctx.getRequest<Request>();
           <span class="hljs-keyword">const</span> res = ctx.getResponse();
      
           <span class="hljs-comment">// Log stack or message</span>
           <span class="hljs-keyword">if</span> (exception <span class="hljs-keyword">instanceof</span> <span class="hljs-built_in">Error</span>) {
             <span class="hljs-built_in">this</span>.logger.error(<span class="hljs-string">`Error on <span class="hljs-subst">${req.method}</span> <span class="hljs-subst">${req.url}</span>`</span>, exception.stack);
           } <span class="hljs-keyword">else</span> {
             <span class="hljs-built_in">this</span>.logger.error(<span class="hljs-string">`Unknown exception on <span class="hljs-subst">${req.method}</span> <span class="hljs-subst">${req.url}</span>`</span>);
           }
      
           <span class="hljs-comment">// Delegate to default filter for HTTP exceptions or generic 500</span>
           <span class="hljs-built_in">super</span>.catch(exception, host);
         }
       }
      

      Apply globally in main.ts:

       <span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">bootstrap</span>(<span class="hljs-params"></span>) </span>{
         <span class="hljs-keyword">const</span> app = <span class="hljs-keyword">await</span> NestFactory.create(AppModule);
         app.useGlobalFilters(<span class="hljs-keyword">new</span> LoggingExceptionFilter(app.get(HttpAdapterHost)));
         <span class="hljs-keyword">await</span> app.listen(<span class="hljs-number">3000</span>);
       }
      

      (If extending BaseExceptionFilter, pass the adapter host to the constructor or super as needed.)

    2. Custom response shape
      Suppose you want all errors to return { success: false, error: { code, message } }:

       <span class="hljs-comment">// custom-response.filter.ts</span>
       <span class="hljs-keyword">import</span> {
         ExceptionFilter,
         Catch,
         ArgumentsHost,
         HttpException,
         HttpStatus,
       } <span class="hljs-keyword">from</span> <span class="hljs-string">'@nestjs/common'</span>;
      
       <span class="hljs-meta">@Catch</span>()
       <span class="hljs-keyword">export</span> <span class="hljs-keyword">class</span> CustomResponseFilter <span class="hljs-keyword">implements</span> ExceptionFilter {
         <span class="hljs-keyword">catch</span>(exception: unknown, host: ArgumentsHost) {
           <span class="hljs-keyword">const</span> ctx = host.switchToHttp();
           <span class="hljs-keyword">const</span> response = ctx.getResponse();
           <span class="hljs-keyword">const</span> request = ctx.getRequest<Request>();
      
           <span class="hljs-keyword">let</span> status: <span class="hljs-built_in">number</span>;
           <span class="hljs-keyword">let</span> message: <span class="hljs-built_in">string</span> | <span class="hljs-built_in">object</span>;
      
           <span class="hljs-keyword">if</span> (exception <span class="hljs-keyword">instanceof</span> HttpException) {
             status = exception.getStatus();
             <span class="hljs-keyword">const</span> res = exception.getResponse();
             <span class="hljs-comment">// res might be a string or object</span>
             message = <span class="hljs-keyword">typeof</span> res === <span class="hljs-string">'string'</span> ? { message: res } : res;
           } <span class="hljs-keyword">else</span> {
             status = HttpStatus.INTERNAL_SERVER_ERROR;
             message = { message: <span class="hljs-string">'Internal server error'</span> };
           }
      
           response.status(status).json({
             success: <span class="hljs-literal">false</span>,
             error: {
               statusCode: status,
               ...(
                 <span class="hljs-keyword">typeof</span> message === <span class="hljs-string">'object'</span>
                   ? message
                   : { message }
               ),
             },
             timestamp: <span class="hljs-keyword">new</span> <span class="hljs-built_in">Date</span>().toISOString(),
             path: request.url,
           });
         }
       }
      

      Apply at controller or route level:

       <span class="hljs-meta">@Controller</span>(<span class="hljs-string">'orders'</span>)
       <span class="hljs-meta">@UseFilters</span>(CustomResponseFilter)
       <span class="hljs-keyword">export</span> <span class="hljs-keyword">class</span> OrdersController {
         <span class="hljs-comment">// ...</span>
       }
      
    3. Catching specific exceptions
      If you have a custom exception class:

       <span class="hljs-keyword">export</span> <span class="hljs-keyword">class</span> PaymentFailedException <span class="hljs-keyword">extends</span> HttpException {
         <span class="hljs-keyword">constructor</span>(<span class="hljs-params">details: <span class="hljs-built_in">string</span></span>) {
           <span class="hljs-built_in">super</span>({ message: <span class="hljs-string">'Payment failed'</span>, details }, HttpStatus.PAYMENT_REQUIRED);
         }
       }
      

      You can write a filter that only catches that:

       <span class="hljs-meta">@Catch</span>(PaymentFailedException)
       <span class="hljs-keyword">export</span> <span class="hljs-keyword">class</span> PaymentFailedFilter <span class="hljs-keyword">implements</span> ExceptionFilter {
         <span class="hljs-keyword">catch</span>(exception: PaymentFailedException, host: ArgumentsHost) {
           <span class="hljs-keyword">const</span> ctx = host.switchToHttp();
           <span class="hljs-keyword">const</span> res = ctx.getResponse();
           <span class="hljs-keyword">const</span> status = exception.getStatus();
           <span class="hljs-keyword">const</span> { message, details } = exception.getResponse() <span class="hljs-keyword">as</span> <span class="hljs-built_in">any</span>;
           res.status(status).json({
             error: {
               message,
               details,
             },
             help: <span class="hljs-string">'Please verify your payment method and retry.'</span>,
           });
         }
       }
      

      Then apply only where payments occur:

       <span class="hljs-meta">@Post</span>(<span class="hljs-string">'charge'</span>)
       <span class="hljs-meta">@UseFilters</span>(PaymentFailedFilter)
       charge() {
         <span class="hljs-comment">// ...</span>
       }
      

    With exception filters in place, you ensure a consistent error contract, centralized logging or reporting, and tailored handling of different error types. Next up: Interceptors and Logging, where we’ll see how to transform responses, measure performance, and hook around method execution.

    10. Interceptors & Logging

    Interceptors wrap around method execution, letting you transform responses, bind extra logic before/after method calls, or measure performance. They’re ideal for cross-cutting concerns like logging, response shaping, caching, or timing metrics.

    10.1 Transforming Responses

    An Interceptor implements the NestInterceptor interface with an intercept(context, next) method.

    Inside intercept, you typically call next.handle() which returns an Observable of the handler’s result. You can then apply RxJS operators (like map) to modify the data before it’s sent to the client.

    Common uses are wrapping all responses in a uniform envelope, filtering out certain fields, or adding metadata.

    Here’s how it works:

    1. Basic response wrapper
      Suppose you want every successful response to be { success: true, data: <original> }.

       <span class="hljs-comment">// response.interceptor.ts</span>
       <span class="hljs-keyword">import</span> {
         Injectable,
         NestInterceptor,
         ExecutionContext,
         CallHandler,
       } <span class="hljs-keyword">from</span> <span class="hljs-string">'@nestjs/common'</span>;
       <span class="hljs-keyword">import</span> { Observable } <span class="hljs-keyword">from</span> <span class="hljs-string">'rxjs'</span>;
       <span class="hljs-keyword">import</span> { map } <span class="hljs-keyword">from</span> <span class="hljs-string">'rxjs/operators'</span>;
      
       <span class="hljs-meta">@Injectable</span>()
       <span class="hljs-keyword">export</span> <span class="hljs-keyword">class</span> ResponseInterceptor <span class="hljs-keyword">implements</span> NestInterceptor {
         intercept(context: ExecutionContext, next: CallHandler): Observable<<span class="hljs-built_in">any</span>> {
           <span class="hljs-keyword">return</span> next.handle().pipe(
             map(<span class="hljs-function"><span class="hljs-params">data</span> =></span> ({
               success: <span class="hljs-literal">true</span>,
               data,
             })),
           );
         }
       }
      

      Apply globally in main.ts:

       <span class="hljs-keyword">import</span> { NestFactory } <span class="hljs-keyword">from</span> <span class="hljs-string">'@nestjs/core'</span>;
       <span class="hljs-keyword">import</span> { AppModule } <span class="hljs-keyword">from</span> <span class="hljs-string">'./app.module'</span>;
       <span class="hljs-keyword">import</span> { ResponseInterceptor } <span class="hljs-keyword">from</span> <span class="hljs-string">'./common/response.interceptor'</span>;
      
       <span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">bootstrap</span>(<span class="hljs-params"></span>) </span>{
         <span class="hljs-keyword">const</span> app = <span class="hljs-keyword">await</span> NestFactory.create(AppModule);
         app.useGlobalInterceptors(<span class="hljs-keyword">new</span> ResponseInterceptor());
         <span class="hljs-keyword">await</span> app.listen(<span class="hljs-number">3000</span>);
       }
       bootstrap();
      

      Now, if a controller method returns { id: 1, name: 'Alice' }, the client sees:

       {
         <span class="hljs-attr">"success"</span>: <span class="hljs-literal">true</span>,
         <span class="hljs-attr">"data"</span>: { <span class="hljs-attr">"id"</span>: <span class="hljs-number">1</span>, <span class="hljs-attr">"name"</span>: <span class="hljs-string">"Alice"</span> }
       }
      
    2. Filtering sensitive fields
      You might want to strip out fields like password before sending a user object:

       <span class="hljs-comment">// sanitize.interceptor.ts</span>
       <span class="hljs-keyword">import</span> {
         Injectable,
         NestInterceptor,
         ExecutionContext,
         CallHandler,
       } <span class="hljs-keyword">from</span> <span class="hljs-string">'@nestjs/common'</span>;
       <span class="hljs-keyword">import</span> { Observable } <span class="hljs-keyword">from</span> <span class="hljs-string">'rxjs'</span>;
       <span class="hljs-keyword">import</span> { map } <span class="hljs-keyword">from</span> <span class="hljs-string">'rxjs/operators'</span>;
      
       <span class="hljs-meta">@Injectable</span>()
       <span class="hljs-keyword">export</span> <span class="hljs-keyword">class</span> SanitizeInterceptor <span class="hljs-keyword">implements</span> NestInterceptor {
         intercept(context: ExecutionContext, next: CallHandler): Observable<<span class="hljs-built_in">any</span>> {
           <span class="hljs-keyword">return</span> next.handle().pipe(
             map(<span class="hljs-function"><span class="hljs-params">data</span> =></span> {
               <span class="hljs-keyword">if</span> (data && <span class="hljs-keyword">typeof</span> data === <span class="hljs-string">'object'</span>) {
                 <span class="hljs-keyword">const</span> { password, ...rest } = data;
                 <span class="hljs-keyword">return</span> rest;
               }
               <span class="hljs-keyword">return</span> data;
             }),
           );
         }
       }
      

      Apply at controller or route:

       <span class="hljs-meta">@Controller</span>(<span class="hljs-string">'users'</span>)
       <span class="hljs-meta">@UseInterceptors</span>(SanitizeInterceptor)
       <span class="hljs-keyword">export</span> <span class="hljs-keyword">class</span> UsersController {
         <span class="hljs-meta">@Get</span>(<span class="hljs-string">':id'</span>)
         getUser(<span class="hljs-meta">@Param</span>(<span class="hljs-string">'id'</span>) id: <span class="hljs-built_in">string</span>) {
           <span class="hljs-comment">// returns a user object with a password field internally,</span>
           <span class="hljs-comment">// but interceptor strips it before sending to client</span>
           <span class="hljs-keyword">return</span> <span class="hljs-built_in">this</span>.usersService.findOne(+id);
         }
       }
      
    3. Serializing with class-transformer
      If you use classes with decorators, you can integrate with class-transformer:

       <span class="hljs-comment">// user.entity.ts</span>
       <span class="hljs-keyword">import</span> { Exclude, Expose } <span class="hljs-keyword">from</span> <span class="hljs-string">'class-transformer'</span>;
      
       <span class="hljs-keyword">export</span> <span class="hljs-keyword">class</span> User {
         id: <span class="hljs-built_in">number</span>;
         name: <span class="hljs-built_in">string</span>;
      
         <span class="hljs-meta">@Exclude</span>()
         password: <span class="hljs-built_in">string</span>;
      
         <span class="hljs-meta">@Expose</span>()
         get displayName(): <span class="hljs-built_in">string</span> {
           <span class="hljs-keyword">return</span> <span class="hljs-string">`<span class="hljs-subst">${<span class="hljs-built_in">this</span>.name}</span> (#<span class="hljs-subst">${<span class="hljs-built_in">this</span>.id}</span>)`</span>;
         }
       }
      
       <span class="hljs-comment">// class-transform.interceptor.ts</span>
       <span class="hljs-keyword">import</span> {
         Injectable,
         NestInterceptor,
         ExecutionContext,
         CallHandler,
       } <span class="hljs-keyword">from</span> <span class="hljs-string">'@nestjs/common'</span>;
       <span class="hljs-keyword">import</span> { plainToInstance } <span class="hljs-keyword">from</span> <span class="hljs-string">'class-transformer'</span>;
       <span class="hljs-keyword">import</span> { Observable } <span class="hljs-keyword">from</span> <span class="hljs-string">'rxjs'</span>;
       <span class="hljs-keyword">import</span> { map } <span class="hljs-keyword">from</span> <span class="hljs-string">'rxjs/operators'</span>;
      
       <span class="hljs-meta">@Injectable</span>()
       <span class="hljs-keyword">export</span> <span class="hljs-keyword">class</span> ClassTransformInterceptor<T> <span class="hljs-keyword">implements</span> NestInterceptor {
         <span class="hljs-keyword">constructor</span>(<span class="hljs-params"><span class="hljs-keyword">private</span> dto: <span class="hljs-keyword">new</span> (...args: <span class="hljs-built_in">any</span>[]) => T</span>) {}
      
         intercept(context: ExecutionContext, next: CallHandler): Observable<<span class="hljs-built_in">any</span>> {
           <span class="hljs-keyword">return</span> next.handle().pipe(
             map(<span class="hljs-function"><span class="hljs-params">data</span> =></span> {
               <span class="hljs-keyword">return</span> plainToInstance(<span class="hljs-built_in">this</span>.dto, data, {
                 excludeExtraneousValues: <span class="hljs-literal">true</span>,
               });
             }),
           );
         }
       }
      

      Apply with a DTO:

       <span class="hljs-meta">@Controller</span>(<span class="hljs-string">'users'</span>)
       <span class="hljs-keyword">export</span> <span class="hljs-keyword">class</span> UsersController {
         <span class="hljs-meta">@Get</span>(<span class="hljs-string">':id'</span>)
         <span class="hljs-meta">@UseInterceptors</span>(<span class="hljs-keyword">new</span> ClassTransformInterceptor(User))
         getUser(<span class="hljs-meta">@Param</span>(<span class="hljs-string">'id'</span>) id: <span class="hljs-built_in">string</span>) {
           <span class="hljs-comment">// service returns a plain object; interceptor transforms to User instance</span>
           <span class="hljs-keyword">return</span> <span class="hljs-built_in">this</span>.usersService.findOne(+id);
         }
       }
      

    10.2 Logging and Performance Metrics

    Interceptors can also measure execution time or log request/response details. You capture timestamps before and after next.handle(), logging the difference. This helps monitor slow endpoints. Combined with a logging framework or Nest’s Logger, you can standardize logs.

    Here’s how it works:

    1. Timing interceptor
      Logs how long each request-handler takes:

       <span class="hljs-comment">// logging.interceptor.ts</span>
       <span class="hljs-keyword">import</span> {
         Injectable,
         NestInterceptor,
         ExecutionContext,
         CallHandler,
         Logger,
       } <span class="hljs-keyword">from</span> <span class="hljs-string">'@nestjs/common'</span>;
       <span class="hljs-keyword">import</span> { Observable } <span class="hljs-keyword">from</span> <span class="hljs-string">'rxjs'</span>;
       <span class="hljs-keyword">import</span> { tap } <span class="hljs-keyword">from</span> <span class="hljs-string">'rxjs/operators'</span>;
      
       <span class="hljs-meta">@Injectable</span>()
       <span class="hljs-keyword">export</span> <span class="hljs-keyword">class</span> LoggingInterceptor <span class="hljs-keyword">implements</span> NestInterceptor {
         <span class="hljs-keyword">private</span> <span class="hljs-keyword">readonly</span> logger = <span class="hljs-keyword">new</span> Logger(LoggingInterceptor.name);
      
         intercept(context: ExecutionContext, next: CallHandler): Observable<<span class="hljs-built_in">any</span>> {
           <span class="hljs-keyword">const</span> req = context.switchToHttp().getRequest();
           <span class="hljs-keyword">const</span> method = req.method;
           <span class="hljs-keyword">const</span> url = req.url;
           <span class="hljs-keyword">const</span> now = <span class="hljs-built_in">Date</span>.now();
           <span class="hljs-keyword">return</span> next.handle().pipe(
             tap(<span class="hljs-function">() =></span> {
               <span class="hljs-keyword">const</span> elapsed = <span class="hljs-built_in">Date</span>.now() - now;
               <span class="hljs-built_in">this</span>.logger.log(<span class="hljs-string">`<span class="hljs-subst">${method}</span> <span class="hljs-subst">${url}</span> - <span class="hljs-subst">${elapsed}</span>ms`</span>);
             }),
           );
         }
       }
      

      Apply globally:

       <span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">bootstrap</span>(<span class="hljs-params"></span>) </span>{
         <span class="hljs-keyword">const</span> app = <span class="hljs-keyword">await</span> NestFactory.create(AppModule);
         app.useGlobalInterceptors(<span class="hljs-keyword">new</span> LoggingInterceptor());
         <span class="hljs-keyword">await</span> app.listen(<span class="hljs-number">3000</span>);
       }
      

      Now each request logs something like:

       [LoggingInterceptor] GET /users/1 - 35ms
      
    2. Detailed request/response logging
      For more detail, log request body or response size (careful with sensitive data):

       <span class="hljs-comment">// detailed-logging.interceptor.ts</span>
       <span class="hljs-keyword">import</span> {
         Injectable,
         NestInterceptor,
         ExecutionContext,
         CallHandler,
         Logger,
       } <span class="hljs-keyword">from</span> <span class="hljs-string">'@nestjs/common'</span>;
       <span class="hljs-keyword">import</span> { Observable } <span class="hljs-keyword">from</span> <span class="hljs-string">'rxjs'</span>;
       <span class="hljs-keyword">import</span> { tap, map } <span class="hljs-keyword">from</span> <span class="hljs-string">'rxjs/operators'</span>;
      
       <span class="hljs-meta">@Injectable</span>()
       <span class="hljs-keyword">export</span> <span class="hljs-keyword">class</span> DetailedLoggingInterceptor <span class="hljs-keyword">implements</span> NestInterceptor {
         <span class="hljs-keyword">private</span> <span class="hljs-keyword">readonly</span> logger = <span class="hljs-keyword">new</span> Logger(<span class="hljs-string">'HTTP'</span>);
      
         intercept(context: ExecutionContext, next: CallHandler): Observable<<span class="hljs-built_in">any</span>> {
           <span class="hljs-keyword">const</span> ctx = context.switchToHttp();
           <span class="hljs-keyword">const</span> req = ctx.getRequest<Request>();
           <span class="hljs-keyword">const</span> { method, url, body } = req;
           <span class="hljs-keyword">const</span> now = <span class="hljs-built_in">Date</span>.now();
      
           <span class="hljs-built_in">this</span>.logger.log(<span class="hljs-string">`Incoming <span class="hljs-subst">${method}</span> <span class="hljs-subst">${url}</span> - body: <span class="hljs-subst">${<span class="hljs-built_in">JSON</span>.stringify(body)}</span>`</span>);
      
           <span class="hljs-keyword">return</span> next.handle().pipe(
             map(<span class="hljs-function"><span class="hljs-params">data</span> =></span> {
               <span class="hljs-keyword">const</span> elapsed = <span class="hljs-built_in">Date</span>.now() - now;
               <span class="hljs-built_in">this</span>.logger.log(<span class="hljs-string">`Response <span class="hljs-subst">${method}</span> <span class="hljs-subst">${url}</span> - <span class="hljs-subst">${elapsed}</span>ms - data: <span class="hljs-subst">${<span class="hljs-built_in">JSON</span>.stringify(data)}</span>`</span>);
               <span class="hljs-keyword">return</span> data;
             }),
           );
         }
       }
      

      Apply conditionally: perhaps only in development:

       <span class="hljs-keyword">if</span> (process.env.NODE_ENV !== <span class="hljs-string">'production'</span>) {
         app.useGlobalInterceptors(<span class="hljs-keyword">new</span> DetailedLoggingInterceptor());
       }
      
    3. Combining with guards/pipes
      Since interceptors run after guards and before the response is sent, logging time captures the full handler including service calls, but after validation/authorization. That ensures you measure only authorized requests and valid data flows.

    Interceptors offer a flexible way to wrap your handlers with extra behavior: transforming outputs, sanitizing data, timing execution, or adding headers. In the next section, we’ll explore Database integration to see how you can integrate your data layer in Nest.

    11. Database Integration

    In many real-world applications, persisting data is essential. NestJS offers first-class support and integrations for several database technologies. In this section we cover three common approaches:

    • TypeORM with NestJS (relational databases, Active Record/Data Mapper style)

    • Mongoose (MongoDB) (NoSQL document store)

    • Prisma (Type-safe query builder/ORM alternative)

    For each, we’ll explain the theory – when and why to choose it – and show concise practical examples of setup and usage in a NestJS context.

    11.1 TypeORM with NestJS

    TypeORM is a popular ORM for Node.js that supports multiple relational databases (PostgreSQL, MySQL, SQLite, SQL Server, and so on), offering both Active Record and Data Mapper patterns.

    In NestJS, the @nestjs/typeorm package wraps TypeORM to provide:

    • Automatic connection management via TypeOrmModule.forRoot()

    • Module-scoped repositories/entities via TypeOrmModule.forFeature()

    • Dependency injection for repositories and the DataSource/Connection

    • Entity decorators (@Entity(), @Column(), and so on) for schema definition

    • Migrations and advanced features via TypeORM CLI or programmatic usage

    When to choose TypeORM

    Type ORM is useful in several scenarios. Use it when your data is relational and you want a full-featured ORM with decorators and built-in migrations. It’s also great if you prefer to work with classes/entities and automatically map them to tables. And it’s a great choice if you value built-in features like eager/lazy relations, cascading, query builders, and repository patterns.

    Here’s how to use it:

    1. Install dependencies:

       npm install --save @nestjs/typeorm typeorm reflect-metadata
       <span class="hljs-comment"># Also install the database driver; e.g., for Postgres:</span>
       npm install --save pg
      
    2. Configure the root module:

      In app.module.ts, import TypeOrmModule.forRoot() with connection options. These can come from environment variables (discussed later in Configuration Management).

       <span class="hljs-comment">// src/app.module.ts</span>
       <span class="hljs-keyword">import</span> { Module } <span class="hljs-keyword">from</span> <span class="hljs-string">'@nestjs/common'</span>;
       <span class="hljs-keyword">import</span> { TypeOrmModule } <span class="hljs-keyword">from</span> <span class="hljs-string">'@nestjs/typeorm'</span>;
       <span class="hljs-keyword">import</span> { UsersModule } <span class="hljs-keyword">from</span> <span class="hljs-string">'./users/users.module'</span>;
      
       <span class="hljs-meta">@Module</span>({
         imports: [
           TypeOrmModule.forRoot({
             <span class="hljs-keyword">type</span>: <span class="hljs-string">'postgres'</span>,
             host: process.env.DB_HOST || <span class="hljs-string">'localhost'</span>,
             port: +process.env.DB_PORT || <span class="hljs-number">5432</span>,
             username: process.env.DB_USER || <span class="hljs-string">'postgres'</span>,
             password: process.env.DB_PASS || <span class="hljs-string">'password'</span>,
             database: process.env.DB_NAME || <span class="hljs-string">'mydb'</span>,
             entities: [__dirname + <span class="hljs-string">'/**/*.entity{.ts,.js}'</span>],
             synchronize: <span class="hljs-literal">false</span>, <span class="hljs-comment">// recommended false in production; use migrations</span>
             <span class="hljs-comment">// logging: true,</span>
           }),
           UsersModule,
           <span class="hljs-comment">// ...other modules</span>
         ],
       })
       <span class="hljs-keyword">export</span> <span class="hljs-keyword">class</span> AppModule {}
      
      • synchronize: true can auto-sync schema in development, but in production prefer migrations.

      • Entities are auto-loaded via glob. Ensure path matches compiled output.

    3. Define an entity:

      Create an entity class with decorators:

       <span class="hljs-comment">// src/users/user.entity.ts</span>
       <span class="hljs-keyword">import</span> { Entity, PrimaryGeneratedColumn, Column, CreateDateColumn, UpdateDateColumn } <span class="hljs-keyword">from</span> <span class="hljs-string">'typeorm'</span>;
      
       <span class="hljs-meta">@Entity</span>({ name: <span class="hljs-string">'users'</span> })
       <span class="hljs-keyword">export</span> <span class="hljs-keyword">class</span> User {
         <span class="hljs-meta">@PrimaryGeneratedColumn</span>()
         id: <span class="hljs-built_in">number</span>;
      
         <span class="hljs-meta">@Column</span>({ unique: <span class="hljs-literal">true</span> })
         email: <span class="hljs-built_in">string</span>;
      
         <span class="hljs-meta">@Column</span>()
         password: <span class="hljs-built_in">string</span>;
      
         <span class="hljs-meta">@Column</span>({ nullable: <span class="hljs-literal">true</span> })
         name?: <span class="hljs-built_in">string</span>;
      
         <span class="hljs-meta">@CreateDateColumn</span>()
         createdAt: <span class="hljs-built_in">Date</span>;
      
         <span class="hljs-meta">@UpdateDateColumn</span>()
         updatedAt: <span class="hljs-built_in">Date</span>;
       }
      
    4. Set up the feature module:

       <span class="hljs-comment">// src/users/users.module.ts</span>
       <span class="hljs-keyword">import</span> { Module } <span class="hljs-keyword">from</span> <span class="hljs-string">'@nestjs/common'</span>;
       <span class="hljs-keyword">import</span> { TypeOrmModule } <span class="hljs-keyword">from</span> <span class="hljs-string">'@nestjs/typeorm'</span>;
       <span class="hljs-keyword">import</span> { UsersService } <span class="hljs-keyword">from</span> <span class="hljs-string">'./users.service'</span>;
       <span class="hljs-keyword">import</span> { UsersController } <span class="hljs-keyword">from</span> <span class="hljs-string">'./users.controller'</span>;
       <span class="hljs-keyword">import</span> { User } <span class="hljs-keyword">from</span> <span class="hljs-string">'./user.entity'</span>;
      
       <span class="hljs-meta">@Module</span>({
         imports: [TypeOrmModule.forFeature([User])],
         providers: [UsersService],
         controllers: [UsersController],
         <span class="hljs-built_in">exports</span>: [UsersService], <span class="hljs-comment">// if other modules need UsersService</span>
       })
       <span class="hljs-keyword">export</span> <span class="hljs-keyword">class</span> UsersModule {}
      
    5. Inject the repository:

      In the service, inject the Repository<User>:

       <span class="hljs-comment">// src/users/users.service.ts</span>
       <span class="hljs-keyword">import</span> { Injectable, NotFoundException } <span class="hljs-keyword">from</span> <span class="hljs-string">'@nestjs/common'</span>;
       <span class="hljs-keyword">import</span> { InjectRepository } <span class="hljs-keyword">from</span> <span class="hljs-string">'@nestjs/typeorm'</span>;
       <span class="hljs-keyword">import</span> { Repository } <span class="hljs-keyword">from</span> <span class="hljs-string">'typeorm'</span>;
       <span class="hljs-keyword">import</span> { User } <span class="hljs-keyword">from</span> <span class="hljs-string">'./user.entity'</span>;
       <span class="hljs-keyword">import</span> { CreateUserDto } <span class="hljs-keyword">from</span> <span class="hljs-string">'./dto/create-user.dto'</span>;
      
       <span class="hljs-meta">@Injectable</span>()
       <span class="hljs-keyword">export</span> <span class="hljs-keyword">class</span> UsersService {
         <span class="hljs-keyword">constructor</span>(<span class="hljs-params">
           <span class="hljs-meta">@InjectRepository</span>(User)
           <span class="hljs-keyword">private</span> <span class="hljs-keyword">readonly</span> userRepository: Repository<User>,
         </span>) {}
      
         <span class="hljs-keyword">async</span> create(dto: CreateUserDto): <span class="hljs-built_in">Promise</span><User> {
           <span class="hljs-keyword">const</span> user = <span class="hljs-built_in">this</span>.userRepository.create(dto); <span class="hljs-comment">// maps DTO fields to entity</span>
           <span class="hljs-keyword">return</span> <span class="hljs-built_in">this</span>.userRepository.save(user);
         }
      
         <span class="hljs-keyword">async</span> findAll(): <span class="hljs-built_in">Promise</span><User[]> {
           <span class="hljs-keyword">return</span> <span class="hljs-built_in">this</span>.userRepository.find();
         }
      
         <span class="hljs-keyword">async</span> findOne(id: <span class="hljs-built_in">number</span>): <span class="hljs-built_in">Promise</span><User> {
           <span class="hljs-keyword">const</span> user = <span class="hljs-keyword">await</span> <span class="hljs-built_in">this</span>.userRepository.findOne({ where: { id } });
           <span class="hljs-keyword">if</span> (!user) {
             <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> NotFoundException(<span class="hljs-string">`User #<span class="hljs-subst">${id}</span> not found`</span>);
           }
           <span class="hljs-keyword">return</span> user;
         }
      
         <span class="hljs-keyword">async</span> update(id: <span class="hljs-built_in">number</span>, dto: Partial<CreateUserDto>): <span class="hljs-built_in">Promise</span><User> {
           <span class="hljs-keyword">const</span> user = <span class="hljs-keyword">await</span> <span class="hljs-built_in">this</span>.findOne(id);
           <span class="hljs-built_in">Object</span>.assign(user, dto);
           <span class="hljs-keyword">return</span> <span class="hljs-built_in">this</span>.userRepository.save(user);
         }
      
         <span class="hljs-keyword">async</span> remove(id: <span class="hljs-built_in">number</span>): <span class="hljs-built_in">Promise</span><<span class="hljs-built_in">void</span>> {
           <span class="hljs-keyword">await</span> <span class="hljs-built_in">this</span>.userRepository.delete(id);
         }
       }
      
    6. Use in controller:

       <span class="hljs-comment">// src/users/users.controller.ts</span>
       <span class="hljs-keyword">import</span> { Controller, Get, Post, Body, Param, ParseIntPipe, Put, Delete } <span class="hljs-keyword">from</span> <span class="hljs-string">'@nestjs/common'</span>;
       <span class="hljs-keyword">import</span> { UsersService } <span class="hljs-keyword">from</span> <span class="hljs-string">'./users.service'</span>;
       <span class="hljs-keyword">import</span> { CreateUserDto } <span class="hljs-keyword">from</span> <span class="hljs-string">'./dto/create-user.dto'</span>;
      
       <span class="hljs-meta">@Controller</span>(<span class="hljs-string">'users'</span>)
       <span class="hljs-keyword">export</span> <span class="hljs-keyword">class</span> UsersController {
         <span class="hljs-keyword">constructor</span>(<span class="hljs-params"><span class="hljs-keyword">private</span> <span class="hljs-keyword">readonly</span> usersService: UsersService</span>) {}
      
         <span class="hljs-meta">@Post</span>()
         create(<span class="hljs-meta">@Body</span>() dto: CreateUserDto) {
           <span class="hljs-keyword">return</span> <span class="hljs-built_in">this</span>.usersService.create(dto);
         }
      
         <span class="hljs-meta">@Get</span>()
         findAll() {
           <span class="hljs-keyword">return</span> <span class="hljs-built_in">this</span>.usersService.findAll();
         }
      
         <span class="hljs-meta">@Get</span>(<span class="hljs-string">':id'</span>)
         findOne(<span class="hljs-meta">@Param</span>(<span class="hljs-string">'id'</span>, ParseIntPipe) id: <span class="hljs-built_in">number</span>) {
           <span class="hljs-keyword">return</span> <span class="hljs-built_in">this</span>.usersService.findOne(id);
         }
      
         <span class="hljs-meta">@Put</span>(<span class="hljs-string">':id'</span>)
         update(
           <span class="hljs-meta">@Param</span>(<span class="hljs-string">'id'</span>, ParseIntPipe) id: <span class="hljs-built_in">number</span>,
           <span class="hljs-meta">@Body</span>() dto: Partial<CreateUserDto>,
         ) {
           <span class="hljs-keyword">return</span> <span class="hljs-built_in">this</span>.usersService.update(id, dto);
         }
      
         <span class="hljs-meta">@Delete</span>(<span class="hljs-string">':id'</span>)
         remove(<span class="hljs-meta">@Param</span>(<span class="hljs-string">'id'</span>, ParseIntPipe) id: <span class="hljs-built_in">number</span>) {
           <span class="hljs-keyword">return</span> <span class="hljs-built_in">this</span>.usersService.remove(id);
         }
       }
      
    7. Migrations (optional but recommended)

      • Use TypeORM CLI or programmatic migrations.

      • Configure a separate ormconfig or supply options in code.

      • Generate and run migrations to evolve schema without data loss.

    11.2 Mongoose (MongoDB)

    Mongoose is a widely used ODM (Object Document Mapper) for MongoDB. In NestJS, @nestjs/mongoose integrates Mongoose to:

    • Define schemas via classes and decorators (@Schema(), @Prop())

    • Register models in modules with MongooseModule.forFeature()

    • Manage the MongoDB connection with MongooseModule.forRoot()

    • Inject Mongoose Model instances into services

    • Work with documents in a type-safe way (with interfaces/types)

    • Leverage features like hooks, virtuals, and validation at schema level

    When to choose Mongoose

    Mongoose is a good choice if you need a document-oriented, schema-less/ schematized NoSQL store. It’s also great if your data shapes may vary, or you prefer MongoDB’s flexible schema. And it’s helpful if you want features like middleware hooks in schema (pre/post save), virtuals, and so on.

    Here’s how to use it:

    1. Install dependencies:

       npm install --save @nestjs/mongoose mongoose
      
    2. Configure root module:

       <span class="hljs-comment">// src/app.module.ts</span>
       <span class="hljs-keyword">import</span> { Module } <span class="hljs-keyword">from</span> <span class="hljs-string">'@nestjs/common'</span>;
       <span class="hljs-keyword">import</span> { MongooseModule } <span class="hljs-keyword">from</span> <span class="hljs-string">'@nestjs/mongoose'</span>;
       <span class="hljs-keyword">import</span> { CatsModule } <span class="hljs-keyword">from</span> <span class="hljs-string">'./cats/cats.module'</span>;
      
       <span class="hljs-meta">@Module</span>({
         imports: [
           MongooseModule.forRoot(process.env.MONGO_URI || <span class="hljs-string">'mongodb://localhost/nest'</span>),
           CatsModule,
           <span class="hljs-comment">// ...other modules</span>
         ],
       })
       <span class="hljs-keyword">export</span> <span class="hljs-keyword">class</span> AppModule {}
      
    3. Define a schema and document:

      Use decorators and interfaces:

       <span class="hljs-comment">// src/cats/schemas/cat.schema.ts</span>
       <span class="hljs-keyword">import</span> { Prop, Schema, SchemaFactory } <span class="hljs-keyword">from</span> <span class="hljs-string">'@nestjs/mongoose'</span>;
       <span class="hljs-keyword">import</span> { Document } <span class="hljs-keyword">from</span> <span class="hljs-string">'mongoose'</span>;
      
       <span class="hljs-meta">@Schema</span>({ timestamps: <span class="hljs-literal">true</span> })
       <span class="hljs-keyword">export</span> <span class="hljs-keyword">class</span> Cat <span class="hljs-keyword">extends</span> Document {
         <span class="hljs-meta">@Prop</span>({ required: <span class="hljs-literal">true</span> })
         name: <span class="hljs-built_in">string</span>;
      
         <span class="hljs-meta">@Prop</span>()
         age: <span class="hljs-built_in">number</span>;
      
         <span class="hljs-meta">@Prop</span>()
         breed: <span class="hljs-built_in">string</span>;
       }
      
       <span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> CatSchema = SchemaFactory.createForClass(Cat);
      
      • Extending Document gives the Mongoose document methods and properties.

      • timestamps: true auto-adds createdAt and updatedAt.

      • You can add hooks:

          CatSchema.pre<Cat>(<span class="hljs-string">'save'</span>, <span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params">next</span>) </span>{
            <span class="hljs-comment">// e.g., modify data or log before saving</span>
            next();
          });
        
    4. Set up feature module:

       <span class="hljs-comment">// src/cats/cats.module.ts</span>
       <span class="hljs-keyword">import</span> { Module } <span class="hljs-keyword">from</span> <span class="hljs-string">'@nestjs/common'</span>;
       <span class="hljs-keyword">import</span> { MongooseModule } <span class="hljs-keyword">from</span> <span class="hljs-string">'@nestjs/mongoose'</span>;
       <span class="hljs-keyword">import</span> { CatsService } <span class="hljs-keyword">from</span> <span class="hljs-string">'./cats.service'</span>;
       <span class="hljs-keyword">import</span> { CatsController } <span class="hljs-keyword">from</span> <span class="hljs-string">'./cats.controller'</span>;
       <span class="hljs-keyword">import</span> { Cat, CatSchema } <span class="hljs-keyword">from</span> <span class="hljs-string">'./schemas/cat.schema'</span>;
      
       <span class="hljs-meta">@Module</span>({
         imports: [
           MongooseModule.forFeature([{ name: Cat.name, schema: CatSchema }]),
         ],
         controllers: [CatsController],
         providers: [CatsService],
       })
       <span class="hljs-keyword">export</span> <span class="hljs-keyword">class</span> CatsModule {}
      
    5. Inject the model:

      In the service, inject Model<Cat>:

       <span class="hljs-comment">// src/cats/cats.service.ts</span>
       <span class="hljs-keyword">import</span> { Injectable, NotFoundException } <span class="hljs-keyword">from</span> <span class="hljs-string">'@nestjs/common'</span>;
       <span class="hljs-keyword">import</span> { InjectModel } <span class="hljs-keyword">from</span> <span class="hljs-string">'@nestjs/mongoose'</span>;
       <span class="hljs-keyword">import</span> { Model } <span class="hljs-keyword">from</span> <span class="hljs-string">'mongoose'</span>;
       <span class="hljs-keyword">import</span> { Cat } <span class="hljs-keyword">from</span> <span class="hljs-string">'./schemas/cat.schema'</span>;
       <span class="hljs-keyword">import</span> { CreateCatDto } <span class="hljs-keyword">from</span> <span class="hljs-string">'./dto/create-cat.dto'</span>;
       <span class="hljs-keyword">import</span> { UpdateCatDto } <span class="hljs-keyword">from</span> <span class="hljs-string">'./dto/update-cat.dto'</span>;
      
       <span class="hljs-meta">@Injectable</span>()
       <span class="hljs-keyword">export</span> <span class="hljs-keyword">class</span> CatsService {
         <span class="hljs-keyword">constructor</span>(<span class="hljs-params">
           <span class="hljs-meta">@InjectModel</span>(Cat.name) <span class="hljs-keyword">private</span> <span class="hljs-keyword">readonly</span> catModel: Model<Cat>,
         </span>) {}
      
         <span class="hljs-keyword">async</span> create(dto: CreateCatDto): <span class="hljs-built_in">Promise</span><Cat> {
           <span class="hljs-keyword">const</span> created = <span class="hljs-keyword">new</span> <span class="hljs-built_in">this</span>.catModel(dto);
           <span class="hljs-keyword">return</span> created.save();
         }
      
         <span class="hljs-keyword">async</span> findAll(): <span class="hljs-built_in">Promise</span><Cat[]> {
           <span class="hljs-keyword">return</span> <span class="hljs-built_in">this</span>.catModel.find().exec();
         }
      
         <span class="hljs-keyword">async</span> findOne(id: <span class="hljs-built_in">string</span>): <span class="hljs-built_in">Promise</span><Cat> {
           <span class="hljs-keyword">const</span> cat = <span class="hljs-keyword">await</span> <span class="hljs-built_in">this</span>.catModel.findById(id).exec();
           <span class="hljs-keyword">if</span> (!cat) {
             <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> NotFoundException(<span class="hljs-string">`Cat <span class="hljs-subst">${id}</span> not found`</span>);
           }
           <span class="hljs-keyword">return</span> cat;
         }
      
         <span class="hljs-keyword">async</span> update(id: <span class="hljs-built_in">string</span>, dto: UpdateCatDto): <span class="hljs-built_in">Promise</span><Cat> {
           <span class="hljs-keyword">const</span> updated = <span class="hljs-keyword">await</span> <span class="hljs-built_in">this</span>.catModel
             .findByIdAndUpdate(id, dto, { <span class="hljs-keyword">new</span>: <span class="hljs-literal">true</span> })
             .exec();
           <span class="hljs-keyword">if</span> (!updated) {
             <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> NotFoundException(<span class="hljs-string">`Cat <span class="hljs-subst">${id}</span> not found`</span>);
           }
           <span class="hljs-keyword">return</span> updated;
         }
      
         <span class="hljs-keyword">async</span> remove(id: <span class="hljs-built_in">string</span>): <span class="hljs-built_in">Promise</span><<span class="hljs-built_in">void</span>> {
           <span class="hljs-keyword">const</span> res = <span class="hljs-keyword">await</span> <span class="hljs-built_in">this</span>.catModel.findByIdAndDelete(id).exec();
           <span class="hljs-keyword">if</span> (!res) {
             <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> NotFoundException(<span class="hljs-string">`Cat <span class="hljs-subst">${id}</span> not found`</span>);
           }
         }
       }
      
    6. Use in controller:

       <span class="hljs-comment">// src/cats/cats.controller.ts</span>
       <span class="hljs-keyword">import</span> { Controller, Get, Post, Body, Param, Put, Delete } <span class="hljs-keyword">from</span> <span class="hljs-string">'@nestjs/common'</span>;
       <span class="hljs-keyword">import</span> { CatsService } <span class="hljs-keyword">from</span> <span class="hljs-string">'./cats.service'</span>;
       <span class="hljs-keyword">import</span> { CreateCatDto } <span class="hljs-keyword">from</span> <span class="hljs-string">'./dto/create-cat.dto'</span>;
       <span class="hljs-keyword">import</span> { UpdateCatDto } <span class="hljs-keyword">from</span> <span class="hljs-string">'./dto/update-cat.dto'</span>;
      
       <span class="hljs-meta">@Controller</span>(<span class="hljs-string">'cats'</span>)
       <span class="hljs-keyword">export</span> <span class="hljs-keyword">class</span> CatsController {
         <span class="hljs-keyword">constructor</span>(<span class="hljs-params"><span class="hljs-keyword">private</span> <span class="hljs-keyword">readonly</span> catsService: CatsService</span>) {}
      
         <span class="hljs-meta">@Post</span>()
         create(<span class="hljs-meta">@Body</span>() dto: CreateCatDto) {
           <span class="hljs-keyword">return</span> <span class="hljs-built_in">this</span>.catsService.create(dto);
         }
      
         <span class="hljs-meta">@Get</span>()
         findAll() {
           <span class="hljs-keyword">return</span> <span class="hljs-built_in">this</span>.catsService.findAll();
         }
      
         <span class="hljs-meta">@Get</span>(<span class="hljs-string">':id'</span>)
         findOne(<span class="hljs-meta">@Param</span>(<span class="hljs-string">'id'</span>) id: <span class="hljs-built_in">string</span>) {
           <span class="hljs-keyword">return</span> <span class="hljs-built_in">this</span>.catsService.findOne(id);
         }
      
         <span class="hljs-meta">@Put</span>(<span class="hljs-string">':id'</span>)
         update(
           <span class="hljs-meta">@Param</span>(<span class="hljs-string">'id'</span>) id: <span class="hljs-built_in">string</span>,
           <span class="hljs-meta">@Body</span>() dto: UpdateCatDto,
         ) {
           <span class="hljs-keyword">return</span> <span class="hljs-built_in">this</span>.catsService.update(id, dto);
         }
      
         <span class="hljs-meta">@Delete</span>(<span class="hljs-string">':id'</span>)
         remove(<span class="hljs-meta">@Param</span>(<span class="hljs-string">'id'</span>) id: <span class="hljs-built_in">string</span>) {
           <span class="hljs-keyword">return</span> <span class="hljs-built_in">this</span>.catsService.remove(id);
         }
       }
      
    7. Advanced Mongoose features

      • Virtuals: define computed properties not stored in DB.

      • Indexes: via schema options or @Prop({ index: true }).

      • Populate: reference other collections with @Prop({ type: Types.ObjectId, ref: 'OtherModel' }).

      • Transactions: use MongoDB sessions for multi-document atomic operations.

    11.3 Prisma

    Prisma is a modern ORM/Query Builder that generates a type-safe client based on a schema definition. It supports relational databases (PostgreSQL, MySQL, SQLite, SQL Server, and more).

    Here are some of its key features:

    • Type-safe queries: Autogenerated TypeScript definitions prevent many runtime errors.

    • Prisma schema: A declarative .prisma file to define models, relations, and enums.

    • Migrations: prisma migrate for evolving schema.

    • Performance: Lean query builder without heavy runtime overhead.

    • Flexibility: Supports raw queries when needed.

    When to choose Prisma

    Prisma is a great choice if you prefer a schema-first approach with a clear DSL and auto-generated type-safe client. It’s also great if you want modern features like efficient migrations, rich type inference, and a straightforward developer experience. And it’s a solid choice if you don’t need Active Record pattern. Instead, you use the Prisma client in services.

    Here’s how it works:

    1. Install dependencies and initialize:

       npm install @prisma/client
       npm install -D prisma
       npx prisma init
      

      This creates a prisma/schema.prisma file and a .env with DATABASE_URL.

    2. Define the schema:

      In prisma/schema.prisma:

       datasource db {
         provider = <span class="hljs-string">"postgresql"</span>
         url      = env(<span class="hljs-string">"DATABASE_URL"</span>)
       }
      
       generator client {
         provider = <span class="hljs-string">"prisma-client-js"</span>
       }
      
       model User {
         id        Int      @id @default(autoincrement())
         email     String   @unique
         name      String?
         posts     Post[]
         createdAt DateTime @default(now())
         updatedAt DateTime @updatedAt
       }
      
       model Post {
         id        Int      @id @default(autoincrement())
         title     String
         content   String?
         author    User     @relation(fields: [authorId], references: [id])
         authorId  Int
         published Boolean  @default(<span class="hljs-literal">false</span>)
         createdAt DateTime @default(now())
         updatedAt DateTime @updatedAt
       }
      
    3. Run migrations and generate client:

       npx prisma migrate dev --name init
       npx prisma generate
      

      This updates the database schema and regenerates the TypeScript client.

    4. Create a PrismaService in NestJS:

      A common pattern is to wrap the PrismaClient in an injectable service, handling lifecycle hooks.

       <span class="hljs-comment">// src/prisma/prisma.service.ts</span>
       <span class="hljs-keyword">import</span> { Injectable, OnModuleInit, OnModuleDestroy } <span class="hljs-keyword">from</span> <span class="hljs-string">'@nestjs/common'</span>;
       <span class="hljs-keyword">import</span> { PrismaClient } <span class="hljs-keyword">from</span> <span class="hljs-string">'@prisma/client'</span>;
      
       <span class="hljs-meta">@Injectable</span>()
       <span class="hljs-keyword">export</span> <span class="hljs-keyword">class</span> PrismaService <span class="hljs-keyword">extends</span> PrismaClient <span class="hljs-keyword">implements</span> OnModuleInit, OnModuleDestroy {
         <span class="hljs-keyword">async</span> onModuleInit() {
           <span class="hljs-keyword">await</span> <span class="hljs-built_in">this</span>.$connect();
         }
      
         <span class="hljs-keyword">async</span> onModuleDestroy() {
           <span class="hljs-keyword">await</span> <span class="hljs-built_in">this</span>.$disconnect();
         }
       }
      
    5. Register PrismaService in a module:

       <span class="hljs-comment">// src/prisma/prisma.module.ts</span>
       <span class="hljs-keyword">import</span> { Module } <span class="hljs-keyword">from</span> <span class="hljs-string">'@nestjs/common'</span>;
       <span class="hljs-keyword">import</span> { PrismaService } <span class="hljs-keyword">from</span> <span class="hljs-string">'./prisma.service'</span>;
      
       <span class="hljs-meta">@Module</span>({
         providers: [PrismaService],
         <span class="hljs-built_in">exports</span>: [PrismaService],
       })
       <span class="hljs-keyword">export</span> <span class="hljs-keyword">class</span> PrismaModule {}
      

      Then import PrismaModule in any feature module needing DB access.

    6. Use in a feature service:

       <span class="hljs-comment">// src/users/users.service.ts</span>
       <span class="hljs-keyword">import</span> { Injectable } <span class="hljs-keyword">from</span> <span class="hljs-string">'@nestjs/common'</span>;
       <span class="hljs-keyword">import</span> { PrismaService } <span class="hljs-keyword">from</span> <span class="hljs-string">'../prisma/prisma.service'</span>;
       <span class="hljs-keyword">import</span> { CreateUserDto } <span class="hljs-keyword">from</span> <span class="hljs-string">'./dto/create-user.dto'</span>;
      
       <span class="hljs-meta">@Injectable</span>()
       <span class="hljs-keyword">export</span> <span class="hljs-keyword">class</span> UsersService {
         <span class="hljs-keyword">constructor</span>(<span class="hljs-params"><span class="hljs-keyword">private</span> <span class="hljs-keyword">readonly</span> prisma: PrismaService</span>) {}
      
         <span class="hljs-keyword">async</span> create(dto: CreateUserDto) {
           <span class="hljs-keyword">return</span> <span class="hljs-built_in">this</span>.prisma.user.create({ data: dto });
         }
      
         <span class="hljs-keyword">async</span> findAll() {
           <span class="hljs-keyword">return</span> <span class="hljs-built_in">this</span>.prisma.user.findMany();
         }
      
         <span class="hljs-keyword">async</span> findOne(id: <span class="hljs-built_in">number</span>) {
           <span class="hljs-keyword">return</span> <span class="hljs-built_in">this</span>.prisma.user.findUnique({ where: { id } });
         }
      
         <span class="hljs-keyword">async</span> update(id: <span class="hljs-built_in">number</span>, dto: Partial<CreateUserDto>) {
           <span class="hljs-keyword">return</span> <span class="hljs-built_in">this</span>.prisma.user.update({
             where: { id },
             data: dto,
           });
         }
      
         <span class="hljs-keyword">async</span> remove(id: <span class="hljs-built_in">number</span>) {
           <span class="hljs-keyword">await</span> <span class="hljs-built_in">this</span>.prisma.user.delete({ where: { id } });
           <span class="hljs-keyword">return</span> { deleted: <span class="hljs-literal">true</span> };
         }
       }
      

      Note: DTO fields must align with Prisma schema types. Prisma client methods return typed results.

    7. Inject in controller:

       <span class="hljs-comment">// src/users/users.controller.ts</span>
       <span class="hljs-keyword">import</span> { Controller, Get, Post, Body, Param, ParseIntPipe, Put, Delete } <span class="hljs-keyword">from</span> <span class="hljs-string">'@nestjs/common'</span>;
       <span class="hljs-keyword">import</span> { UsersService } <span class="hljs-keyword">from</span> <span class="hljs-string">'./users.service'</span>;
       <span class="hljs-keyword">import</span> { CreateUserDto } <span class="hljs-keyword">from</span> <span class="hljs-string">'./dto/create-user.dto'</span>;
      
       <span class="hljs-meta">@Controller</span>(<span class="hljs-string">'users'</span>)
       <span class="hljs-keyword">export</span> <span class="hljs-keyword">class</span> UsersController {
         <span class="hljs-keyword">constructor</span>(<span class="hljs-params"><span class="hljs-keyword">private</span> <span class="hljs-keyword">readonly</span> usersService: UsersService</span>) {}
      
         <span class="hljs-meta">@Post</span>()
         create(<span class="hljs-meta">@Body</span>() dto: CreateUserDto) {
           <span class="hljs-keyword">return</span> <span class="hljs-built_in">this</span>.usersService.create(dto);
         }
      
         <span class="hljs-meta">@Get</span>()
         findAll() {
           <span class="hljs-keyword">return</span> <span class="hljs-built_in">this</span>.usersService.findAll();
         }
      
         <span class="hljs-meta">@Get</span>(<span class="hljs-string">':id'</span>)
         findOne(<span class="hljs-meta">@Param</span>(<span class="hljs-string">'id'</span>, ParseIntPipe) id: <span class="hljs-built_in">number</span>) {
           <span class="hljs-keyword">return</span> <span class="hljs-built_in">this</span>.usersService.findOne(id);
         }
      
         <span class="hljs-meta">@Put</span>(<span class="hljs-string">':id'</span>)
         update(
           <span class="hljs-meta">@Param</span>(<span class="hljs-string">'id'</span>, ParseIntPipe) id: <span class="hljs-built_in">number</span>,
           <span class="hljs-meta">@Body</span>() dto: Partial<CreateUserDto>,
         ) {
           <span class="hljs-keyword">return</span> <span class="hljs-built_in">this</span>.usersService.update(id, dto);
         }
      
         <span class="hljs-meta">@Delete</span>(<span class="hljs-string">':id'</span>)
         remove(<span class="hljs-meta">@Param</span>(<span class="hljs-string">'id'</span>, ParseIntPipe) id: <span class="hljs-built_in">number</span>) {
           <span class="hljs-keyword">return</span> <span class="hljs-built_in">this</span>.usersService.remove(id);
         }
       }
      
    8. Advanced Prisma usage

      • Relations and nested writes: for example, create a post with nested author connect/create.

      • Transactions: this.prisma.$transaction([...]) for atomic operations.

      • Raw queries: this.prisma.$queryRaw when needed.

      • Middleware: Prisma supports middlewares on the client side.

      • Performance tuning: select only needed fields, use pagination patterns.

    With these three approaches, you can choose the database integration strategy that best fits your application’s needs:

    • TypeORM for a full-fledged ORM with decorators and migrations support in relational databases.

    • Mongoose for flexible document schemas in MongoDB.

    • Prisma for a modern, type-safe query builder/ORM alternative with excellent developer ergonomics.

    In the next section, we’ll cover Configuration Management – how to handle environment variables and config modules in NestJS.

    12. Configuration Management

    Managing configuration cleanly is crucial for applications to behave correctly across environments (development, staging, production). NestJS provides the @nestjs/config module to centralize configuration loading, validation, and injection.

    12.1 @nestjs/config Module

    The @nestjs/config module is a powerful utility for managing application configuration settings. Here are some of its key features:

    • Centralized config: Instead of sprinkling process.env throughout your code, it uses a dedicated service that loads and validates configuration once at startup.

    • Environment agnostic: It loads variables from .env files, environment variables, or other sources, with support for different files per environment.

    • Validation: It integrates a schema (for example, via Joi) to ensure required variables are present and correctly typed, failing fast if misconfigured.

    • Config Namespacing: It organizes related settings into logical groups (for example, database, auth, third-party APIs) via configuration factories.

    • Injection: It injects a ConfigService to read config values in services or modules, with type safety when using custom typed wrappers.

    Here’s how it works:

    1. Install the package

       npm install @nestjs/config
       npm install joi    <span class="hljs-comment"># if you plan to validate via Joi schemas</span>
      
    2. Import and initialize ConfigModule

      In your root module (AppModule), import ConfigModule.forRoot(). Typical options:

       <span class="hljs-comment">// src/app.module.ts</span>
       <span class="hljs-keyword">import</span> { Module } <span class="hljs-keyword">from</span> <span class="hljs-string">'@nestjs/common'</span>;
       <span class="hljs-keyword">import</span> { ConfigModule } <span class="hljs-keyword">from</span> <span class="hljs-string">'@nestjs/config'</span>;
       <span class="hljs-keyword">import</span> configuration <span class="hljs-keyword">from</span> <span class="hljs-string">'./config/configuration'</span>;
       <span class="hljs-keyword">import</span> { validationSchema } <span class="hljs-keyword">from</span> <span class="hljs-string">'./config/validation'</span>;
      
       <span class="hljs-meta">@Module</span>({
         imports: [
           ConfigModule.forRoot({
             <span class="hljs-comment">// Load .env automatically; specify envFilePath if custom:</span>
             isGlobal: <span class="hljs-literal">true</span>,           <span class="hljs-comment">// makes ConfigService available app-wide</span>
             envFilePath: [<span class="hljs-string">'.env.development.local'</span>, <span class="hljs-string">'.env.development'</span>, <span class="hljs-string">'.env'</span>], 
             load: [configuration],    <span class="hljs-comment">// optional: load custom config factory(s)</span>
             validationSchema,         <span class="hljs-comment">// optional: Joi schema to validate env vars</span>
             validationOptions: {
               allowUnknown: <span class="hljs-literal">true</span>,
               abortEarly: <span class="hljs-literal">true</span>,
             },
           }),
           <span class="hljs-comment">// ...other modules</span>
         ],
       })
       <span class="hljs-keyword">export</span> <span class="hljs-keyword">class</span> AppModule {}
      
      • isGlobal: true avoids importing ConfigModule in every feature module.

      • envFilePath: an array lets you try multiple files (for example, local overrides before default).

      • load: array of functions returning partial config objects – see next step.

      • validationSchema: a Joi schema ensuring required variables exist and are correct type/format.

    3. Define a configuration factory

      Organize related settings into a typed object:

       <span class="hljs-comment">// src/config/configuration.ts</span>
       <span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> () => ({
         port: <span class="hljs-built_in">parseInt</span>(process.env.PORT, <span class="hljs-number">10</span>) || <span class="hljs-number">3000</span>,
         database: {
           host: process.env.DB_HOST,
           port: <span class="hljs-built_in">parseInt</span>(process.env.DB_PORT, <span class="hljs-number">10</span>) || <span class="hljs-number">5432</span>,
           user: process.env.DB_USER,
           pass: process.env.DB_PASS,
           name: process.env.DB_NAME,
         },
         jwt: {
           secret: process.env.JWT_SECRET,
           expiresIn: process.env.JWT_EXPIRES_IN || <span class="hljs-string">'1h'</span>,
         },
         <span class="hljs-comment">// add other namespaces as needed</span>
       });
      
    4. Validate environment variables

      Using Joi for validation:

       <span class="hljs-comment">// src/config/validation.ts</span>
       <span class="hljs-keyword">import</span> * <span class="hljs-keyword">as</span> Joi <span class="hljs-keyword">from</span> <span class="hljs-string">'joi'</span>;
      
       <span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> validationSchema = Joi.object({
         NODE_ENV: Joi.string()
           .valid(<span class="hljs-string">'development'</span>, <span class="hljs-string">'production'</span>, <span class="hljs-string">'test'</span>, <span class="hljs-string">'staging'</span>)
           .default(<span class="hljs-string">'development'</span>),
         PORT: Joi.number().default(<span class="hljs-number">3000</span>),
         DB_HOST: Joi.string().required(),
         DB_PORT: Joi.number().default(<span class="hljs-number">5432</span>),
         DB_USER: Joi.string().required(),
         DB_PASS: Joi.string().required(),
         DB_NAME: Joi.string().required(),
         JWT_SECRET: Joi.string().min(<span class="hljs-number">32</span>).required(),
         JWT_EXPIRES_IN: Joi.string().default(<span class="hljs-string">'1h'</span>),
         <span class="hljs-comment">// add other variables...</span>
       });
      

      If validation fails at startup, the application will error out with details, preventing misconfigured deployments.

    5. Inject ConfigService

      Anywhere you need config, inject ConfigService:

       <span class="hljs-comment">// src/some/some.service.ts</span>
       <span class="hljs-keyword">import</span> { Injectable } <span class="hljs-keyword">from</span> <span class="hljs-string">'@nestjs/common'</span>;
       <span class="hljs-keyword">import</span> { ConfigService } <span class="hljs-keyword">from</span> <span class="hljs-string">'@nestjs/config'</span>;
      
       <span class="hljs-meta">@Injectable</span>()
       <span class="hljs-keyword">export</span> <span class="hljs-keyword">class</span> SomeService {
         <span class="hljs-keyword">constructor</span>(<span class="hljs-params"><span class="hljs-keyword">private</span> <span class="hljs-keyword">readonly</span> configService: ConfigService</span>) {}
      
         getDbConfig() {
           <span class="hljs-keyword">const</span> host = <span class="hljs-built_in">this</span>.configService.get<<span class="hljs-built_in">string</span>>(<span class="hljs-string">'database.host'</span>);
           <span class="hljs-keyword">const</span> port = <span class="hljs-built_in">this</span>.configService.get<<span class="hljs-built_in">number</span>>(<span class="hljs-string">'database.port'</span>);
           <span class="hljs-comment">// Use these values to configure a database client, etc.</span>
           <span class="hljs-keyword">return</span> { host, port };
         }
       }
      
      • Use dot notation for nested config: for example, 'jwt.secret'.

      • You can also read raw env vars via configService.get<string>('DB_HOST') if needed, but preferring structured config is clearer.

    6. Typed wrapper for ConfigService (optional)

      For stronger typing, create an interface matching your configuration and a wrapper:

       <span class="hljs-comment">// src/config/config.interface.ts</span>
       <span class="hljs-keyword">export</span> <span class="hljs-keyword">interface</span> AppConfig {
         port: <span class="hljs-built_in">number</span>;
         database: {
           host: <span class="hljs-built_in">string</span>;
           port: <span class="hljs-built_in">number</span>;
           user: <span class="hljs-built_in">string</span>;
           pass: <span class="hljs-built_in">string</span>;
           name: <span class="hljs-built_in">string</span>;
         };
         jwt: {
           secret: <span class="hljs-built_in">string</span>;
           expiresIn: <span class="hljs-built_in">string</span>;
         };
       }
      
       <span class="hljs-comment">// src/config/typed-config.service.ts</span>
       <span class="hljs-keyword">import</span> { Injectable } <span class="hljs-keyword">from</span> <span class="hljs-string">'@nestjs/common'</span>;
       <span class="hljs-keyword">import</span> { ConfigService } <span class="hljs-keyword">from</span> <span class="hljs-string">'@nestjs/config'</span>;
       <span class="hljs-keyword">import</span> { AppConfig } <span class="hljs-keyword">from</span> <span class="hljs-string">'./config.interface'</span>;
      
       <span class="hljs-meta">@Injectable</span>()
       <span class="hljs-keyword">export</span> <span class="hljs-keyword">class</span> TypedConfigService {
         <span class="hljs-keyword">constructor</span>(<span class="hljs-params"><span class="hljs-keyword">private</span> <span class="hljs-keyword">readonly</span> configService: ConfigService</span>) {}
      
         get appConfig(): AppConfig {
           <span class="hljs-keyword">return</span> {
             port: <span class="hljs-built_in">this</span>.configService.get<<span class="hljs-built_in">number</span>>(<span class="hljs-string">'port'</span>),
             database: {
               host: <span class="hljs-built_in">this</span>.configService.get<<span class="hljs-built_in">string</span>>(<span class="hljs-string">'database.host'</span>),
               port: <span class="hljs-built_in">this</span>.configService.get<<span class="hljs-built_in">number</span>>(<span class="hljs-string">'database.port'</span>),
               user: <span class="hljs-built_in">this</span>.configService.get<<span class="hljs-built_in">string</span>>(<span class="hljs-string">'database.user'</span>),
               pass: <span class="hljs-built_in">this</span>.configService.get<<span class="hljs-built_in">string</span>>(<span class="hljs-string">'database.pass'</span>),
               name: <span class="hljs-built_in">this</span>.configService.get<<span class="hljs-built_in">string</span>>(<span class="hljs-string">'database.name'</span>),
             },
             jwt: {
               secret: <span class="hljs-built_in">this</span>.configService.get<<span class="hljs-built_in">string</span>>(<span class="hljs-string">'jwt.secret'</span>),
               expiresIn: <span class="hljs-built_in">this</span>.configService.get<<span class="hljs-built_in">string</span>>(<span class="hljs-string">'jwt.expiresIn'</span>),
             },
           };
         }
       }
      

      Register TypedConfigService in a module if you prefer injecting it instead of raw ConfigService.

    7. Dynamic module registration using config

      Many Nest modules accept dynamic options. For example, TypeORM:

       <span class="hljs-comment">// src/database/database.module.ts</span>
       <span class="hljs-keyword">import</span> { Module } <span class="hljs-keyword">from</span> <span class="hljs-string">'@nestjs/common'</span>;
       <span class="hljs-keyword">import</span> { TypeOrmModule } <span class="hljs-keyword">from</span> <span class="hljs-string">'@nestjs/typeorm'</span>;
       <span class="hljs-keyword">import</span> { ConfigService } <span class="hljs-keyword">from</span> <span class="hljs-string">'@nestjs/config'</span>;
      
       <span class="hljs-meta">@Module</span>({
         imports: [
           TypeOrmModule.forRootAsync({
             inject: [ConfigService],
             useFactory: <span class="hljs-function">(<span class="hljs-params">config: ConfigService</span>) =></span> ({
               <span class="hljs-keyword">type</span>: <span class="hljs-string">'postgres'</span>,
               host: config.get<<span class="hljs-built_in">string</span>>(<span class="hljs-string">'database.host'</span>),
               port: config.get<<span class="hljs-built_in">number</span>>(<span class="hljs-string">'database.port'</span>),
               username: config.get<<span class="hljs-built_in">string</span>>(<span class="hljs-string">'database.user'</span>),
               password: config.get<<span class="hljs-built_in">string</span>>(<span class="hljs-string">'database.pass'</span>),
               database: config.get<<span class="hljs-built_in">string</span>>(<span class="hljs-string">'database.name'</span>),
               entities: [__dirname + <span class="hljs-string">'/../**/*.entity{.ts,.js}'</span>],
               synchronize: config.get(<span class="hljs-string">'NODE_ENV'</span>) !== <span class="hljs-string">'production'</span>,
             }),
           }),
         ],
       })
       <span class="hljs-keyword">export</span> <span class="hljs-keyword">class</span> DatabaseModule {}
      

      Using forRootAsync with useFactory ensures config is loaded before the module initializes.

    12.2 Environment Variables

    Environment variables serve as the bridge between code and its runtime environment, letting you decouple configuration (like database URLs, API keys, or feature flags) from your source.

    By relying on environment variables, you ensure that the same application bundle can run safely across development, staging, and production – each providing its own sensitive or environment-specific settings without changing code. This is how it works:

    • 12-Factor app principle: Stores config in the environment. Avoids hard-coding secrets or environment-specific settings in code.

    • Separation of concerns: Code remains the same across environments. Behavior is driven by env vars or config files.

    • Security: Keeps secrets (API keys, DB passwords) out of source control. Uses environment variables or secure vaults.

    • Overrides and precedence: You may have multiple .env files (for example, .env, .env.local, .env.production) or CI/CD provided vars. It controls the order of loading.

    • Defaults and fallbacks: Provides sensible defaults in code or config factories so the app can run in development without requiring every variable.

    Here’s how to use it:

    1. .env files

      • Create a .env file at project root with key-value pairs:

          PORT=<span class="hljs-number">3000</span>
          DB_HOST=localhost
          DB_PORT=<span class="hljs-number">5432</span>
          DB_USER=postgres
          DB_PASS=secret
          DB_NAME=mydb
          JWT_SECRET=supersecretjwtkey
          JWT_EXPIRES_IN=<span class="hljs-number">2</span>h
        
      • Optionally create .env.development, .env.test, .env.production, and load them based on NODE_ENV.

      • Ensure .env files are in .gitignore to avoid committing secrets.

    2. Loading order

      • With @nestjs/config, specify envFilePath as an array, for example:

          ConfigModule.forRoot({
            envFilePath: [
              <span class="hljs-string">`.env.<span class="hljs-subst">${process.env.NODE_ENV}</span>.local`</span>,
              <span class="hljs-string">`.env.<span class="hljs-subst">${process.env.NODE_ENV}</span>`</span>,
              <span class="hljs-string">`.env`</span>,
            ],
            isGlobal: <span class="hljs-literal">true</span>,
          });
        
      • This tries .env.development.local, then .env.development, then .env. CI/CD can set actual environment variables that override values in files.

    3. Accessing raw environment variables

      • While structured config is preferred, sometimes you need direct access:

          <span class="hljs-keyword">const</span> raw = process.env.SOME_VAR;
        
      • Avoid scattering process.env in multiple places. Instead, prefer reading once in configuration factory and injecting via ConfigService.

    4. Default values

      • In configuration factory or when reading via ConfigService, provide defaults:

          <span class="hljs-keyword">const</span> port = configService.get<<span class="hljs-built_in">number</span>>(<span class="hljs-string">'PORT'</span>, <span class="hljs-number">3000</span>);
        

        or in factory:

          port: <span class="hljs-built_in">parseInt</span>(process.env.PORT, <span class="hljs-number">10</span>) || <span class="hljs-number">3000</span>
        
    5. Type coercion

      • Environment variables are strings by default. Convert to numbers or booleans as needed:

          <span class="hljs-keyword">const</span> isProd = configService.get<<span class="hljs-built_in">string</span>>(<span class="hljs-string">'NODE_ENV'</span>) === <span class="hljs-string">'production'</span>;
          <span class="hljs-keyword">const</span> enableFeature = configService.get<<span class="hljs-built_in">string</span>>(<span class="hljs-string">'FEATURE_FLAG'</span>) === <span class="hljs-string">'true'</span>;
          <span class="hljs-keyword">const</span> timeout = <span class="hljs-built_in">parseInt</span>(configService.get<<span class="hljs-built_in">string</span>>(<span class="hljs-string">'TIMEOUT_MS'</span>), <span class="hljs-number">10</span>) || <span class="hljs-number">5000</span>;
        
    6. Secret management

      • For sensitive data in production, consider using secret managers (AWS Secrets Manager, Vault) instead of plain .env. In that case, load secrets at startup (for example, via a custom provider or factory) and merge into the configuration.

      • Example: in useFactory, asynchronously fetch secrets and return a config object including them.

    7. Runtime configuration changes

      • Generally configs are static at startup. If you need to reload config without restarting, implement a custom mechanism (for example, read from a database or remote config service periodically). Inject a service that fetches and caches values, but note this departs from 12-factor principles.
    8. Validation in production

      • Always validate required env vars at startup so misconfigurations fail early. Use validationSchema with Joi or another validator.

      • Example error: if JWT_SECRET is missing or too short, the app should refuse to start, logging a clear error.

    With configuration managed via @nestjs/config and environment variables, your NestJS app can adapt seamlessly across environments, keep secrets secure, and avoid environment-specific code changes. In the next section, we’ll cover Authentication strategies (JWT, OAuth2/social login).

    13. Authentication

    Handling authentication securely is a common requirement. In NestJS, you typically use Passport strategies alongside the @nestjs/jwt module for JWT-based flows, or OAuth2 strategies for social login.

    Here, we’ll cover two common approaches:

    • JWT Strategy: token-based authentication for APIs.

    • OAuth2 / Social Login: integrating providers like Google or GitHub.

    13.1 JWT Strategy

    JSON Web Tokens (JWTs) are a compact, URL-safe means of representing claims between two parties. In an authentication context, the server issues a signed token containing user identity and possibly other claims, while the client stores and sends this token on subsequent requests (typically in the Authorization: Bearer <token> header).

    Because the token is signed (and optionally encrypted), the server can verify its integrity and authenticity without needing to maintain session state in memory or a database. This stateless nature simplifies scaling and decouples services.

    Tokens include an expiration (exp) so they automatically become invalid after a certain time. For longer-lived sessions, you can layer a refresh-token pattern on top.

    In NestJS, we leverage @nestjs/jwt to sign and verify tokens and @nestjs/passport with passport-jwt to integrate a guard that checks incoming tokens. Below is how it works.

    • JWT (JSON Web Token): a signed token containing claims (for example, user ID) that clients send in the Authorization header.

    • Stateless: the server verifies the token signature without storing session state.

    • Expiration: embed expiry (exp) so tokens auto-expire; possibly use refresh tokens for long-lived sessions.

    • In NestJS, you use @nestjs/jwt to sign/verify tokens and @nestjs/passport with passport-jwt to implement the guard.

    Here’s how to use it:

    1. Install dependencies

       npm install @nestjs/jwt passport-jwt @nestjs/passport passport
      
    2. Configuration

      Use ConfigService (from previous section) to load secrets and TTL:

       <span class="hljs-comment">// src/auth/auth.config.ts</span>
       <span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> () => ({
         jwt: {
           secret: process.env.JWT_SECRET || <span class="hljs-string">'default-secret'</span>,
           expiresIn: process.env.JWT_EXPIRES_IN || <span class="hljs-string">'1h'</span>,
         },
       });
      

      Ensure ConfigModule.forRoot({ load: [authConfig], isGlobal: true, validationSchema: ... }) is set in AppModule.

    3. AuthModule setup

       <span class="hljs-comment">// src/auth/auth.module.ts</span>
       <span class="hljs-keyword">import</span> { Module } <span class="hljs-keyword">from</span> <span class="hljs-string">'@nestjs/common'</span>;
       <span class="hljs-keyword">import</span> { JwtModule } <span class="hljs-keyword">from</span> <span class="hljs-string">'@nestjs/jwt'</span>;
       <span class="hljs-keyword">import</span> { PassportModule } <span class="hljs-keyword">from</span> <span class="hljs-string">'@nestjs/passport'</span>;
       <span class="hljs-keyword">import</span> { ConfigService, ConfigModule } <span class="hljs-keyword">from</span> <span class="hljs-string">'@nestjs/config'</span>;
       <span class="hljs-keyword">import</span> { JwtStrategy } <span class="hljs-keyword">from</span> <span class="hljs-string">'./jwt.strategy'</span>;
       <span class="hljs-keyword">import</span> { AuthService } <span class="hljs-keyword">from</span> <span class="hljs-string">'./auth.service'</span>;
       <span class="hljs-keyword">import</span> { UsersModule } <span class="hljs-keyword">from</span> <span class="hljs-string">'../users/users.module'</span>; <span class="hljs-comment">// assumes a UsersService</span>
      
       <span class="hljs-meta">@Module</span>({
         imports: [
           UsersModule,
           PassportModule.register({ defaultStrategy: <span class="hljs-string">'jwt'</span> }),
           JwtModule.registerAsync({
             imports: [ConfigModule],
             inject: [ConfigService],
             useFactory: <span class="hljs-function">(<span class="hljs-params">config: ConfigService</span>) =></span> ({
               secret: config.get<<span class="hljs-built_in">string</span>>(<span class="hljs-string">'jwt.secret'</span>),
               signOptions: { expiresIn: config.get<<span class="hljs-built_in">string</span>>(<span class="hljs-string">'jwt.expiresIn'</span>) },
             }),
           }),
         ],
         providers: [AuthService, JwtStrategy],
         <span class="hljs-built_in">exports</span>: [AuthService],
       })
       <span class="hljs-keyword">export</span> <span class="hljs-keyword">class</span> AuthModule {}
      
    4. AuthService

      Responsible for validating credentials and issuing tokens:

       <span class="hljs-comment">// src/auth/auth.service.ts</span>
       <span class="hljs-keyword">import</span> { Injectable, UnauthorizedException } <span class="hljs-keyword">from</span> <span class="hljs-string">'@nestjs/common'</span>;
       <span class="hljs-keyword">import</span> { JwtService } <span class="hljs-keyword">from</span> <span class="hljs-string">'@nestjs/jwt'</span>;
       <span class="hljs-keyword">import</span> { UsersService } <span class="hljs-keyword">from</span> <span class="hljs-string">'../users/users.service'</span>;
       <span class="hljs-keyword">import</span> * <span class="hljs-keyword">as</span> bcrypt <span class="hljs-keyword">from</span> <span class="hljs-string">'bcrypt'</span>;
      
       <span class="hljs-meta">@Injectable</span>()
       <span class="hljs-keyword">export</span> <span class="hljs-keyword">class</span> AuthService {
         <span class="hljs-keyword">constructor</span>(<span class="hljs-params">
           <span class="hljs-keyword">private</span> <span class="hljs-keyword">readonly</span> usersService: UsersService,
           <span class="hljs-keyword">private</span> <span class="hljs-keyword">readonly</span> jwtService: JwtService,
         </span>) {}
      
         <span class="hljs-comment">// Validate user credentials (email/password)</span>
         <span class="hljs-keyword">async</span> validateUser(email: <span class="hljs-built_in">string</span>, pass: <span class="hljs-built_in">string</span>) {
           <span class="hljs-keyword">const</span> user = <span class="hljs-keyword">await</span> <span class="hljs-built_in">this</span>.usersService.findByEmail(email);
           <span class="hljs-keyword">if</span> (user && (<span class="hljs-keyword">await</span> bcrypt.compare(pass, user.password))) {
             <span class="hljs-comment">// exclude password before returning</span>
             <span class="hljs-keyword">const</span> { password, ...result } = user;
             <span class="hljs-keyword">return</span> result;
           }
           <span class="hljs-keyword">return</span> <span class="hljs-literal">null</span>;
         }
      
         <span class="hljs-comment">// Called after validateUser succeeds</span>
         <span class="hljs-keyword">async</span> login(user: <span class="hljs-built_in">any</span>) {
           <span class="hljs-keyword">const</span> payload = { sub: user.id, email: user.email };
           <span class="hljs-keyword">return</span> {
             access_token: <span class="hljs-built_in">this</span>.jwtService.sign(payload),
           };
         }
       }
      
    5. JwtStrategy

       <span class="hljs-comment">// src/auth/jwt.strategy.ts</span>
       <span class="hljs-keyword">import</span> { Injectable } <span class="hljs-keyword">from</span> <span class="hljs-string">'@nestjs/common'</span>;
       <span class="hljs-keyword">import</span> { PassportStrategy } <span class="hljs-keyword">from</span> <span class="hljs-string">'@nestjs/passport'</span>;
       <span class="hljs-keyword">import</span> { ExtractJwt, Strategy } <span class="hljs-keyword">from</span> <span class="hljs-string">'passport-jwt'</span>;
       <span class="hljs-keyword">import</span> { ConfigService } <span class="hljs-keyword">from</span> <span class="hljs-string">'@nestjs/config'</span>;
      
       <span class="hljs-meta">@Injectable</span>()
       <span class="hljs-keyword">export</span> <span class="hljs-keyword">class</span> JwtStrategy <span class="hljs-keyword">extends</span> PassportStrategy(Strategy) {
         <span class="hljs-keyword">constructor</span>(<span class="hljs-params"><span class="hljs-keyword">private</span> <span class="hljs-keyword">readonly</span> configService: ConfigService</span>) {
           <span class="hljs-built_in">super</span>({
             jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
             ignoreExpiration: <span class="hljs-literal">false</span>,
             secretOrKey: configService.get<<span class="hljs-built_in">string</span>>(<span class="hljs-string">'jwt.secret'</span>),
           });
         }
      
         <span class="hljs-keyword">async</span> validate(payload: <span class="hljs-built_in">any</span>) {
           <span class="hljs-comment">// payload.sub is user ID</span>
           <span class="hljs-keyword">return</span> { userId: payload.sub, email: payload.email };
           <span class="hljs-comment">// returned value is assigned to req.user</span>
         }
       }
      
    6. Auth Controller

      Expose login endpoint:

       <span class="hljs-comment">// src/auth/auth.controller.ts</span>
       <span class="hljs-keyword">import</span> { Controller, Post, Body, Request, UseGuards } <span class="hljs-keyword">from</span> <span class="hljs-string">'@nestjs/common'</span>;
       <span class="hljs-keyword">import</span> { AuthService } <span class="hljs-keyword">from</span> <span class="hljs-string">'./auth.service'</span>;
       <span class="hljs-keyword">import</span> { LocalAuthGuard } <span class="hljs-keyword">from</span> <span class="hljs-string">'./local-auth.guard'</span>; <span class="hljs-comment">// optional if using local strategy</span>
      
       <span class="hljs-meta">@Controller</span>(<span class="hljs-string">'auth'</span>)
       <span class="hljs-keyword">export</span> <span class="hljs-keyword">class</span> AuthController {
         <span class="hljs-keyword">constructor</span>(<span class="hljs-params"><span class="hljs-keyword">private</span> <span class="hljs-keyword">readonly</span> authService: AuthService</span>) {}
      
         <span class="hljs-comment">// Example: using a local strategy for email/password</span>
         <span class="hljs-meta">@UseGuards</span>(LocalAuthGuard)
         <span class="hljs-meta">@Post</span>(<span class="hljs-string">'login'</span>)
         <span class="hljs-keyword">async</span> login(<span class="hljs-meta">@Request</span>() req) {
           <span class="hljs-comment">// LocalAuthGuard attaches user to req.user</span>
           <span class="hljs-keyword">return</span> <span class="hljs-built_in">this</span>.authService.login(req.user);
         }
      
         <span class="hljs-comment">// Alternatively, implement login logic directly:</span>
         <span class="hljs-meta">@Post</span>(<span class="hljs-string">'login-basic'</span>)
         <span class="hljs-keyword">async</span> loginBasic(<span class="hljs-meta">@Body</span>() body: { email: <span class="hljs-built_in">string</span>; password: <span class="hljs-built_in">string</span> }) {
           <span class="hljs-keyword">const</span> user = <span class="hljs-keyword">await</span> <span class="hljs-built_in">this</span>.authService.validateUser(body.email, body.password);
           <span class="hljs-keyword">if</span> (!user) {
             <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> UnauthorizedException(<span class="hljs-string">'Invalid credentials'</span>);
           }
           <span class="hljs-keyword">return</span> <span class="hljs-built_in">this</span>.authService.login(user);
         }
       }
      
      • LocalAuthGuard would use a LocalStrategy to validate credentials via Passport.
    7. Protecting routes

      Use the JwtAuthGuard:

       <span class="hljs-comment">// src/auth/jwt-auth.guard.ts</span>
       <span class="hljs-keyword">import</span> { Injectable } <span class="hljs-keyword">from</span> <span class="hljs-string">'@nestjs/common'</span>;
       <span class="hljs-keyword">import</span> { AuthGuard } <span class="hljs-keyword">from</span> <span class="hljs-string">'@nestjs/passport'</span>;
      
       <span class="hljs-meta">@Injectable</span>()
       <span class="hljs-keyword">export</span> <span class="hljs-keyword">class</span> JwtAuthGuard <span class="hljs-keyword">extends</span> AuthGuard(<span class="hljs-string">'jwt'</span>) {}
      

      Apply to controllers or routes:

       <span class="hljs-comment">// src/profile/profile.controller.ts</span>
       <span class="hljs-keyword">import</span> { Controller, Get, UseGuards, Request } <span class="hljs-keyword">from</span> <span class="hljs-string">'@nestjs/common'</span>;
       <span class="hljs-keyword">import</span> { JwtAuthGuard } <span class="hljs-keyword">from</span> <span class="hljs-string">'../auth/jwt-auth.guard'</span>;
      
       <span class="hljs-meta">@Controller</span>(<span class="hljs-string">'profile'</span>)
       <span class="hljs-keyword">export</span> <span class="hljs-keyword">class</span> ProfileController {
         <span class="hljs-meta">@UseGuards</span>(JwtAuthGuard)
         <span class="hljs-meta">@Get</span>()
         getProfile(<span class="hljs-meta">@Request</span>() req) {
           <span class="hljs-keyword">return</span> req.user; <span class="hljs-comment">// { userId, email }</span>
         }
       }
      
    8. Refresh Tokens (optional)

      • Issue a refresh token (longer expiry) and store it (for example, in DB or as HTTP-only cookie).

      • Create a separate endpoint to issue new access token when the access token expires.

      • Verify refresh token validity (for example, compare stored token or a hashed version).

      • Implementation details vary – consider security best practices (rotate tokens, revoke on logout).

    13.2 OAuth2 / Social Login

    Social login via OAuth2 lets users authenticate with third-party providers (Google, GitHub, Facebook, and so on) without creating a separate password for your service.

    Under the Authorization Code Flow, the user is redirected to the provider’s consent screen. After granting permission, the provider redirects back with a temporary code. The backend exchanges this code for access (and optionally refresh) tokens, fetches the user’s profile, and then you can link or create a local user record. Finally, you typically issue your own JWT (or session) so the client can call your secured APIs.

    Keeping OAuth client IDs/secrets in environment variables (via ConfigService) ensures security and flexibility. Here’s how it works:

    • OAuth2 Authorization Code Flow: Redirect the user to the provider’s consent screen. The provider redirects back with a code. The back-end exchanges code for tokens and retrieves user info.

    • In server-side (NestJS) you use Passport strategies (for example, passport-google-oauth20, passport-github2).

    • After getting user profile from provider, you look up or create a matching local user record, then issue your own JWT or session.

    • Keep secrets (client ID/secret) in environment variables and load via ConfigService.

    Here’s how to use it:

    1. Install dependencies

       npm install @nestjs/passport passport passport-google-oauth20
       <span class="hljs-comment"># or passport-facebook, passport-github2, etc.</span>
      
    2. Configuration

      Add OAuth credentials to env and ConfigModule:

       GOOGLE_CLIENT_ID=your-google-client-id
       GOOGLE_CLIENT_SECRET=your-google-client-secret
       GOOGLE_CALLBACK_URL=http://localhost:3000/auth/google/callback
      
    3. OAuth Strategy

      Example: Google

       <span class="hljs-comment">// src/auth/google.strategy.ts</span>
       <span class="hljs-keyword">import</span> { Injectable } <span class="hljs-keyword">from</span> <span class="hljs-string">'@nestjs/common'</span>;
       <span class="hljs-keyword">import</span> { PassportStrategy } <span class="hljs-keyword">from</span> <span class="hljs-string">'@nestjs/passport'</span>;
       <span class="hljs-keyword">import</span> { Strategy, VerifyCallback } <span class="hljs-keyword">from</span> <span class="hljs-string">'passport-google-oauth20'</span>;
       <span class="hljs-keyword">import</span> { ConfigService } <span class="hljs-keyword">from</span> <span class="hljs-string">'@nestjs/config'</span>;
       <span class="hljs-keyword">import</span> { AuthService } <span class="hljs-keyword">from</span> <span class="hljs-string">'./auth.service'</span>;
      
       <span class="hljs-meta">@Injectable</span>()
       <span class="hljs-keyword">export</span> <span class="hljs-keyword">class</span> GoogleStrategy <span class="hljs-keyword">extends</span> PassportStrategy(Strategy, <span class="hljs-string">'google'</span>) {
         <span class="hljs-keyword">constructor</span>(<span class="hljs-params">configService: ConfigService, <span class="hljs-keyword">private</span> <span class="hljs-keyword">readonly</span> authService: AuthService</span>) {
           <span class="hljs-built_in">super</span>({
             clientID: configService.get<<span class="hljs-built_in">string</span>>(<span class="hljs-string">'GOOGLE_CLIENT_ID'</span>),
             clientSecret: configService.get<<span class="hljs-built_in">string</span>>(<span class="hljs-string">'GOOGLE_CLIENT_SECRET'</span>),
             callbackURL: configService.get<<span class="hljs-built_in">string</span>>(<span class="hljs-string">'GOOGLE_CALLBACK_URL'</span>),
             scope: [<span class="hljs-string">'email'</span>, <span class="hljs-string">'profile'</span>],
           });
         }
      
         <span class="hljs-keyword">async</span> validate(accessToken: <span class="hljs-built_in">string</span>, refreshToken: <span class="hljs-built_in">string</span>, profile: <span class="hljs-built_in">any</span>, done: VerifyCallback): <span class="hljs-built_in">Promise</span><<span class="hljs-built_in">any</span>> {
           <span class="hljs-keyword">const</span> { id, emails, displayName } = profile;
           <span class="hljs-keyword">const</span> email = emails && emails[<span class="hljs-number">0</span>]?.value;
           <span class="hljs-comment">// Delegate to AuthService to find or create local user</span>
           <span class="hljs-keyword">const</span> user = <span class="hljs-keyword">await</span> <span class="hljs-built_in">this</span>.authService.validateOAuthLogin(<span class="hljs-string">'google'</span>, id, email, displayName);
           done(<span class="hljs-literal">null</span>, user);
         }
       }
      

      In AuthService:

       <span class="hljs-comment">// src/auth/auth.service.ts (add method)</span>
       <span class="hljs-keyword">async</span> validateOAuthLogin(provider: <span class="hljs-built_in">string</span>, providerId: <span class="hljs-built_in">string</span>, email: <span class="hljs-built_in">string</span>, name?: <span class="hljs-built_in">string</span>) {
         <span class="hljs-comment">// Find existing user by provider+providerId or email</span>
         <span class="hljs-keyword">let</span> user = <span class="hljs-keyword">await</span> <span class="hljs-built_in">this</span>.usersService.findByProvider(provider, providerId);
         <span class="hljs-keyword">if</span> (!user) {
           <span class="hljs-comment">// Optionally check by email: if exists, link accounts; otherwise create new</span>
           user = <span class="hljs-keyword">await</span> <span class="hljs-built_in">this</span>.usersService.createOAuthUser({ provider, providerId, email, name });
         }
         <span class="hljs-comment">// Issue JWT or return user object; here we return minimal payload for login</span>
         <span class="hljs-keyword">return</span> user;
       }
      
    4. AuthController endpoints

       <span class="hljs-comment">// src/auth/auth.controller.ts</span>
       <span class="hljs-keyword">import</span> { Controller, Get, Req, UseGuards } <span class="hljs-keyword">from</span> <span class="hljs-string">'@nestjs/common'</span>;
       <span class="hljs-keyword">import</span> { AuthGuard } <span class="hljs-keyword">from</span> <span class="hljs-string">'@nestjs/passport'</span>;
       <span class="hljs-keyword">import</span> { AuthService } <span class="hljs-keyword">from</span> <span class="hljs-string">'./auth.service'</span>;
      
       <span class="hljs-meta">@Controller</span>(<span class="hljs-string">'auth'</span>)
       <span class="hljs-keyword">export</span> <span class="hljs-keyword">class</span> AuthController {
         <span class="hljs-keyword">constructor</span>(<span class="hljs-params"><span class="hljs-keyword">private</span> <span class="hljs-keyword">readonly</span> authService: AuthService</span>) {}
      
         <span class="hljs-meta">@Get</span>(<span class="hljs-string">'google'</span>)
         <span class="hljs-meta">@UseGuards</span>(AuthGuard(<span class="hljs-string">'google'</span>))
         <span class="hljs-keyword">async</span> googleAuth(<span class="hljs-meta">@Req</span>() req) {
           <span class="hljs-comment">// Initiates Google OAuth2 flow</span>
         }
      
         <span class="hljs-meta">@Get</span>(<span class="hljs-string">'google/callback'</span>)
         <span class="hljs-meta">@UseGuards</span>(AuthGuard(<span class="hljs-string">'google'</span>))
         <span class="hljs-keyword">async</span> googleAuthRedirect(<span class="hljs-meta">@Req</span>() req) {
           <span class="hljs-comment">// Google redirects here after consent; req.user set by GoogleStrategy.validate</span>
           <span class="hljs-keyword">const</span> user = req.user;
           <span class="hljs-comment">// Issue JWT or set a cookie, then redirect or return token</span>
           <span class="hljs-keyword">const</span> jwt = <span class="hljs-keyword">await</span> <span class="hljs-built_in">this</span>.authService.login(user);
           <span class="hljs-comment">// E.g., redirect with token as query, or set cookie:</span>
           <span class="hljs-comment">// res.redirect(`http://frontend-app.com?token=${jwt.access_token}`);</span>
           <span class="hljs-keyword">return</span> { access_token: jwt.access_token };
         }
       }
      
      • The first endpoint (/auth/google) triggers redirect to Google.

      • The callback endpoint handles the response, then issues your JWT.

    5. Session vs. Stateless

      • Many examples use sessions and @nestjs/passport session support, but for APIs you often skip sessions: Passport still invokes validate, returns user, and you issue JWT immediately.

      • Ensure you disable sessions in PassportModule registration: PassportModule.register({ session: false }).

    6. Multiple Providers

      • Repeat strategy setup for each provider (for example, GitHubStrategy).

      • In validateOAuthLogin, handle provider parameter to distinguish logic.

      • You can store in your user entity fields like googleId, githubId, and so on, or a separate table for OAuth accounts.

    7. Protecting routes post-login

      • Clients use the issued JWT in Authorization: Bearer <token> to access protected endpoints via JwtAuthGuard.

      • If you prefer sessions/cookies, configure Nest to use sessions and Passport’s session features, but for SPAs or mobile clients JWT is common.

    8. Frontend considerations

      • Redirect URIs must match those configured in the OAuth provider console.

      • After receiving JWT, store it securely (for example, HTTP-only cookie or secure storage on client).

      • Handle token expiry: possibly combine OAuth refresh tokens or your own refresh token flow.

    With JWT and OAuth2 strategies set up, your NestJS backend can support secured endpoints, user registration/login flows, and social logins.

    Conclusion & Further Resources

    Summary

    We’ve walked through key aspects of building a NestJS application: its architectural patterns, core building blocks (modules, controllers, providers), dependency injection, routing and middleware, request lifecycle with pipes, guards, exception filters, interceptors, database integration options (TypeORM, Mongoose, Prisma), configuration management, authentication strategies (JWT, OAuth2), and strategies for migrating existing apps.

    NestJS provides a structured, TypeScript-first framework that accelerates development of scalable, maintainable backends. By leveraging its module system and built-in integrations, you get consistency, testability, and clear separation of concerns out of the box.

    Whether you choose a relational database via TypeORM, a document store with Mongoose, or Prisma’s type-safe client, you can plug these into Nest’s DI container and configuration module. Authentication flows – both JWT-based and social login – fit naturally into Nest’s Passport integration.

    Overall, NestJS is well-suited for APIs, microservices, real-time apps, and enterprise backends where maintainability and developer experience matter.

    Official Docs and Community Links

    • NestJS Official Documentation: Comprehensive guide and API reference for all core features.

      • https://docs.nestjs.com
    • GitHub Repository: Source code, issue tracker, and community contributions.

      • https://github.com/nestjs/nest

    Source: freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More 

    Facebook Twitter Reddit Email Copy Link
    Previous ArticlePaid proxy servers vs free proxies: Is paying for a proxy service worth it?
    Next Article How to Build a Medical Chatbot with Flutter and Gemini: A Beginner’s Guide

    Related Posts

    Repurposing Protein Folding Models for Generation with Latent Diffusion
    Artificial Intelligence

    Repurposing Protein Folding Models for Generation with Latent Diffusion

    September 14, 2025
    Artificial Intelligence

    Scaling Up Reinforcement Learning for Traffic Smoothing: A 100-AV Highway Deployment

    September 14, 2025
    Leave A Reply Cancel Reply

    For security, use of Google's reCAPTCHA service is required which is subject to the Google Privacy Policy and Terms of Use.

    Continue Reading

    Last Call: Early Access for NativePHP Ends This Week

    Development

    CVE-2025-31263 – Apple macOS Coprocessor Memory Corruption

    Common Vulnerabilities and Exposures (CVEs)

    CVE-2024-48702 – PHPGurukul Old Age Home Management System HTML Injection

    Common Vulnerabilities and Exposures (CVEs)

    VRider SBK Launching for PS VR2 This Week – All You Need to Know About the First-Person Superbikes Game

    Operating Systems

    Highlights

    FastVLM: Efficient Vision encoding for Vision Language Models

    April 18, 2025

    Scaling the input image resolution is essential for enhancing the performance of Vision Language Models…

    CVE-2025-48375 – Schule Open-Source School Management System OTP Email Flooding Vulnerability

    May 23, 2025

    Laravel Request Content Type Inspection Methods

    July 7, 2025

    Microsoft Edge lets you turn off the Copilot-inspired New Tab Page and built-in Copilot Search for good

    May 13, 2025
    © DevStackTips 2025. All rights reserved.
    • Contact
    • Privacy Policy

    Type above and press Enter to search. Press Esc to cancel.