NestJS is a framework for building efficient, scalable Node.js server-side applications. It is built with and fully supports TypeScript(though it also allows pure JavaScript) and combines elements of Object Oriented Programming (OOP), Functional Programming (FP), and Functional Reactive Programming (FRP).
Under the hood, NestJS makes use of robust HTTP server frameworks like Express (the default) or optionally Fastify, providing a level of abstraction above these Node.js frameworks while still exposing their APIs.
Why is it "Opinionated"?A framework is described as "opinionated" when it enforces a specific architectural style and set of conventions rather than leaving those decisions to the developer. NestJS is considered opinionated because:
- Enforced Architecture:> It dictates a modular structure (Modules, Controllers, Providers/Services) inspired heavily by Angular. You cannot simply drop files anywhere; you must follow the framework's wiring logic.
- Design Patterns:> It mandates the use of Dependency Injection (DI)> and heavily utilizes Decorators> to define routes, middleware, and guards.
-
Built-in Solutions: > Unlike "unopinionated" frameworks like Express—where you must manually choose libraries for validation, testing, and database ORMs—NestJS provides standard, built-in techniques for these tasks (e.g., Pipes for validation, Guards for auth, Interceptors for response mapping).
,Key Benefit: This strict structure ensures that codebases remain consistent, maintainable, and scalable, allowing different developers to jump into a project and immediately understand the code organization.
NestJS is built with and for TypeScript, treating it as a first-class citizen rather than an afterthought. It leverages TypeScript's advanced features to provide a robust, scalable backend architecture:
- Decorators & Metadata Reflection: NestJS heavily relies on TypeScript
decorators (e.g.,
@Controller,@Get,@Injectable) to define the role and behavior of classes. Under the hood, it uses reflection reflection to access runtime type information, allowing the framework to automatically register routes, manage middleware, and handle request pipelines without verbose configuration files. - Dependency Injection (DI): By using TypeScript types in constructor
signatures
(e.g.,
constructor(private readonly usersService: UsersService)), NestJS automatically resolves and injects the correct dependencies. This "magical" wiring is possible because TypeScript emits type metadata that NestJS reads at runtime. - Strong Typing & Safety: It enforces strict type checking across the application, from database entities to API responses. This catches potential errors at compile-time (e.g., passing the wrong object structure to a service) rather than crashing the server at runtime.
- Data Transfer Objects (DTOs): NestJS uses TypeScript classes for DTOs.
When combined with libraries like
class-validatorandclass-transformer, these classes serve a dual purpose: they define the shape of the data for type checking and provide runtime validation rules for incoming HTTP requests. - Tooling & Developer Experience: Because the framework provides strict interfaces and types, developers get meaningful IntelliSense, auto-completion, and safe refactoring tools in IDEs like VS Code, significantly speeding up development.
A Module in NestJS is a class annotated with the @Module()
decorator. It serves as the fundamental structural unit of the application, organizing
related components (controllers, services, etc.) into cohesive blocks.
- Organization & Structure: Modules group related features together
(e.g., a
UsersModulecontainingUsersControllerandUsersService). Every NestJS application has at least one module, the Root Module, which serves as the entry point for the application graph. - Dependency Resolution Context: The module defines the scope for
dependency injection. The
@Module()decorator accepts an object with four key properties that tell Nest how to wire the application: -
controllers:The set of controllers defined in this module which have to be instantiated.- providers: The services or providers that will be instantiated by the Nest injector and used within this module.
imports:The list of other imported modules that export the providers required in this module.exports:The subset of providers that are owned by this module and should be available in other modules which import this module.
- Encapsulation: Modules enforce a strong boundary. Providers are private
to the module by default; they cannot be injected elsewhere unless they are explicitly
listed in the
exportsarray. This promotes loose coupling and clean architecture.
Dependency Injection (DI) is a core design pattern in NestJS where the framework manages the
creation and lifecycle of objects (dependencies), rather than the developer creating them
manually (e.g., new Service()).
- 1. Defining Providers (
@Injectable()):- You define a class (e.g.,
UsersService) and mark it with the@Injectable()decorator. This tells NestJS that this class can be managed by the IoC (Inversion of Control) container and injected into other classes.
- 2. Registering in Modules:
- The provider must be registered in the
providersarray of a Module (e.g.,AppModule). This registers the class with the NestJS IoC container, allowing it to be looked up and instantiated.
- 3. Constructor Injection:
- To use a dependency, you simply declare it in the constructor of the class that needs it (e.g., a Controller).
- Syntax:
constructor(private readonly usersService: UsersService) {} - NestJS uses TypeScript types to identify that
UsersServiceis needed.
- 4. Resolution (The "Magic"):
- When the application starts, NestJS inspects the constructor.
- It checks its internal container to see if an instance of
UsersServicealready exists. - If yes, it returns the existing instance (Singleton pattern).
- If no, it creates a new instance, caches it, and then injects it.
By default, dependencies in NestJS are Singletons (one instance shared across the entire application). However, you can configure them to be:
- Transient: A new instance is created every time it is injected.
- Request-Scoped: A new instance is created for each incoming HTTP request.
In NestJS, Controllers and Providers have distinct roles in the request lifecycle, separating the handling of HTTP traffic from the underlying business logic.
1. Controller (@Controller()):
- Role: The entry point for the application. It is responsible for handling incoming HTTP requests and returning responses to the client.
- Functionality: It defines routes (endpoints like
GET /users,POST /items) using decorators. It extracts data from the request (params, query, body) but should not contain complex business logic. It delegates that work to a service.
@Injectable()):
- Role: A broad category that includes Services, Repositories, Factories, and Helpers. Its main job is to handle business logic, data storage/retrieval, and complex calculations.
- Functionality: It is a class annotated with
@Injectable(). Providers are managed by the NestJS IoC container and can be injected into Controllers or other Providers.
| Feature | Controller | Provider (Service) |
|---|---|---|
| Decorator | @Controller('path') |
@Injectable() |
| Primary Responsibility | Handling HTTP Requests & Responses | Executing Business Logic & Data Access |
| Methods | Route Handlers (@Get, @Post, etc.) |
Functional Methods (findAll, createUser) |
| Interaction | Calls Services to perform tasks | Called by Controllers or other Services |
| Analogy | The Waiter (takes order, brings food) | The Chef (cooks the food) |
Creating a NestJS project is best done using the Nest CLI, which scaffolds a best-practice project structure for you.
Prerequisite: Ensure Node.js (version >= 16) is installed on your operating system.
Steps:Run the following command in your terminal to install the NestJS CLI tool globally:
2. Create the Project:Run the new command followed by your desired project name:
The CLI will prompt you to choose a package manager (npm, yarn, or pnpm). Select one using the arrow keys and hit Enter. NestJS will then scaffold the files and install dependencies.
4. Run the Application:Navigate into your new project directory and start the development server:
The application will start on http://localhost:3000/.
If you prefer not to install the CLI globally, you can use npx:
The AppModule is the primary entry point for a NestJS application's structure. It is the root node of the dependency graph that NestJS builds at startup.
Why is it the "Root"?NestJS organizes code into a tree-like structure of modules. The AppModule is
the trunk of this tree.
- Application Graph: When the application starts, NestJS looks at the
AppModuleto understand which controllers, services, and other modules need to be loaded. - Orchestration: It binds together all other feature modules (like
UsersModule,AuthModule,OrdersModule). Without importing these into theAppModule(directly or transitively), NestJS would not know they exist.
In your main.ts file, the AppModule is explicitly passed to the
factory method to create the app instance:
Decorators are a core feature of TypeScript that NestJS uses heavily to define the behavior and role of classes and their members. They are functions prefixed with the @ symbol that attach metadata to code.
How They Function:Decorators wrap the target (class, method, or property) and modify it or store metadata about it. NestJS reads this metadata at runtime using a process called Reflection.
- Metadata Storage: When you use
@Controller('/users'), you aren't changing the class code directly. Instead, you are attaching a piece of data (the route path) to the class. - Runtime Processing: When the application starts, NestJS scans all
classes. It sees the
@Controllermetadata and says, "Aha! This class handles routes starting with/users," and sets up the routing table accordingly.
- Class Decorators: Define the role of the class.
@Module(): Defines a module.@Controller('path'): Defines a controller.@Injectable(): Defines a service/provider.- Method Decorators: Define behavior for specific methods.
@Get(),@Post(): HTTP method handlers.@UseGuards(): Applies authentication guards.- Property Decorators: Used inside classes (less common in basic usage).
@Inject(): Manually injects a dependency (rarely needed due to constructor injection).- Parameter Decorators: Access request data in method arguments.
@Body(): Extracts the request body.@Param(): Extracts route parameters.@Query(): Extracts query strings.
NestJS offers two distinct approaches for handling the HTTP Request and Response objects: the Standard (Platform-Agnostic) approach and the Library-Specific approach.
1. Standard Approach (Recommended)NestJS abstracts the underlying platform (Express or Fastify) to promote cleaner, more testable code.
- Requests: Instead of accessing the entire Request object, you use dedicated decorators to extract specific data.
@Body(): Extracts the request body.@Query(): Extracts query parameters (e.g.,?limit=10).@Param(): Extracts route parameters (e.g.,/users/:id).@Headers(): Extracts request headers.- Responses: You simply
returna JavaScript object, array, or string from the handler. - NestJS automatically serializes objects/arrays to JSON.
- It automatically sets the status code to 200 OK (or 201 Created for POST requests).
- You can change the status code declaratively using the
@HttpCode(204)decorator.
You can access the underlying Express (or Fastify) objects directly if you need fine-grained control (e.g., manipulating cookies, headers manually).
- Injection: Use the
@Req()and@Res()(or@Response()) decorators in your handler signature. - Trade-off: Using
@Res()puts NestJS into Library-Specific Mode for that handler. You lose standard features like Interceptors and automatic serialization unless you enablepassthrough: truein the decorator options (e.g.,@Res({ passthrough: true })).
The main.ts file serves as the entry point of a NestJS
application. It is responsible for bootstrapping the application and starting the server.
- Bootstrapping: It contains an asynchronous function (conventionally
named
bootstrap()) that initializes the NestJS application instance. - Factory Creation: It uses the
NestFactoryclass to create the application instance, passing in the root module (AppModule). - Server Listener: It starts the HTTP server (Express or Fastify) by
calling
app.listen(), specifying the port number (e.g., 3000). - Global Configuration: This is where you apply global settings that affect the entire application, such as:
- Global Pipes: For validation (e.g.,
app.useGlobalPipes(new ValidationPipe())). - Global Guards/Interceptors: For app-wide logic.
- CORS: Enabling Cross-Origin Resource Sharing.
- Swagger: Setting up API documentation.
- Prefixing: Setting a global route prefix
(e.g.,
/api/v1).
Middleware in NestJS acts as a bridge between the incoming request and the route handler. It is a function that executes before the route handler is reached.
Key Characteristics:- Access: It has access to the Request object, the Response object, and the next() function.
- Control Flow: It can execute code, modify the request/response objects,
end the request-response cycle early, or call
next()to pass control to the next middleware or the route handler. Ifnext()is not called, the request will hang.
Use middleware for tasks that are not specific to a single route or business logic but are rather "global" or "utility" in nature. Common use cases include:
- Logging: Recording details of every incoming request (e.g., method, URL, timestamp).
- Body Parsing: Transforming raw request data into a usable format (e.g., JSON parsing).
- Security Headers: Setting HTTP headers for security (e.g., using
helmet). - Compression: Compressing HTTP responses (e.g., using
compression). - Cookie Parsing: extracting cookies from headers.
- Rate Limiting: restricting the number of requests from a single IP.
Middleware can be implemented as either a class (implementing
NestMiddleware) or a simple functional middleware. It is
configured in the configure() method of a Module using the
MiddlewareConsumer.
Pipes in NestJS are classes annotated with @Injectable() that
implement the PipeTransform interface. They operate on the arguments being
processed by a route handler, just before the method is executed.
-
Transformation:
- Function: Pipes transform input data into the desired form.
- Example: Converting a string from a URL parameter (e.g.,
"123") into an integer (123) so the controller method receives the correct type. - Built-in Pipes:
ParseIntPipe,ParseBoolPipe,ParseArrayPipe,ParseUUIDPipe, etc. - Function: Pipes evaluate input data and, if valid, pass it through unchanged. If the data is invalid, they throw an exception.
- Mechanism: When an exception is thrown (usually
BadRequestException), NestJS automatically sends an appropriate error response to the client, and the route handler is never executed. - Tooling: Commonly used with
class-validatorandclass-transformerlibraries to validate complex objects (DTOs) against rules defined with decorators (e.g.,@IsString(),@IsEmail()).
Pipes can be bound at different levels:
- Parameter-scoped: Applied to specific arguments
(e.g.,
@Body(new ValidationPipe())). - Method-scoped: Applied to the entire handler (e.g.,
@UsePipes()). - Controller-scoped: Applied to every handler in a controller.
- Global-scoped: Applied to every route in the application.
Guards are classes annotated with @Injectable() that implement
the CanActivate interface. They have a single responsibility: to determine
whether a given request will be handled by the route handler or not.
Every Guard must implement a canActivate() function. This function receives the
ExecutionContext argument and must return a boolean (or a Promise/Observable
resolving to a boolean).
- True: The request is allowed to proceed to the route handler.
- False: The request is denied, and NestJS automatically throws a
ForbiddenException.
Guards are the primary mechanism for Authentication (identifying the user) and Authorization (checking permissions) in NestJS.
- Extraction: The Guard inspects the incoming request headers (usually looking for a 'Bearer' token or API key).
- Validation: It validates this token (e.g., verifying a JWT signature).
- Context Awareness: Unlike Middleware, Guards have access to the
ExecutionContext. This means they know exactly which route handler is about to be executed. - User Attachment: If the token is valid, the Guard typically attaches
the user profile to the Request object (e.g.,
request.user = validatedUser), making it accessible in the Controller.
Middleware can do authentication, but it is "dumb"—it doesn't know which route handler will
execute next. Guards are "smart"; they can read metadata from the specific route handler
(like @Roles('admin')) to make granular decisions.
Interceptors in NestJS are a powerful feature inspired by Aspect-Oriented
Programming (AOP). They are @Injectable() classes that implement the
NestInterceptor interface.
AOP is a programming paradigm that aims to increase modularity by allowing the separation of cross-cutting concerns. These are functions that span multiple parts of an application (like logging, caching, or error handling) but are not the primary business logic.
How Interceptors Enable AOP:Interceptors have the unique ability to wrap the request/response stream. They can execute logic before a method is called and after it returns, effectively "intercepting" the execution flow.
Key Capabilities:- Bind Extra Logic: run code before and after the execution of a route handler (e.g., logging execution time).
- Transform Results: modify the result returned from a function (e.g.,
wrapping every response in a
{ data: ... }object). - Transform Exceptions: catch and modify exceptions thrown from a function.
- Extend Behavior: extend the basic function behavior without modifying the function code itself.
- Override Execution: completely override a function depending on specific conditions (e.g., returning a cached response instead of calling the handler).
Every interceptor implements the intercept(context, next) method.
context:The execution context (similar to Guards).next:ACallHandlerinterface. Callingnext.handle()invokes the route handler. If you don't call it, the route handler never runs.- RxJS: The handle() method returns an RxJS
Observable. This allows you to use powerful operators likemap(),tap(),catchError(), andtimeout()to manipulate the response stream.
Exception Filters in NestJS are the layer responsible for processing all unhandled exceptions across your application.
When your code throws an exception (e.g.,
throw new HttpException('Forbidden', HttpStatus.FORBIDDEN)), NestJS catches it
and automatically sends a user-friendly JSON response. Exception Filters allow you to
customize this behavior—for example, to log the error to a file, change the
response format, or hide internal server error details.
To handle errors globally, you create a custom filter class and then register it in your
main.ts.
1. Create the Filter Class (http-exception.filter.ts):
This class implements ExceptionFilter and uses the @Catch()
decorator.
2. Register Globally (main.ts):
In your entry file, use useGlobalFilters() to apply it to every route.
Understanding the Request Lifecycle is crucial for debugging and structuring your application logic. NestJS executes these components in a specific sequence for every incoming request.
The Execution Flow:- Incoming Request ??
- Middleware:
- Global Middleware runs first.
- Module Middleware runs next.
- (Purpose: Low-level HTTP handling like logging, parsing headers).
- Guards:
- Global Guards ?? Controller Guards ?? Route Guards.
- (Purpose: Authorization. If a guard fails, the cycle stops here).
- Interceptors (Pre-Controller):
- Global ?? Controller ?? Route.
- (Purpose: Transform request or bind extra logic before the handler).
- Pipes:
- Global ?? Controller ?? Route ?? Route Parameter.
- (Purpose: Validation and transformation of payload/params).
- Route Handler (Controller Method):
- The actual business logic is executed.
- Interceptors (Post-Request):
- Logic inside the
handle().pipe(...)runs (Response mapping). - Exception Filters:
- Catches any errors thrown during the process.
- Server Response ??
| Component | Runs... | Primary Goal |
|---|---|---|
| Middleware | First | Modify Request/Response objects |
| Guards | Second | Auth & Permissions |
| Interceptors | Third (Pre) & Fifth (Post) | AOP, transform stream |
| Pipes | Fourth | Validate & Transform Data |
| Filters | On Error | Handle Exceptions |
Handling custom HTTP exceptions involves two main steps: throwing them (either using built-in classes or creating your own) and filtering them (customizing the response sent to the client).
1. Using Built-in HTTP ExceptionsNestJS provides a set of standard exception classes that cover most common HTTP error scenarios. You simply instantiate and throw them.
BadRequestExceptionUnauthorizedExceptionNotFoundExceptionForbiddenExceptionNotAcceptableException- ...and many more.
If the built-in exceptions aren't specific enough, you can create your own by extending the
base HttpException class. This allows you to encapsulate specific error logic
and messages.
The HttpException constructor takes two arguments:
- Response: A string or an object defining the JSON response body.
- Status: The HTTP status code.
To send a custom JSON structure:
These decorators are used in Controller route handlers to extract specific data from the incoming HTTP request. They provide a clean, declarative way to access client data without manually parsing the raw request object.
1. @Body()
- Purpose: Extracts data from the request body (typically JSON payloads in POST/PUT requests).
- Usage: Used when creating or updating resources. You can extract the entire body or a specific property.
- DTOs: Commonly used with Data Transfer Objects (DTOs) to define the expected shape of the data.
2.@Query()
- Purpose: Extracts query parameters from the URL (the
key-value pairs after the
?). - Usage: Used for filtering, sorting, or pagination (e.g.,
?role=admin&page=2). - Behavior: By default, all query params are strings. You may need
ParseIntPipeorParseBoolPipeto convert them.
3.@Param()
- Purpose: Extracts route parameters from the URL path
(segments defined with a colon
:). - Usage: Used to identify a specific resource (e.g.,
GET /users/:idwhere :idis the parameter). - Dynamic: Enables dynamic routing based on IDs or slugs.
Here is a controller method that demonstrates all three in a single request (e.g.,
PUT /users/50?role=admin with a JSON body):
To implement validation, you combine Data Transfer Objects (DTOs) with the
class-validator library. This allows you to define rules using decorators
directly in your class definition, and NestJS will automatically enforce them using a Pipe.
Install the necessary packages:
Step 1: Create a DTO with Validation DecoratorsDefine a class representing the data you expect. Decorate properties with rules like
@IsString, @IsInt, or @IsEmail.
You must register the ValidationPipe globally (in main.ts) or
locally (in the controller) for the validation to actually run.
Type-hint your route parameter with the DTO class. NestJS will now validate the incoming body against the rules before executing the handler.
A Custom Decorator is a reusable function you define to simplify extracting data or applying
logic in your controllers. While NestJS provides built-in decorators like
@Body() or @Query(), custom decorators allow you to create your
own semantic shortcuts (e.g., @User(), @AuthToken(), or
@DeviceType()).
- Abstraction: Hide complex logic (like parsing headers or extracting a
user from
request.user). - Readability: Make controller method signatures cleaner and more descriptive.
- Reusability: Apply the same logic across multiple controllers without code duplication.
You use the createParamDecorator function provided by
@nestjs/common.
@User() Decorator
Scenario: You want to extract the currently authenticated user (attached to the request by a Guard) directly in your controller method.
Step 1: Define the Decorator file (user.decorator.ts)
Step 2: Use it in a Controller
Integrating TypeORM allows you to interact with SQL databases using TypeScript classes
(Entities) instead of writing raw SQL queries. NestJS provides a dedicated package,
@nestjs/typeorm, to make this seamless.
You need the NestJS wrapper, the TypeORM core, and the driver for your specific database
(e.g., mysql, postgres, sqlite).
In your app.module.ts, import TypeOrmModule and configure it
with your database connection details using .forRoot().
Define a class that maps to a database table.
Step 4: Register Entity in Feature ModuleTo use the User entity in your UsersModule, you must register
it using .forFeature().
Now you can inject the standard TypeORM repository into your service to perform CRUD operations.
The @InjectRepository() decorator is specific to
the @nestjs/typeorm package. It is used in the constructor
of a service to inject a TypeORM Repository instance for a specific Entity.
In standard NestJS Dependency Injection, you usually inject a class by its type (e.g.,
private userService: UserService). However, TypeORM repositories
are generic classes (Repository).
- If you just asked for
Repository, NestJS wouldn't know which entity's repository you want (User? Product? Order?). @InjectRepository(Entity)generates a unique injection token based on the Entity you pass to it, allowing NestJS to resolve the correct repository instance.
- Registration: When you add
TypeOrmModule.forFeature([User])in a module, NestJS creates a provider for theUserrepository. - Injection: In your service, you use
@InjectRepository(User). - Resolution: NestJS matches the token from the decorator with the
provider created in the module and injects the
Repositoryinstance.
Yes, absolutely. NestJS has first-class support for MongoDB through the Mongoose object
modeling tool. It provides a dedicated package, @nestjs/mongoose, that
wraps the standard Mongoose library to make it compatible with NestJS's dependency
injection and modular architecture.
In your root AppModule, import the MongooseModule and use the
.forRoot() method to establish a connection.
Instead of the traditional Mongoose schema syntax, NestJS allows you to use Classes and Decorators to define schemas, which is much cleaner and Type-safe.
Step 4: Register the ModelIn your feature module (e.g., CatsModule), you must register the schema to
inject it later.
Use the @InjectModel() decorator in your service to get the Mongoose model.
A Data Transfer Object (DTO) is an object that defines how data is sent over the network between different parts of your application (e.g., from the client to the server). In NestJS, DTOs are typically defined as Classes.
Why are they necessary?- Data Validation:
- DTOs act as a contract. By combining them with decorators from
class-validator, you can automatically validate incoming data (e.g., checking if an email is valid or a password is strong) before it even reaches your controller logic. - Type Safety:
- They provide TypeScript type definitions, ensuring that your code knows exactly what properties exist on the incoming request body. This enables IntelliSense and catches errors at compile time.
- Security (Filtering):
- They allow you to whitelist only the properties you expect. If a malicious user
sends extra fields (e.g.,
isAdmin: true), a configuredValidationPipecan automatically strip them out, preventing mass assignment vulnerabilities. - Documentation:
- If you use Swagger/OpenAPI, NestJS automatically reads your DTO classes to generate API documentation and request schemas.
While TypeScript interfaces are removed during compilation, Classes are preserved as real values at runtime. NestJS relies on this runtime metadata to perform validation and transformation; therefore, always use Classes for DTOs in NestJS.
Code Example:Database migrations are essential for version-controlling your database schema. They allow you to evolve your database structure (e.g., creating tables, adding columns) over time without losing data.
Since NestJS is agnostic, the specific method depends on the ORM you use. The most common approach is using TypeORM.
Steps for TypeORM Migrations:- Configuration (DataSource):
- Add Scripts to package.json:
- Generate a Migration:
- Run the Migration:
- Revert (Optional):
You must create a DataSource configuration file (e.g.,
data-source.ts) specifically for the CLI to use. This file tells
TypeORM where your entities and migrations are located.
To simplify running commands, add these scripts:
After changing your Entity class (e.g., adding a phoneNumber column),
run the generation command. TypeORM compares your code to the current database state
and generates a SQL file.
Output: A file like 1678900000000-AddPhoneNumber.ts is created with
up() and down() methods.
Execute the pending migrations to update the database schema.
If something goes wrong, you can undo the last migration.
Managing transactions ensures data integrity—either all operations succeed, or none do
(ACID compliance). In NestJS with TypeORM, there are two primary ways
to handle this: using the DataSource transaction callback
(simpler) or using a QueryRunner (more control).
This method gives you full manual control over when to start, commit, and rollback the transaction.
Steps:- Inject the
DataSource:You need the data source to create a query runner. - Create & Connect: Initialize the
QueryRunnerand connect to the database. -
Start Transaction: Explicitly begin the transaction block. - Try/Catch/Finally:
- Try: Execute your database operations. Crucial: You must use
the
queryRunner.managerto execute queries, not your standard repository. - Commit: If successful, save changes permanently.
- Catch: If an error occurs,
rollbackchanges to the previous state. Finally:Alwaysreleasethe query runner to free up the connection.
NestJS (via TypeORM) supports two primary patterns for interacting with databases: Repository and Active Record. The key difference lies in where the data access logic resides.
1. Repository Pattern (Recommended for NestJS)- Philosophy: Entities are "dumb" data structures. All data access logic (finding, saving, deleting) is separated into a dedicated Repository class.
- Structure: You inject the repository into your service.
- Pros: Promotes Separation of Concerns (SoC) and makes testing easier (you can mock the repository). It scales better for complex applications.
- Philosophy: Entities are "smart." They contain both data and the methods to manage that data (save, remove, find).
- Structure: The Entity class extends
BaseEntity. You call static methods directly on the Entity class. - Pros: Simpler and less verbose for small applications. It resembles the pattern used in frameworks like Ruby on Rails or Laravel.
| Feature | Repository Pattern | Active Record Pattern |
|---|---|---|
| Logic Location | Dedicated Repository Class | Within the Entity Class |
| Entity Role | Pure Data Structure | Data + Behavior |
| Dependency Injection | Highly Compatible | Less Compatible |
| Testability | High (Easy to mock) | Lower (Harder to mock static methods) |
| Use Case | Complex / Large Enterprise Apps | Simple / Small Apps |
The @Entity() decorator is the fundamental building block in TypeORM (and
NestJS database integration) that maps a TypeScript class to a database table.
When you annotate a class with @Entity(), TypeORM treats instances of that
class as rows in a specific table. By default, the table name is generated from the
class name (e.g., User class becomes user table).
You can pass an options object to the decorator to customize the table name, database engine, schema, etc.
- Custom Table Name:
- Database Engine (MySQL specific):
- Schema (PostgreSQL specific):
- Synchronization Control:
You can disable schema synchronization for specific entities (useful for views or legacy tables you don't want TypeORM to modify).
Database Seeding is the process of populating your database with an initial set of data (e.g., admin accounts, lookup tables, or default settings).
There are two common ways to handle this in NestJS:
Method 1: Lifecycle Hooks (Simplest)Use the OnApplicationBootstrap interface. This runs the seeding logic every
time the application starts. This is best for ensuring essential data (like an "Admin"
role) always exists.
Create a separate script (e.g., seed.ts) that bootstraps a minimal NestJS
context, runs the logic, and then exits. This prevents seeding logic from slowing down
your production server startup.
seed.ts):
Run it with: npx ts-node seed.ts
A Circular Dependency occurs when two classes depend on each other. For
example, Service A needs Service B, and Service B
needs Service A. NestJS cannot resolve this dependency graph natively and
will throw an error during instantiation.
To resolve this, we use Forward Referencing.
The Solution:forwardRef()
The forwardRef() utility allows NestJS to reference classes that haven't
been defined yet. You must use it on both sides of the relationship.
@Inject() with forwardRef() in
Services
Service A (cats.service.ts):
dogs.service.ts):If the circular dependency exists between Modules (e.g.,
CatsModule imports DogsModule and vice-versa), you must also
use forwardRef() in the module imports.
Note: While forwardRef solves the immediate error, circular
dependencies are often a "code smell." If possible, try to refactor your code to extract
the shared logic into a third, independent service to break the cycle.
NestJS is designed to be a transport-agnostic framework. This means that while you can build standard HTTP (REST) applications, you can also easily switch to or add a microservices layer using various communication protocols without changing your core business logic.
Key Concepts:- Transporters:
- TCP: The default protocol.
- Redis / MQTT / NATS: Message brokers for pub/sub patterns.
- RabbitMQ / Kafka: For robust, asynchronous message queuing.
- gRPC: For high-performance, typed communication.
- Creation:
- Communication Patterns:
- Request-Response: The requester expects a reply (used for queries).
Use the
@MessagePattern()decorator. - Event-Based: "Fire and forget." The requester just emits an event
(used for notifications or state updates). Use the
@EventPattern()decorator.
NestJS provides built-in support for several "transporters" that handle communication between services:
Instead of NestFactory.create(), you use
NestFactory.createMicroservice() to bootstrap a service.
Client-Side Communication:
To talk to another microservice from within a service, you use
the ClientProxy class, which is injected using ClientsModule.
NestJS is transport-agnostic, meaning it separates the application logic from the network layer. It provides several built-in transporters that allow microservices to communicate over different protocols.
Available Transporters- TCP: * Nature: Default transport.
- Use Case: Simple, direct communication between services within the same network. It is reliable but synchronous in nature.
- Redis:
- Nature: Uses the Redis Pub/Sub mechanism.
- Use Case: Lightweight message broadcasting. Great for high-throughput scenarios where message persistence isn't the primary concern.
- MQTT:
- Nature: Message Queuing Telemetry Transport.
- Use Case: Ideal for IoT (Internet of Things) or scenarios with limited bandwidth and high latency.
- RabbitMQ:
- Nature: Uses the AMQP protocol.
- Use Case: Robust, enterprise-grade messaging. Supports complex routing, message acknowledgment, and high reliability with persistence.
- NATS:
- Nature: Cloud-native messaging system.
- Use Case: Extremely fast and lightweight. Often used in modern distributed systems and sidecar patterns.
- Kafka:
- Nature: Distributed streaming platform.
- Use Case: High-volume data processing and event sourcing. Best when you need to process vast amounts of data with long-term retention.
- gRPC:
- Nature: High-performance, open-source RPC framework using Protocol Buffers.
- Use Case: Strictly typed, cross-language communication where performance and low latency are critical.
To use a specific transporter, you define it in the options object during
bootstrap!
In NestJS Microservices, communication typically falls into two distinct categories based on whether the sender expects an answer or is just broadcasting information.
1. Request-Response Pattern:- This pattern is used when the client needs a specific response from the microservice to continue its work. It is similar to a traditional HTTP call.
- Decorator:
@MessagePattern() - Behavior: The sender (ClientProxy) uses the
.send()method, which returns an Observable. This observable will emit the result once the microservice finishes processing. - Use Case: Ideal for operations that retrieve data or perform a calculation (e.g., calculating a total, fetching a user profile, or verifying a stock level).
This is a "fire-and-forget" mechanism. The client emits an event and does not wait for any data to be returned.
- Decorator:
@EventPattern() - Behavior: The sender uses the
.emit()method. Since there is no return value, the client doesn't block its execution waiting for a response. - Use Case: Perfect for notifications, logging, updating analytics, or triggering background tasks where the success of the task doesn't immediately affect the current request flow.
| Feature | Request-Response | Event-based |
|---|---|---|
| Decorator | @MessagePattern() |
@EventPattern() |
| Method | client.send() |
client.emit() |
| Wait for reply? | Yes | No |
| Logic Type | Commands / Queries | Notifications / State changes |
WebSockets enable real-time, bidirectional, event-based communication between the client and the server. In NestJS, this is handled via Gateways, which are classes decorated with @WebSocketGateway().
1. InstallationNestJS supports two main libraries for WebSockets: Socket.io and ws. Socket.io is the more common choice due to its built-in features like auto-reconnection and rooms.
2. Creating a GatewayA gateway is essentially a provider that can handle incoming messages and broadcast outgoing ones.
3. Key Decorators & Interfaces@WebSocketGateway(): Marks the class as a gateway. You can pass options likeport,namespace, andcors.@WebSocketServer(): Injects the native platform-specific server instance (e.g., the Socket.io server).@SubscribeMessage(): Binds a specific event name to a method.@MessageBody(): Extracts the payload sent by the client.@ConnectedSocket(): (Optional) Injects the specific client socket instance that sent the message.- Lifecycle Hooks:
OnGatewayInit,OnGatewayConnection, andOnGatewayDisconnecthelp manage the socket lifecycle.
Once created, you must add the Gateway to the providers array of a module
(e.g., AppModule or a specific ChatModule).
NestJS provides task scheduling via the @nestjs/schedule package, which is built
on top of the popular cron library. This allows you to execute code at specific
intervals, fixed dates, or recurring times.
First, install the required dependencies:
2. Enable SchedulingTo enable task scheduling, import the ScheduleModule into your root
AppModule using the .forRoot() method.
You can define scheduled tasks within any provider (Service) using the @Cron()
decorator.
- Standard Cron Patterns: You can use the standard 6-digit cron syntax (seconds, minutes, hours, day of month, month, day of week).
- CronExpression Enum: Nest provides a
CronExpressionenum with human-readable constantslike EVERY_DAY_AT_MIDNIGHTorEVERY_5_SECONDS. - Intervals (
@Interval): Runs the method exactly every X milliseconds after the app starts. - Timeouts (
@Timeout): Runs the method once after a specified delay.
Dynamic Schedules
If you need to create, stop, or delete cron jobs dynamically at runtime (e.g., based on
user input), you can inject the SchedulerRegistry into your service to
manage jobs by name.
In NestJS, modules are encapsulated by default. This means you cannot use a provider (like a Service) in another module unless you explicitly export it from its host module and import that module into the consuming one.
The @Global() decorator changes this behavior by making a module and its exported providers available everywhere in your application without needing to import it into other modules.
When to use @Global()?You should use it sparingly for "utility" or "core" features that are required by almost every other module in your system. Common examples include:
- Database Connections (e.g., a shared TypeORM or Mongoose module).
- Configuration Services (e.g., a service that reads
.envfiles). - Logging Services.
- Common Helpers/Utilities.
The @Global() decorator must be applied to the class definition of the module
you want to make global.
- Export is still required: Even if a module is
@Global(), only the providers listed in theexportsarray will be accessible to other modules. - Only one global instance: You should define a global module only once, typically in the root or a core directory.
- Avoid over-use: Over-using
@Global()makes your dependency graph harder to understand and can lead to naming collisions. It "pollutes" the global namespace and makes your code less modular and harder to test in isolation.
In NestJS, environment variables are managed using the @nestjs/config package.
This module provides a clean way to load .env files and access their values
throughout your application using a dedicated service.
First, install the package:
2. Basic SetupTo load your .env file, import the ConfigModule in your root
AppModule. It is recommended to use the isGlobal: true property so
you don't have to import it in every other module.
Once registered, you can inject the ConfigService into any provider or
controller to retrieve values using the .get() method.
- Validation: You can use
Joito validate your environment variables at startup, ensuring the app won't run if critical keys (likePORTorDATABASE_URL) are missing. - Custom Configuration Files: Instead of just a flat
.envfile, you can load nested TypeScript/JavaScript objects for better organization. - Expansion: It supports variable expansion
(e.g.,
APP_URL=${PROTOCOL}://${HOST}).
main.ts
If you need to access a variable in your entry point (like the port number for
app.listen()), you can get the service from the app instance:
NestJS provides a built-in module for handling file uploads based on the Multer middleware. It uses decorators to intercept requests and extract uploaded files.
1. InstallationThe necessary types for Multer are required for TypeScript support:
2. Basic Single File UploadTo handle a single file, you use the FileInterceptor() and the
@UploadedFile() decorator.
- Multiple files with the same key: Use
FilesInterceptor(). - Multiple files with different keys: Use
FileFieldsInterceptor().
By default, Multer stores files in memory. To save them to the disk with specific names, you can configure the storage engine.
5. ValidationYou can use the ParseFilePipe to enforce rules like file size or allowed MIME
types (e.g., only images).
The DiscoveryService is a specialized utility provided by the
@nestjs/core package. It allows you to "discover" or scan all the active
providers, controllers, and enhancers (like guards or interceptors) within your application
at runtime.
While NestJS usually handles the dependency graph for you, there are advanced scenarios where you need to manually inspect your application’s internal structure:
- Building Custom Decorators with Logic: If you create a custom decorator
(e.g.,
@CronJob()or@LogMe()), you use the DiscoveryService to find every method in your app that has been tagged with that decorator so you can execute specific logic on them. - Automatic Registration: Automatically registering handlers or hooks without the user having to manually add them to a module's array.
- Creating Plugins or Libraries: If you are building a library for others to use, you might use it to find specific classes that implement a certain interface or use a specific decorator.
To use it, you must first install and import the DiscoveryModule.
You typically combine the DiscoveryService with the MetadataScanner
to look inside the classes you find.
The DiscoveryService is a powerful "meta-programming" tool. It is rarely used in
standard CRUD applications but is essential when building frameworks, internal tools, or
highly dynamic systems where you need to "see" what classes exist in the NestJS container.
NestJS offers a powerful integration with GraphQL via the @nestjs/graphql
package. It supports two distinct ways to build your API: Code-first and
Schema-first.
In this approach, you use TypeScript decorators to define your data models and resolvers. NestJS automatically generates the GraphQL SDLC (Schema Definition Language) file for you at runtime.
- Pros: Single source of truth (your TypeScript classes), better integration with TypeScript types, and less boilerplate.
- Ideal for: Most modern TypeScript projects.
In this approach, you write the raw GraphQL SDL (.graphql
files) first. NestJS then maps these definitions to your TypeScript classes/interfaces.
- Pros: Language-agnostic schema, clear contract for frontend/backend teams before coding begins.
- Ideal for: Teams coming from other languages or those who prefer strictly following the GraphQL specification manually.
Regardless of the approach, you start by installing the core dependencies:
Then, configure the GraphQLModule in your AppModule:
The Resolver is the equivalent of a Controller in REST. It provides the logic to fetch the data for specific GraphQL fields.
NestJS is built with testability in mind. By default, every project comes pre-configured with Jest. The core philosophy of unit testing a service is to test the class in isolation by mocking its dependencies (like Repositories or other Services).
1.The Test.createTestingModule Utility
Instead of manually instantiating classes with new, Nest provides a
Test utility that creates a mock NestJS IoC (Inversion of Control) container.
This allows you to "fake" the providers your service needs.
When you generate a service using the CLI (nest g s users), a
.spec.ts file is created automatically. Here is how you structure it:
beforeEach: Sets up a fresh testing module before every individual test to ensure state doesn't leak between tests.getRepositoryToken(Entity): Since Repositories are injected using a special token, you must use this helper to tell the testing module which mock to associate with which Entity.jest.fn(): Creates a "spy" or "mock function." It allows you to define what the function should return (mockResolvedValue) and track if it was called (toHaveBeenCalled).useValue vs useClass:useValue: Use this for a simple object containing mock functions (as seen above).useClass: Use this if you have a dedicated "MockService" class.
Run your unit tests using the following command in your terminal:
To run in "watch mode" while you develop:
While unit tests focus on individual classes, End-to-End (E2E) testing validates the entire application flow—from the incoming HTTP request through the guards, controllers, and services, all the way to the response. NestJS uses Supertest to simulate these HTTP requests without actually binding to a network port.
1. The E2E Test EnvironmentE2E tests are typically located in the test/ folder of your project. They use a
similar setup to unit tests but bootstrap the entire AppModule.
A standard E2E test file (app.e2e-spec.ts) looks like this:
To test creating resources, you can send payloads using the .send() method.
Running E2E tests against your production database is dangerous. There are two common strategies:
- Mocking Providers: Use
.overrideProvider()to replace the real Database service or Repository with a mock version. - Test Database: Point your
ConfigModuleto a dedicated.env.testfile that uses a temporary database (like SQLite in-memory) which is wiped after each run.
Use the dedicated script provided in your package.json:
By default, NestJS loads all modules eagerly when the application starts. Lazy Loading is a technique where a module is only loaded and instantiated at the moment it is actually needed (on-demand), rather than during the initial bootstrap process.
How it Helps- Reduced Start-up Time: In large applications with hundreds of modules, loading everything at once can significantly delay the "ready" state. Lazy loading allows the core app to start instantly.
- Memory Optimization: Modules that are rarely used (e.g., a complex report generator or a specific admin tool) won't consume memory until a user actually triggers them.
- Serverless Efficiency: For serverless environments (like AWS Lambda or Google Cloud Functions), lazy loading helps reduce "cold start" times by minimizing the initial code execution required to handle a request.
To lazy load a module, you use the LazyModuleLoader class. This is typically
done within a service or a controller.
- Encapsulation: Even when lazy-loaded, modules still respect NestJS
encapsulation rules. You can only access providers that the lazy module explicitly
exports. - WebSockets/Microservices: Currently, lazy loading is primarily designed
for the HTTP context. You cannot lazy load modules that register
GatewaysorMicroservicelisteners, as those need to be established during the initial binding phase. - Manual Control: Unlike frontend frameworks (like Angular) where lazy loading is often route-based and automatic, in NestJS, you must manually trigger the loading logic. Summary
Lazy loading is an advanced optimization tool. While not necessary for most standard REST APIs, it is a game-changer for modular monoliths or high-scale serverless functions where startup performance and resource footprint are critical.
Caching is a powerful technique to improve application performance by storing the results of
expensive operations (like complex database queries or external API calls) in temporary
storage for fast retrieval. NestJS provides a unified interface for various caching engines
via the cache-manager package.
Install the required packages. By default, NestJS uses an in-memory cache, but you can also use Redis or other stores.
2. Global SetupImport the CacheModule in your root AppModule. Using
isGlobal: true makes the caching service available throughout your app.
The easiest way to cache entire GET requests is by using the CacheInterceptor.
You can apply it to a single route, an entire controller, or globally.
For more granular control (e.g., caching a specific database query result), you can inject
the CACHE_MANAGER directly into your service.
For production environments, in-memory cache is often insufficient because it's wiped when the server restarts and isn't shared across multiple server instances.
Change your AppModule configuration:
Securing your application against common web vulnerabilities is a critical step before deployment. Two of the most essential tools for this in the NestJS ecosystem are Helmet and CORS.
1. Helmet: Protecting HTTP HeadersHelmet is a collection of middleware functions that set various HTTP headers to protect your app from well-known web vulnerabilities like Cross-Site Scripting (XSS), clickjacking, and packet sniffing.
Installation: Implementation:Since Helmet is standard middleware, it is best applied in your main.ts file so
it covers every incoming request.
CORS is a security mechanism that allows or restricts requested resources on a web page to be requested from another domain outside the domain from which the first resource was served. By default, NestJS (and most browsers) block cross-origin requests for security.
Implementation:You can enable CORS in two ways:
Option A: Simple Enable (All origins allowed - NOT recommended for production) Option B: Detailed Configuration (Recommended)Restrict access to specific domains and methods to prevent unauthorized sites from making requests to your API.
3. Summary of Protections| Feature | Provided by | Benefit |
|---|---|---|
| XSS Protection | Helmet | Prevents attackers from injecting malicious scripts. |
| Content Security Policy | Helmet | Restricts where resources (scripts/images) can be loaded from. |
| Hide Powered-By | Helmet | Removes the X-Powered-By: Express header to hide your tech
stack. |
| Origin Whitelisting | CORS | Ensures only your trusted frontend can talk to your backend. |
| Credential Control | CORS | Manages whether browsers should send sensitive info like JWTs/Cookies. |
In NestJS, the Reflector class is a helper utility used to retrieve
custom metadata that you’ve attached to controllers or specific route
handlers (methods) using decorators like @SetMetadata() or custom-built
decorators.
Guards and Interceptors are generic. For example, an AuthGuard doesn't
inherently know if a route should be "Public" or "Admin-only" just by looking at the
request. The Reflector allows the Guard to "look up" at the class or method
it is currently processing and read the configuration rules you've assigned to it.
You can use the built-in @SetMetadata() or create a custom decorator (cleaner).
Apply it to a controller method:
2. Retrieving Metadata with ReflectorInside your Guard, you inject the Reflector and use it to pull the metadata
associated with the current execution context.
reflector.get(key, target): Retrieves metadata for a specific key.reflector.getAllAndOverride(key, [targets]): Retrieves metadata and "overrides" the class-level metadata if method-level metadata exists. (Commonly used for "Public" vs "Private" flags).reflector.getAllAndMerge(key, [targets]): Combines metadata from both the class and the method into a single array.
The Reflector is the bridge between declarative programming
(decorators) and imperative logic (Guards/Interceptors). It allows you to
write smart, reusable logic that adapts based on the "tags" you put on your routes.
NestJS includes a built-in Logger service that provides a consistent way to track application behavior, debug issues, and monitor health in production. By default, it outputs logs to the system console with helpful color-coding and timestamps.
1. Using the Logger in a ClassThe most common way to use the logger is to instantiate it within your service or controller. It is recommended to pass the class name as a context so you can easily identify where a log message originated.
2. Global ConfigurationYou can control the logging behavior globally in your main.ts. This allows you
to restrict certain log levels (like debug or verbose) in
production to keep your logs clean.
For advanced application monitoring, you often need to send logs to external services (like
ELK Stack, Datadog, or CloudWatch) instead of just the console. You can implement the
LoggerService interface to create a custom logger.
- Install:
npm install --save nest-winston winston - Apply:
- Searchability: By including contexts and structured data, you can
search for logs related to specific services (e.g.,
[UsersService]). - Filtering: You can turn off verbose debugging logs in production without changing your code.
- Standardization: It ensures every developer on your team logs information in the same format, making troubleshooting much faster.
Dockerizing a NestJS application ensures that your app runs in a consistent environment across development, staging, and production. To do this efficiently, we use a multi-stage build.
1. Creating the DockerfileA multi-stage build is the best practice because it keeps the final image small by excluding
development dependencies and source code, only including the compiled dist
folder.
Create a file named Dockerfile in your root directory:
To prevent large or sensitive folders (like node_modules or .git)
from being sent to the Docker daemon, create a .dockerignore file:
Note: The -p 3000:3000 flag maps your machine's port 3000 to the container's
port 3000.
Most NestJS apps need a database. Docker Compose allows you to spin up both the app and the database (like PostgreSQL) with a single command.
Exampledocker-compose.yml:
Summary of Benefits
- Isolation: No "it works on my machine" issues.
- Scalability: Easily deploy multiple instances behind a load balancer.
- CI/CD: Simplifies the pipeline as the build artifact is a standard Docker image.
Standalone Mode allows you to use the NestJS IoC (Inversion of Control) container and its dependency injection system without starting an HTTP server or a microservice listener.
In this mode, you bootstrap the application context just to access its providers. This is incredibly useful for tasks that don't require a continuous network connection but still need the logic stored within your services.
Common Use Cases- CRON Jobs: Running a script that executes once and then exits.
- Database Migrations/Seeding: Running scripts to update your database schema or populate initial data.
- CLI Tools: Building custom command-line interfaces that leverage your existing business logic.
- One-off Scripts: Performing maintenance tasks, like cleaning up old logs or generating a one-time report.
To use Standalone Mode, you use the NestFactory.createApplicationContext()
method instead of the standard .create().
- Full DI Support: You can inject any service, repository, or provider
defined in your
AppModule. - Lifecycle Hooks: The
onModuleInitandonApplicationBootstraphooks are still triggered when the context is created. - Lightweight: Because it doesn't bind to a port or set up a web server (Express/Fastify), it consumes fewer resources and starts faster.
- Graceful Shutdown: Using
app.close()ensures that database connections and other open handles are properly terminated.
As your application grows, you will inevitably need to introduce breaking changes without disrupting existing users. NestJS provides built-in support for API Versioning, allowing you to run multiple versions of your controllers or routes simultaneously.
1. Enabling VersioningVersioning is disabled by default. You enable it in main.ts and choose a
Versioning Strategy.
| Strategy | Usage | Example URL / Request |
|---|---|---|
| URI | Version is part of the path. | https://api.com/v1/users |
| Header | Version is specified in a custom header. | x-api-version: 2 |
| Media Type | Version is part of the Accept header. |
Accept: application/json; v=2 |
Once enabled, you use the @Version() decorator. You can apply it to an entire
controller or to specific methods.
All routes within this controller will be prefixed with v1.
This is useful for evolving a single endpoint while keeping the rest of the controller the same.
4. Advanced Versioning Concepts- Neutral Versioning: Use
VERSION_NEUTRALif a specific route should stay the same regardless of the version requested. - Multiple Versions: A route can support multiple versions
simultaneously:
@Version(['1', '2']). - Global Prefixing: If you use a global prefix (like
app.setGlobalPrefix('api')), the version typically appears after the prefix (e.g.,/api/v1/users).
Versioning ensures backward compatibility.
URI Versioningis the most common and "browsable."- Header Versioning is cleaner for URLs but harder to test in a browser.
- Media Type is strictly following REST best practices (HATEOAS).