Documentation Index
Fetch the complete documentation index at: https://resources.devweekends.com/llms.txt
Use this file to discover all available pages before exploring further.
Chapter 1: NestJS Fundamentals
NestJS is a progressive Node.js framework for building efficient, reliable, and scalable server-side applications. Think of it as the “Angular for the backend”—it brings structure, modularity, and best practices to Node.js development, making it ideal for both startups and enterprises.
1.1 Introduction to NestJS
NestJS provides a robust foundation for building enterprise-grade applications. It leverages TypeScript by default, supports modular architecture, and encourages best practices such as dependency injection, testability, and separation of concerns.Why NestJS?
TypeScript-first approach:- TypeScript provides compile-time type checking, catching errors before runtime
- Better IDE support with autocomplete and refactoring tools
- Self-documenting code through type annotations
- Gradual adoption—you can use JavaScript, but TypeScript is recommended
- Code is organized into logical modules, making it easy to scale and maintain
- Each module encapsulates related functionality (controllers, services, providers)
- Clear separation of concerns makes testing straightforward
- Modules can be easily swapped or replaced without affecting the entire application
- Write loosely coupled, easily testable code
- Dependencies are injected rather than hard-coded, enabling better unit testing
- Supports different provider scopes (singleton, request-scoped, transient)
- Makes it easy to mock dependencies in tests
- Build REST APIs, GraphQL endpoints, WebSockets, and microservices—all with the same framework
- Extensive ecosystem with official and community packages
- Integrates seamlessly with popular libraries (Express, Fastify, TypeORM, Prisma, etc.)
- Supports multiple transport layers for microservices (TCP, Redis, RabbitMQ, NATS, gRPC)
- Similar decorators, modules, and dependency injection patterns
- If you know Angular, NestJS will feel natural
- Shared concepts reduce learning curve
- Built-in support for validation, transformation, and serialization
- Exception filters for consistent error handling
- Guards for authentication and authorization
- Interceptors for cross-cutting concerns (logging, caching, transformation)
- Pipes for data transformation and validation
Imagine building with Lego blocks—each block (module, controller, service) has a clear purpose and fits together to create a robust structure. NestJS gives you the blueprint and the blocks, ensuring everything fits perfectly.
When to Use NestJS
Perfect for:- Enterprise applications requiring structure and maintainability
- Large teams where consistency matters
- Applications that need to scale horizontally
- Projects requiring extensive testing
- Microservices architectures
- Applications with complex business logic
- Building simple CRUD APIs (Express might be simpler)
- Prototyping quickly (though NestJS CLI helps here)
- Team has no TypeScript experience (learning curve)
1.2 Setting Up Your Environment
Before you begin, ensure you have the necessary tools installed. NestJS requires Node.js and a package manager.Prerequisites
Node.js:- Minimum version: Node.js 16.x or higher
- Recommended: Node.js 18.x LTS or 20.x LTS
- Check your version:
node -v
- npm (comes with Node.js)
- yarn (alternative, faster)
- pnpm (alternative, disk-efficient)
- NestJS CLI will set this up for you
- Understanding TypeScript basics helps significantly
Installing the NestJS CLI
The NestJS CLI is a powerful tool that generates boilerplate code, helping you maintain consistency and save time.10.0.0).
Creating Your First Project
Let’s create a simple NestJS application to understand the structure:- npm
- yarn
- pnpm
Hello World!
Development Mode:
start:dev runs the app in watch mode, automatically restarting when you make changes. This is perfect for development. For production, use start:prod which builds and runs the optimized version.Understanding the Project Structure
Let’s examine what the CLI generated:main.ts - Application Entry Point:
- Bootstraps the NestJS application
- Creates the NestFactory instance
- Configures global pipes, filters, guards, interceptors
- Starts the HTTP server
- This is where your app “starts”
app.module.ts - Root Module:
- The root module of your application
- Imports other modules
- Declares controllers and providers
- Every NestJS app has at least one module (the root module)
app.controller.ts - Controller:
- Handles incoming HTTP requests
- Defines routes and HTTP methods
- Delegates business logic to services
- Returns responses to clients
app.service.ts - Service:
- Contains business logic
- Can be injected into controllers or other services
- Should be stateless (no instance variables that change)
- Handles data processing, validation, and orchestration
1.3 Understanding the Core Concepts
NestJS is built around a few core building blocks. Understanding these concepts is crucial for building effective NestJS applications.1.3.1 Modules
Modules are like departments in a company. The HR department (UsersModule) handles everything about employees, the Finance department (PaymentsModule) handles money, and the IT department (SharedModule) provides services everyone needs like email and logging. Each department has its own staff (providers), its own reception desk (controllers), and it decides what services it makes available to other departments (exports). Every app has at least one root module (AppModule), which is like the company’s CEO office — it knows about all the departments and coordinates them.
Why Modules Matter:
- Organization: Group related functionality together
- Encapsulation: Control what’s exposed to other modules — a module’s providers are private by default, just like a department’s internal processes
- Reusability: Modules can be imported and reused across different applications
- Testing: Test modules in isolation, since each module is self-contained
controllers: Array of controllers that handle HTTP requests for this module. These are automatically registered as route handlers.providers: Services, repositories, factories, or values that can be injected. These are private to the module by default.imports: Other modules whose exported providers you want to use. Think of this as declaring your dependencies — “I need what the AuthModule offers.”exports: Providers from this module that should be available to other modules. If you forget to export, other modules will get a runtime error when they try to inject your provider.
UsersModule, AuthModule, OrdersModule).
Example: Feature Module Structure:
1.3.2 Controllers
Controllers are like the front desk at a hotel. When a guest (HTTP request) arrives, the front desk greets them, figures out what they need (parses the route, query params, body), calls the right department to handle it (delegates to a service), and then relays the answer back to the guest (sends the response). The front desk never cleans the room itself — that is the service’s job. Controller Responsibilities:- Define routes (URLs and HTTP methods)
- Extract data from requests (params, query, body, headers)
- Validate input (using DTOs and validation pipes)
- Call services to handle business logic
- Return responses (JSON, status codes)
- Handle errors (throw exceptions)
@Controller('route')- Defines the base route for all methods in this controller@Get(),@Post(),@Put(),@Delete(),@Patch()- HTTP method decorators@Param('name')- Extract route parameter@Query('name')- Extract query parameter@Body()- Extract request body@Headers('name')- Extract header value
1.3.3 Providers & Services
Providers are classes that can be injected as dependencies. Think of the NestJS DI container as a service desk at a large office: you submit a request (“I need a UsersService”), and the service desk either hands you the existing one (singleton) or creates a fresh one (transient/request-scoped). You never walk into the back room and build it yourself. Services are the most common type of provider. They contain the business logic that controllers delegate to. But providers can also be repositories, helpers, configuration objects, or anything else your code needs. What Makes a Provider:- Any class decorated with
@Injectable()— this decorator tells NestJS “you can manage my lifecycle and inject me” - Can be injected into controllers or other providers via constructor parameters
- Managed by NestJS dependency injection container — you never call
newyourself - Can have different scopes (singleton, request-scoped, transient) depending on your needs
- Keep services focused on a single responsibility
- Make services stateless when possible (no mutable instance variables)
- Use services for business logic, not HTTP concerns
- Services can depend on other services (inject them in constructor)
Think of a restaurant: the client is the customer, the controller is the waiter (takes orders, serves food), the service is the chef (prepares the meal), and the repository/database is the pantry (stores ingredients).
NestJS Building Blocks at a Glance
Before diving deeper, here is a quick reference for how the core building blocks relate:| Building Block | Responsibility | Decorator | Registered In | Injected Via |
|---|---|---|---|---|
| Module | Organizes related code, defines boundaries | @Module() | imports array of parent module | N/A (imported, not injected) |
| Controller | Handles HTTP requests, defines routes | @Controller() | controllers array of its module | Constructor (services) |
| Service | Business logic, orchestration | @Injectable() | providers array of its module | Constructor (other providers) |
| Repository | Data access abstraction | @Injectable() | providers array of its module | Constructor (ORM repositories) |
| Guard | Authentication / authorization gate | @Injectable() | Applied via @UseGuards() | Constructor (services) |
| Pipe | Validation / transformation of input | @Injectable() | Applied via @UsePipes() or per-param | Constructor (services) |
| Interceptor | Pre/post-processing, response wrapping | @Injectable() | Applied via @UseInterceptors() | Constructor (services) |
| Filter | Exception handling, error formatting | @Catch() | Applied via @UseFilters() | Constructor (services) |
| Middleware | Raw request/response processing | Class or function | configure() method in module | Constructor (services, class-based only) |
1.4 How Decorators Work Under the Hood
NestJS relies heavily on TypeScript decorators. Understanding how they work makes you a better NestJS developer.What Are Decorators?
Decorators are special functions that can modify classes, methods, properties, or parameters. They’re prefixed with@ and executed at class definition time.
How Decorators Work
Under the hood, decorators are just functions that receive metadata about the decorated target:Reflect Metadata: The Magic Behind DI
NestJS uses thereflect-metadata library to store and retrieve metadata about classes. This is how dependency injection works!
NestJS Built-in Decorators
| Decorator | Type | Purpose |
|---|---|---|
@Module() | Class | Defines a module |
@Controller() | Class | Defines a controller |
@Injectable() | Class | Marks class as injectable |
@Get(), @Post() etc | Method | Maps HTTP methods |
@Body(), @Param(), @Query() | Parameter | Extracts request data |
@UseGuards() | Class/Method | Applies guards |
@UseInterceptors() | Class/Method | Applies interceptors |
@UsePipes() | Class/Method | Applies pipes |
Creating Custom Decorators
You can create your own decorators to reduce boilerplate:1.5 Application Lifecycle
Understanding how a NestJS application starts and runs helps you configure it properly and debug issues.Bootstrap Process
NestJS applications are initialized via themain.ts file. This is the entry point that bootstraps the root module and starts the HTTP server.
Basic Bootstrap:
-
NestFactory.create() - Creates a NestJS application instance
- Loads the root module (
AppModule) - Resolves all dependencies
- Initializes all providers
- Registers all controllers
- Loads the root module (
-
Module Loading - NestJS processes the module tree
- Starts with the root module
- Recursively loads imported modules
- Resolves provider dependencies
- Creates provider instances (based on scope)
-
Controller Registration - All controllers are registered
- Routes are mapped to controller methods
- Middleware, guards, interceptors are attached
-
Server Startup - HTTP server starts listening
- Default port: 3000
- Can be configured via environment variables
Enhanced Bootstrap with Configuration
In production, you’ll want to configure your app:- The app starts (
main.tsis executed) - The root module (
AppModule) is loaded - All imported modules are loaded recursively
- Controllers and providers are registered
- Dependency injection container resolves all dependencies
- Global pipes, filters, guards, interceptors are applied
- The HTTP server listens for requests
- Application is ready to handle requests
1.6 Real-World Example: Building a User API
Let’s build a simple REST API for managing users. This example will show you how the pieces fit together in a real project.Step 1: Generate Resources
NestJS CLI can generate boilerplate code for you:Step 2: Create a DTO (Data Transfer Object)
DTOs define the shape of data for requests and responses. They help with validation and type safety.- Type safety: TypeScript knows the shape of your data
- Validation:
class-validatordecorators validate input - Documentation: DTOs document your API contract
- Transformation: Can transform data automatically
Step 3: Implement the Service
- Service handles all business logic
- Uses NestJS exceptions for error handling
- Methods are async-ready (can easily add
async/awaitlater) - Service is stateless (data stored in memory for this example)
Step 4: Implement the Controller
- Controller is thin—delegates to service
- Uses
ParseIntPipeto transform and validate route parameters - DTOs are used for request body validation
- HTTP methods map to service methods
Step 5: Wire Up the Module
Step 6: Import into App Module
Testing the API
Try it out with curl or Postman:1.7 Best Practices & Tips
Following best practices from the start will save you time and prevent issues later.Code Organization
Feature-based structure:- Related code is grouped together
- Easy to find and maintain
- Scales well as app grows
- Clear module boundaries
Use DTOs and Validation
Always validate input using DTOs andclass-validator:
- Prevents invalid data from entering your system
- Clear error messages for API consumers
- Type safety at runtime
- Self-documenting API
Keep Controllers Thin
Controllers should only handle HTTP concerns:Use Environment Variables
Never hard-code configuration:@nestjs/config for better configuration management (covered in later chapters).
Leverage the CLI
The NestJS CLI is your friend:Document Your API
Use Swagger/OpenAPI for API documentation:Handle Errors Gracefully
Use NestJS built-in exceptions:Write Tests
Write tests early and often. NestJS makes testing easy (covered in Testing chapter).1.8 Edge Cases New Projects Hit Early
These are situations that tutorials skip but real projects encounter within the first week. Edge Case 1: Multiple controllers with overlapping routes If you have@Controller('users') with @Get(':id') and also @Get('search'), NestJS evaluates routes top-to-bottom. The :id wildcard will match “search” before the literal “search” route gets a chance. Always place specific routes above parameterized routes in the controller.
forRootAsync() with a factory that fetches config from a remote source, Module A may try to inject before Module B is ready. NestJS handles this via the module dependency graph — if you list Module B in Module A’s imports, it will be initialized first. But if you forget the import and rely on @Global(), initialization order is not guaranteed for async providers.
Edge Case 3: Hot module reload loses singleton state
When using start:dev with hot module replacement, singleton services are re-instantiated on every code change. If you are storing state in a singleton (like an in-memory cache during development), it resets on every save. This is not a bug — it is HMR working as designed. Use an external store (Redis, database) even in development if state persistence matters.
Edge Case 4: Fastify vs Express differences
NestJS supports both Express and Fastify as underlying HTTP platforms. Most tutorials assume Express, but if you switch to Fastify for performance, be aware: @Res() gives you a Fastify Reply object, not an Express Response. Middleware that uses Express-specific APIs (res.send, res.json overrides) will break. Check your middleware compatibility before switching.
1.8 Common Pitfalls and How to Avoid Them
Pitfall 1: Forgetting @Injectable() decorator This is the single most common NestJS beginner error. The error message “Nest can’t resolve dependencies of UsersController (?)” usually means one of the constructor parameters is missing@Injectable().
exports. NestJS providers are module-private by default — this is intentional encapsulation, not a bug.
findOne("1") does not match user.id === 1.
1.9 Summary
You’ve learned the foundational concepts of NestJS:- Modules: Organize your code into logical units
- Controllers: Handle HTTP requests and responses
- Services: Contain business logic
- Dependency Injection: Enables loose coupling and testability
- Application Lifecycle: How NestJS starts and runs
- NestJS brings structure and best practices to Node.js
- TypeScript-first approach provides type safety
- Modular architecture makes code maintainable
- Dependency injection enables testability
- CLI tools speed up development
- Practice building more features using these concepts
- Learn about Dependency Injection in depth (next chapter)
- Experiment with the CLI to generate different resources
Interview Deep-Dive
Explain what happens under the hood when NestJS bootstraps an application. Walk me through the lifecycle from NestFactory.create() to the first request.
Explain what happens under the hood when NestJS bootstraps an application. Walk me through the lifecycle from NestFactory.create() to the first request.
Strong Answer:
NestFactory.create(AppModule)triggers a multi-step initialization. First, NestJS instantiates the IoC (Inversion of Control) container. Then it reads the@Module()decorator metadata fromAppModuleusingReflect.getMetadata, which returns the imports, controllers, providers, and exports arrays.- It recursively loads all imported modules in a depth-first manner. For each module, it registers the providers in the DI container. The key detail: providers are not instantiated yet at this point — they are just registered. NestJS uses lazy instantiation for most providers, creating them only when they are first injected.
- After all modules are loaded, the container resolves the dependency graph. It reads
design:paramtypesmetadata from each provider’s constructor (set by TypeScript’semitDecoratorMetadatacompiler option) to determine what each class needs. If a dependency is missing or circular withoutforwardRef, this is where the “Nest can’t resolve dependencies” error fires. - Controllers are then registered, and their route decorators (
@Get,@Post, etc.) are read to build the internal route map. Middleware, global guards, interceptors, and pipes are attached to the appropriate routes. - Finally,
app.listen(3000)starts the underlying HTTP server (Express or Fastify). At this point, the application is ready to handle requests. - The critical production detail: if any async provider (a factory provider with
useFactory: async () => ...) fails during initialization, the entire bootstrap fails. This is actually desirable — you want the app to crash at startup if the database connection fails, not silently start serving 500 errors.
NestFactory.create() returns a Promise that does not resolve until every module is initialized. In Kubernetes, this means your readiness probe will fail during that 30 seconds, which is correct behavior — the pod should not receive traffic until it is fully initialized. The fix is to set initialDelaySeconds on your readiness probe high enough to accommodate slow providers. If the 30-second initialization is unacceptable, consider making the connection lazy — initialize the provider as a singleton but defer the actual connection until the first request, using an onModuleInit hook that runs in the background.A developer on your team puts database query logic directly in a controller method. Why is this problematic, and how would you refactor it?
A developer on your team puts database query logic directly in a controller method. Why is this problematic, and how would you refactor it?
Strong Answer:
- The immediate problem is testability. To test that controller method, you now need a real database connection or a complex mock of the database client. If the logic were in a service, you could mock the service with one line:
{ provide: UsersService, useValue: { findAll: jest.fn() } }. - The second problem is reusability. If another controller or a scheduled job needs the same query, you duplicate code. With a service, you call
usersService.findAll()from anywhere. - The third problem is separation of concerns. The controller is now responsible for HTTP concerns (routing, status codes, response format) AND business logic (query construction, data filtering). When the query needs to change, you are editing a file that is also responsible for routing, which increases the blast radius of the change and makes code review harder.
- The fourth problem, which most people miss, is that it breaks NestJS’s DI-based architecture. Interceptors can transform the response from a controller method, but if the controller is directly calling the database, the interceptor cannot easily cache or transform the database query — it only sees the final result. With a service layer, you can add caching at the service level independently.
- Refactoring: extract the query into a service method, inject the service into the controller, and have the controller delegate to the service. If the query is complex, add a repository layer between the service and the database. The controller method should be 1-3 lines: validate input (via pipes), call service, return result.
How do TypeScript decorators enable NestJS's dependency injection? What would break if emitDecoratorMetadata were set to false?
How do TypeScript decorators enable NestJS's dependency injection? What would break if emitDecoratorMetadata were set to false?
Strong Answer:
- TypeScript decorators are functions that run at class definition time (not at runtime when you create instances). When you write
@Injectable(), the decorator function is called with the class as an argument. NestJS’s@Injectable()does not do much itself — it mainly exists so that TypeScript emits metadata for the class. - The real magic is
emitDecoratorMetadata: trueintsconfig.json. When this is enabled, TypeScript automatically callsReflect.defineMetadata('design:paramtypes', [...], TargetClass)for every class that has at least one decorator. This metadata records the types of all constructor parameters. - So when NestJS sees
constructor(private usersService: UsersService), it callsReflect.getMetadata('design:paramtypes', ControllerClass)and gets back[UsersService]. It then looks upUsersServicein the DI container and injects the instance. - If you set
emitDecoratorMetadata: false, NestJS cannot determine constructor parameter types. Every injection would fail with “Nest can’t resolve dependencies” because the framework has no way to know what to inject. You would have to use explicit@Inject()decorators on every constructor parameter, which defeats the purpose of automatic DI. - There is a subtle edge case: if a constructor parameter’s type is an interface (not a class),
design:paramtypesrecords it asObjectbecause interfaces are erased at runtime in TypeScript. This is why you need@Inject('TOKEN')for interface-based injection — the metadata cannot distinguish between different interfaces.
emitDecoratorMetadata — there is no automatic parameter type emission. This means NestJS would need to move toward explicit injection tokens or a different metadata strategy (like Prisma’s approach of code generation, or Angular’s approach of using a compiler plugin). The NestJS team has been cautious about this migration because it would be a breaking change for every existing NestJS application. For now, NestJS still requires experimentalDecorators: true in tsconfig.json, and the TC39 decorator migration is a future concern.You have a NestJS application that works fine in development but crashes at startup in production with 'Cannot find module'. What is your debugging approach?
You have a NestJS application that works fine in development but crashes at startup in production with 'Cannot find module'. What is your debugging approach?
Strong Answer:
- This is almost always a build or path resolution issue. The first thing I check is whether
npm run buildcompletes without errors locally. NestJS compiles TypeScript to JavaScript in thedist/folder, and the production server runsnode dist/main.js— it never sees.tsfiles. - The most common cause is a missing import that TypeScript resolves at compile time but fails at runtime. For example, if you use a path alias like
@app/usersintsconfig.jsonbut do not configure the same alias in the build output, the compiled JavaScript will still haverequire('@app/users'), which Node.js cannot resolve. The fix is to usetsconfig-pathsin production or switch to relative imports. - The second common cause is a dynamic import or a file path reference (like
entities: [__dirname + '/**/*.entity{.ts,.js}']in TypeORM config). In development,__dirnamepoints to thesrc/directory. In production, it points todist/. If the glob pattern includes.tsbut thedist/folder only has.jsfiles, the entity is never loaded. - The third cause is missing dependencies.
npm ci --only=productionskips devDependencies. If your code accidentally imports a dev-only package (like@nestjs/testingin a production file), it will crash. Check yourpackage.jsonto ensure all runtime dependencies are independencies, notdevDependencies. - My debugging checklist: (1) Build locally, copy
dist/andnode_modulesto a clean directory, runnode dist/main.js. (2) Check for path alias issues in compiled.jsfiles. (3) Verify entity/migration glob patterns work with thedist/directory structure. (4) Check that the DockerCOPYstage includes all necessary files.
docker build, execute docker run --rm myapp node dist/main.js --dry-run (or a health check curl). If the app crashes at startup, CI fails before the deployment step. I also add a pre-commit check that runs npm run build to catch TypeScript compilation errors early. For path aliases, I use tsc-alias as a post-build step to rewrite aliases to relative paths in the compiled output, eliminating the entire class of path resolution bugs.