NestJS Project Setup: Creating Your First Application
You've learned what NestJS is and why it's powerful. Now comes the exciting part - let's build something! By the end of this guide, you'll have a running NestJS application and understand every file in your project.
Think of this as unpacking a new toolbox. We'll explore each tool, understand what it does, and use it to build something real.
Quick Reference
Setup command:
npm i -g @nestjs/cli
nest new my-project
cd my-project
npm run start:dev
Project structure:
src/
main.ts # Application entry point
app.module.ts # Root module
app.controller.ts # Sample controller
app.service.ts # Sample service
Common commands:
nest new <name>- Create new projectnest generate <schematic>- Generate codenpm run start:dev- Run with hot reloadnpm run build- Build for production
Gotchas:
- ⚠️ CLI installation requires Node.js 18+
- ⚠️ Project names can't contain spaces or special characters
- ⚠️ Always use
npm run start:devduring development
What You Need to Know First
Required reading:
- NestJS: Enterprise Node.js Framework for Scalable Applications - You must understand what NestJS is before setting it up
Technical prerequisites:
- Node.js installed: Version 18.x or higher
- npm basics: How to install packages and run scripts
- Terminal/Command line: Basic navigation (cd, ls/dir)
- Text editor/IDE: VS Code recommended (with TypeScript support)
System requirements:
- 4 GB RAM minimum (8 GB recommended)
- 1 GB free disk space
- macOS, Windows, or Linux
What We'll Cover in This Article
By the end of this guide, you'll understand:
- How to install the NestJS CLI globally
- How to create a new NestJS project
- The complete project structure and what each file does
- How to run your application in development mode
- How to generate new components using the CLI
- Common CLI commands and their purposes
What We'll Explain Along the Way
These concepts will be explained with examples as we encounter them:
- Project scaffolding (what files are generated and why)
- Package.json scripts (what each command does)
- TypeScript configuration (tsconfig.json explained)
- Hot reload and watch mode (how it works)
- Development vs production builds
Installing the NestJS CLI
The NestJS Command Line Interface (CLI) is your best friend when working with NestJS. It's like having a construction crew that builds the framework of your application for you.
What Is the CLI?
The CLI is a tool that:
- Creates new projects with proper structure
- Generates boilerplate code (controllers, services, modules)
- Manages your project's build and development
- Saves hours of manual file creation
Think of it like a blueprint machine - you tell it what you want, and it creates the perfect structure for you.
Step 1: Verify Node.js Installation
Before installing the CLI, let's make sure you have Node.js installed:
# Check Node.js version
node --version
# Should output: v18.x.x or higher
# Check npm version
npm --version
# Should output: 9.x.x or higher
What you'll see:
$ node --version
v20.10.0
$ npm --version
10.2.3
If you see "command not found":
- You need to install Node.js first
- Download from: https://nodejs.org/
- Choose the LTS (Long Term Support) version
- Install and restart your terminal
Step 2: Install NestJS CLI Globally
Now let's install the CLI so it's available from anywhere on your system:
# Install globally with npm
npm install -g @nestjs/cli
# Or if you prefer using npx (no installation needed)
# npx @nestjs/cli new my-project
What's happening here:
npm install: The npm package installer-g: Global flag - installs for your entire system, not just one project@nestjs/cli: The NestJS command-line interface package
Installation output:
$ npm install -g @nestjs/cli
added 276 packages in 23s
22 packages are looking for funding
run `npm fund` for details
This might take 1-2 minutes. The CLI is installing itself and all its dependencies.
Step 3: Verify CLI Installation
Let's confirm the CLI is installed correctly:
# Check if nest command is available
nest --version
# Should output: 10.x.x
# See all available commands
nest --help
What you'll see:
$ nest --version
10.3.0
$ nest --help
Usage: nest <command> [options]
Options:
-v, --version Output the current version.
-h, --help Output usage information.
Commands:
new|n [options] [name] Generate Nest application.
generate|g [options] <schematic> Generate a Nest element.
build [options] [app] Build Nest application.
start [options] [app] Run Nest application.
info|i Display Nest project details.
If you see "command not found":
- Close and reopen your terminal (necessary for global installations)
- Check npm global path:
npm config get prefix
# Make sure this path is in your system PATH - Alternative: Use npx (no installation required):
npx @nestjs/cli --version
Understanding the CLI Commands
Here's what each command does - we'll use these throughout your NestJS journey:
# Create new project
nest new <project-name>
# Generate components
nest generate controller users
nest generate service users
nest generate module users
# Build and run
nest build # Compile TypeScript to JavaScript
nest start # Run in production mode
nest start --watch # Run in development mode with hot reload
# Get project information
nest info # Shows versions and environment details
Real-world analogy:
nest new= Building a new house (complete structure)nest generate= Adding new rooms (controllers, services)nest start= Opening the house for businessnest build= Final inspection before opening
Creating Your First NestJS Project
Now that we have the CLI installed, let's create our first project. This is where the magic happens!
Step 1: Create a New Project
# Create a project called "my-first-nest-app"
nest new my-first-nest-app
# You'll see interactive prompts - let's walk through them
What you'll see:
$ nest new my-first-nest-app
⚡ We will scaffold your app in a few seconds..
? Which package manager would you ❤️ to use? (Use arrow keys)
❯ npm
yarn
pnpm
Choose your package manager:
- npm: Default, comes with Node.js (recommended for beginners)
- yarn: Alternative, faster in some cases
- pnpm: Disk space efficient, faster installs
For this guide, let's choose npm (press Enter).
Step 2: Watch the Project Creation
After selecting npm, you'll see:
CREATE my-first-nest-app/.eslintrc.js (663 bytes)
CREATE my-first-nest-app/.prettierrc (51 bytes)
CREATE my-first-nest-app/README.md (3339 bytes)
CREATE my-first-nest-app/nest-cli.json (171 bytes)
CREATE my-first-nest-app/package.json (1951 bytes)
CREATE my-first-nest-app/tsconfig.build.json (97 bytes)
CREATE my-first-nest-app/tsconfig.json (546 bytes)
CREATE my-first-nest-app/src/app.controller.spec.ts (617 bytes)
CREATE my-first-nest-app/src/app.controller.ts (274 bytes)
CREATE my-first-nest-app/src/app.module.ts (249 bytes)
CREATE my-first-nest-app/src/app.service.ts (142 bytes)
CREATE my-first-nest-app/src/main.ts (208 bytes)
CREATE my-first-nest-app/test/app.e2e-spec.ts (630 bytes)
CREATE my-first-nest-app/test/jest-e2e.json (183 bytes)
✔ Installation in progress... ☕
🚀 Successfully created project my-first-nest-app
👉 Get started with the following commands:
$ cd my-first-nest-app
$ npm run start
What just happened?
- Created project folder with all necessary files
- Generated starter code (controllers, services, modules)
- Installed dependencies (Express, TypeScript, testing tools)
- Set up configuration (TypeScript, ESLint, Prettier)
This took about 30-60 seconds. The CLI just saved you hours of manual setup!
Step 3: Navigate to Your Project
# Enter your project directory
cd my-first-nest-app
# List the files
ls -la
# On Windows: dir
What you'll see:
$ cd my-first-nest-app
$ ls -la
drwxr-xr-x node_modules/ # Dependencies (1000+ packages)
drwxr-xr-x src/ # Your application code
drwxr-xr-x test/ # End-to-end tests
-rw-r--r-- .eslintrc.js # Code quality rules
-rw-r--r-- .prettierrc # Code formatting rules
-rw-r--r-- nest-cli.json # NestJS CLI configuration
-rw-r--r-- package.json # Project dependencies
-rw-r--r-- README.md # Project documentation
-rw-r--r-- tsconfig.json # TypeScript configuration
Congratulations! You've just created your first NestJS project. Let's explore what each file does.
Understanding the Project Structure
Let's take a tour of your new project, file by file. Think of this as a guided tour of your new home - we'll visit every room and understand its purpose.
The Root Directory: Project Configuration
my-first-nest-app/
├── node_modules/ # Dependencies (don't edit)
├── src/ # Your application code (THIS IS WHERE YOU WORK)
├── test/ # End-to-end tests
├── .eslintrc.js # Linting rules
├── .prettierrc # Formatting rules
├── nest-cli.json # NestJS CLI config
├── package.json # Project metadata & scripts
├── tsconfig.json # TypeScript configuration
├── tsconfig.build.json # Build-specific TS config
└── README.md # Project documentation
The src/ Directory: Your Application Code
This is where you'll spend 99% of your time:
src/
├── main.ts # Application entry point (starts the server)
├── app.module.ts # Root module (organizes everything)
├── app.controller.ts # Sample HTTP controller
├── app.service.ts # Sample business logic service
└── app.controller.spec.ts # Unit tests for controller
Let's explore each file in detail:
1. main.ts - The Application Entry Point
This is where your application starts. Think of it as the ignition key of a car:
// src/main.ts
import { NestFactory } from "@nestjs/core";
import { AppModule } from "./app.module";
async function bootstrap() {
// Create the NestJS application
const app = await NestFactory.create(AppModule);
// Start listening on port 3000
await app.listen(3000);
}
// Start the application
bootstrap();
What's happening here:
// Step 1: Import the necessary pieces
import { NestFactory } from "@nestjs/core";
// NestFactory is like a car factory - it creates your application
import { AppModule } from "./app.module";
// AppModule is the blueprint for what to build
// Step 2: Define the startup function
async function bootstrap() {
// "bootstrap" is a common term meaning "start up"
// Step 3: Create the application
const app = await NestFactory.create(AppModule);
// This line:
// 1. Reads your AppModule blueprint
// 2. Creates all controllers and services
// 3. Sets up dependency injection
// 4. Configures Express/Fastify
// All in this one line!
// Step 4: Start the server
await app.listen(3000);
// Server is now running on http://localhost:3000
console.log("Application is running on: http://localhost:3000");
}
// Step 5: Execute the startup function
bootstrap();
Customizing main.ts:
You'll often enhance this file as your app grows:
// Enhanced main.ts with common additions
import { NestFactory } from "@nestjs/core";
import { ValidationPipe } from "@nestjs/common";
import { AppModule } from "./app.module";
async function bootstrap() {
const app = await NestFactory.create(AppModule);
// Enable CORS for frontend applications
app.enableCors();
// Enable validation for all endpoints
app.useGlobalPipes(new ValidationPipe());
// Set global prefix for all routes
app.setGlobalPrefix("api"); // All routes now start with /api
// Start server
const port = process.env.PORT || 3000;
await app.listen(port);
console.log(`Application is running on: http://localhost:${port}`);
}
bootstrap();
2. app.module.ts - The Root Module
Modules are like departments in a company. The root module is the headquarters:
// src/app.module.ts
import { Module } from "@nestjs/common";
import { AppController } from "./app.controller";
import { AppService } from "./app.service";
@Module({
imports: [], // Other modules this one needs
controllers: [AppController], // HTTP handlers
providers: [AppService], // Business logic services
})
export class AppModule {}
Understanding the @Module decorator:
@Module({
// 1. imports: Other modules we depend on
imports: [
// Example: UsersModule, ProductsModule
// We'll add these as we build features
],
// 2. controllers: Classes that handle HTTP requests
controllers: [
AppController, // Handles routes like GET /
],
// 3. providers: Services that can be injected
providers: [
AppService, // Business logic used by controllers
],
// 4. exports: What other modules can use (optional)
exports: [
// AppService, // If other modules need it
],
})
export class AppModule {}
Real-world example as your app grows:
// app.module.ts - After adding features
import { Module } from "@nestjs/common";
import { AppController } from "./app.controller";
import { AppService } from "./app.service";
import { UsersModule } from "./users/users.module";
import { ProductsModule } from "./products/products.module";
import { AuthModule } from "./auth/auth.module";
@Module({
imports: [
UsersModule, // User management feature
ProductsModule, // Product catalog feature
AuthModule, // Authentication feature
],
controllers: [AppController],
providers: [AppService],
})
export class AppModule {}
Key insight: The root module imports all feature modules. It's like a table of contents for your entire application.
3. app.controller.ts - Handling HTTP Requests
Controllers are like receptionists - they receive requests and return responses:
// src/app.controller.ts
import { Controller, Get } from "@nestjs/common";
import { AppService } from "./app.service";
@Controller() // Base route: / (empty = root)
export class AppController {
// Dependency injection: service is automatically provided
constructor(private readonly appService: AppService) {}
@Get() // Handles GET /
getHello(): string {
// Delegate to service for business logic
return this.appService.getHello();
}
}
Breaking down the controller:
// 1. Import necessary decorators
import { Controller, Get } from "@nestjs/common";
import { AppService } from "./app.service";
// 2. Mark class as a controller
@Controller() // Could be @Controller('users') for /users routes
export class AppController {
// 3. Inject dependencies via constructor
constructor(
private readonly appService: AppService // "private readonly" creates a property and assigns it automatically // Now we can use this.appService anywhere in the class
) {}
// 4. Define route handlers
@Get() // Responds to GET requests to /
getHello(): string {
// Return type is string
return this.appService.getHello();
// Controller stays thin - delegates to service
}
// We can add more routes:
@Get("about") // Responds to GET /about
getAbout(): string {
return "About page";
}
}
Controller with more examples:
// Expanded controller showing common patterns
import { Controller, Get, Post, Body, Param } from "@nestjs/common";
import { AppService } from "./app.service";
@Controller("api") // Base route: /api
export class AppController {
constructor(private readonly appService: AppService) {}
// GET /api
@Get()
getHello(): string {
return this.appService.getHello();
}
// GET /api/users
@Get("users")
getUsers(): string[] {
return ["Alice", "Bob", "Charlie"];
}
// GET /api/users/:id
@Get("users/:id")
getUser(@Param("id") id: string): string {
return `User ${id}`;
}
// POST /api/users
@Post("users")
createUser(@Body() userData: any): any {
return { id: 1, ...userData };
}
}
4. app.service.ts - Business Logic
Services contain your actual business logic. Controllers call services:
// src/app.service.ts
import { Injectable } from "@nestjs/common";
@Injectable() // Makes this available for dependency injection
export class AppService {
getHello(): string {
return "Hello World!";
}
}
Understanding services:
// 1. Mark as injectable
@Injectable() // This decorator is REQUIRED
export class AppService {
// 2. Business logic methods
getHello(): string {
// Simple method returning a string
return "Hello World!";
}
// Services can have multiple methods
getWelcomeMessage(name: string): string {
return `Welcome, ${name}!`;
}
// Services can be async
async fetchData(): Promise<any> {
// Imagine calling a database or API here
return { data: "some data" };
}
}
Why separate services from controllers?
// ❌ Bad: Business logic in controller
@Controller()
export class AppController {
@Get("users/:id")
getUser(@Param("id") id: string) {
// All logic in controller - hard to test and reuse
const user = database.query(`SELECT * FROM users WHERE id = ${id}`);
if (!user) throw new Error("Not found");
delete user.password;
return user;
}
}
// ✅ Good: Business logic in service
@Injectable()
export class UsersService {
async findById(id: string): Promise<User> {
const user = await this.database.findOne({ where: { id } });
if (!user) throw new NotFoundException("User not found");
return this.sanitizeUser(user);
}
private sanitizeUser(user: User): User {
const { password, ...safeUser } = user;
return safeUser;
}
}
@Controller("users")
export class UsersController {
constructor(private usersService: UsersService) {}
@Get(":id")
getUser(@Param("id") id: string) {
// Controller stays thin - just routing
return this.usersService.findById(id);
}
}
Benefits of this separation:
- Testable: Test business logic without HTTP layer
- Reusable: Use service in multiple controllers
- Maintainable: Changes to logic don't affect routing
- Clear: Each class has one responsibility
5. app.controller.spec.ts - Unit Tests
NestJS includes testing setup out of the box:
// src/app.controller.spec.ts
import { Test, TestingModule } from "@nestjs/testing";
import { AppController } from "./app.controller";
import { AppService } from "./app.service";
describe("AppController", () => {
let appController: AppController;
beforeEach(async () => {
// Create a testing module
const app: TestingModule = await Test.createTestingModule({
controllers: [AppController],
providers: [AppService],
}).compile();
// Get instance of controller to test
appController = app.get & lt;
AppController > AppController;
});
describe("root", () => {
it('should return "Hello World!"', () => {
// Test the getHello method
expect(appController.getHello()).toBe("Hello World!");
});
});
});
Understanding the test structure:
// 1. Import testing utilities
import { Test, TestingModule } from "@nestjs/testing";
// 2. Describe what we're testing
describe("AppController", () => {
// Declare variables for test scope
let appController: AppController;
// 3. Setup before each test
beforeEach(async () => {
// Create a mini-application for testing
const app: TestingModule = await Test.createTestingModule({
controllers: [AppController], // What to test
providers: [AppService], // What it depends on
}).compile();
// Get the controller instance
appController = app.get & lt;
AppController > AppController;
});
// 4. Write test cases
describe("root", () => {
it('should return "Hello World!"', () => {
// Call the method
const result = appController.getHello();
// Assert the expected outcome
expect(result).toBe("Hello World!");
});
});
});
We'll cover testing in depth in a future article. For now, know that tests are included by default!
Configuration Files Explained
Let's understand the configuration files in the root directory:
package.json - Project Metadata:
{
"name": "my-first-nest-app",
"version": "0.0.1",
"scripts": {
"build": "nest build", // Compile TypeScript
"start": "nest start", // Run in production mode
"start:dev": "nest start --watch", // Run with hot reload
"start:debug": "nest start --debug --watch", // Run with debugger
"start:prod": "node dist/main", // Run compiled JavaScript
"lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix",
"test": "jest", // Run unit tests
"test:watch": "jest --watch", // Run tests in watch mode
"test:cov": "jest --coverage", // Run tests with coverage
"test:e2e": "jest --config ./test/jest-e2e.json" // End-to-end tests
},
"dependencies": {
"@nestjs/common": "^10.0.0", // Core NestJS functionality
"@nestjs/core": "^10.0.0", // NestJS core
"@nestjs/platform-express": "^10.0.0", // Express adapter
"reflect-metadata": "^0.1.13", // Required for decorators
"rxjs": "^7.8.1" // Reactive extensions
},
"devDependencies": {
"@nestjs/cli": "^10.0.0", // CLI tools
"@nestjs/testing": "^10.0.0", // Testing utilities
"typescript": "^5.1.3" // TypeScript compiler
}
}
tsconfig.json - TypeScript Configuration:
{
"compilerOptions": {
"module": "commonjs", // Module system
"declaration": true, // Generate .d.ts files
"removeComments": true, // Remove comments in output
"emitDecoratorMetadata": true, // Required for NestJS
"experimentalDecorators": true, // Enable decorators
"allowSyntheticDefaultImports": true,
"target": "ES2021", // Target JavaScript version
"sourceMap": true, // Generate source maps for debugging
"outDir": "./dist", // Output directory
"baseUrl": "./", // Base directory
"incremental": true, // Faster subsequent builds
"skipLibCheck": true, // Skip type checking of declaration files
"strictNullChecks": false, // Allow null/undefined
"noImplicitAny": false, // Allow implicit any type
"strictBindCallApply": false,
"forceConsistentCasingInFileNames": false,
"noFallthroughCasesInSwitch": false
}
}
nest-cli.json - NestJS CLI Configuration:
{
"$schema": "https://json.schemastore.org/nest-cli",
"collection": "@nestjs/schematics", // Code generation templates
"sourceRoot": "src", // Where source code lives
"compilerOptions": {
"deleteOutDir": true // Clean dist/ before building
}
}
Running Your Application
Now for the exciting part - let's see your application in action!
Development Mode (with Hot Reload)
This is what you'll use 99% of the time during development:
# Start the application in watch mode
npm run start:dev
What you'll see:
$ npm run start:dev
[12:34:56 PM] Starting compilation in watch mode...
[12:34:58 PM] Found 0 errors. Watching for file changes.
[Nest] 12345 - 12/01/2024, 12:34:58 PM LOG [NestFactory] Starting Nest application...
[Nest] 12345 - 12/01/2024, 12:34:58 PM LOG [InstanceLoader] AppModule dependencies initialized +15ms
[Nest] 12345 - 12/01/2024, 12:34:58 PM LOG [RoutesResolver] AppController {/}: +3ms
[Nest] 12345 - 12/01/2024, 12:34:58 PM LOG [RouterExplorer] Mapped {/, GET} route +2ms
[Nest] 12345 - 12/01/2024, 12:34:58 PM LOG [NestApplication] Nest application successfully started +2ms
What this means:
- TypeScript compilation - Your .ts files are compiled to JavaScript
- Application startup - NestJS initializes everything
- Route mapping - All routes are registered
- Server listening - Application is ready on port 3000
Testing Your Application
Open your browser and navigate to:
http://localhost:3000
You should see:
Hello World!
Congratulations! Your first NestJS application is running!
Hot Reload in Action
Let's see the magic of hot reload. Keep the server running and edit a file:
// src/app.service.ts
import { Injectable } from "@nestjs/common";
@Injectable()
export class AppService {
getHello(): string {
// Change this line:
return "Hello World!";
// To this:
return "Welcome to NestJS! 🚀";
}
}
Save the file and watch your terminal:
[12:36:15 PM] File change detected. Starting incremental compilation...
[12:36:16 PM] Found 0 errors. Compilation successful.
[Nest] 12345 - 12/01/2024, 12:36:16 PM LOG [NestFactory] Starting Nest application...
[Nest] 12345 - 12/01/2024, 12:36:16 PM LOG Application restarted successfully
Refresh your browser - you'll see the new message immediately!
This is hot reload - changes are detected and the server restarts automatically. No need to manually stop and start!
Other Run Modes
# Production mode (no hot reload)
npm run start
# Debug mode (attach debugger)
npm run start:debug
# Build for production deployment
npm run build
npm run start:prod
Stopping the Application
To stop the development server:
# Press Ctrl + C in your terminal
# You'll see:
^C[Nest] 12345 - 12/01/2024, 12:40:00 PM LOG Application terminated
Using the NestJS CLI to Generate Code
The CLI is not just for creating projects - it's your assistant for generating all types of code!
Understanding CLI Schematics
"Schematics" are templates for generating code. Think of them as blueprints:
# General syntax:
nest generate <schematic> <name> [options]
# Or shorthand:
nest g <schematic> <name>
Available schematics:
| Schematic | Generates | Example |
|---|---|---|
module | A new module | nest g module users |
controller | A new controller | nest g controller users |
service | A new service (provider) | nest g service users |
class | A TypeScript class | nest g class dto/create-user |
interface | A TypeScript interface | nest g interface user |
guard | An authentication guard | nest g guard auth |
interceptor | An interceptor | nest g interceptor logging |
pipe | A validation pipe | nest g pipe validation |
middleware | Middleware | nest g middleware logger |
filter | Exception filter | nest g filter http-exception |
gateway | WebSocket gateway | nest g gateway events |
resolver | GraphQL resolver | nest g resolver users |
Example: Creating a Complete Feature
Let's create a "users" feature with all necessary files:
# Option 1: Generate each piece individually
nest generate module users
nest generate controller users
nest generate service users
# Option 2: Use resource (generates everything at once!)
nest generate resource users
Let's try the resource generator:
$ nest generate resource users
? What transport layer do you use? (Use arrow keys)
❯ REST API
GraphQL (code first)
GraphQL (schema first)
Microservice (non-HTTP)
WebSockets
# Choose: REST API
? Would you like to generate CRUD entry points? (Y/n)
# Choose: Y (Yes)
CREATE src/users/users.controller.spec.ts (566 bytes)
CREATE src/users/users.controller.ts (894 bytes)
CREATE src/users/users.module.ts (247 bytes)
CREATE src/users/users.service.spec.ts (453 bytes)
CREATE src/users/users.service.ts (609 bytes)
CREATE src/users/dto/create-user.dto.ts (30 bytes)
CREATE src/users/dto/update-user.dto.ts (169 bytes)
CREATE src/users/entities/user.entity.ts (21 bytes)
UPDATE src/app.module.ts (312 bytes)
What just happened?
The CLI generated:
- ✅ Complete users module with controller and service
- ✅ CRUD endpoints (Create, Read, Update, Delete)
- ✅ DTOs (Data Transfer Objects) for validation
- ✅ Entity file for database modeling
- ✅ Unit tests for controller and service
- ✅ Automatically imported UsersModule in AppModule
All this in one command! Let's see what was created:
// src/users/users.module.ts
import { Module } from "@nestjs/common";
import { UsersService } from "./users.service";
import { UsersController } from "./users.controller";
@Module({
controllers: [UsersController],
providers: [UsersService],
})
export class UsersModule {}
// src/users/users.controller.ts
import {
Controller,
Get,
Post,
Body,
Patch,
Param,
Delete,
} from "@nestjs/common";
import { UsersService } from "./users.service";
import { CreateUserDto } from "./dto/create-user.dto";
import { UpdateUserDto } from "./dto/update-user.dto";
@Controller("users")
export class UsersController {
constructor(private readonly usersService: UsersService) {}
@Post()
create(@Body() createUserDto: CreateUserDto) {
return this.usersService.create(createUserDto);
}
@Get()
findAll() {
return this.usersService.findAll();
}
@Get(":id")
findOne(@Param("id") id: string) {
return this.usersService.findOne(+id);
}
@Patch(":id")
update(@Param("id") id: string, @Body() updateUserDto: UpdateUserDto) {
return this.usersService.update(+id, updateUserDto);
}
@Delete(":id")
remove(@Param("id") id: string) {
return this.usersService.remove(+id);
}
}
// src/users/users.service.ts
import { Injectable } from "@nestjs/common";
import { CreateUserDto } from "./dto/create-user.dto";
import { UpdateUserDto } from "./dto/update-user.dto";
@Injectable()
export class UsersService {
create(createUserDto: CreateUserDto) {
return "This action adds a new user";
}
findAll() {
return `This action returns all users`;
}
findOne(id: number) {
return `This action returns a #${id} user`;
}
update(id: number, updateUserDto: UpdateUserDto) {
return `This action updates a #${id} user`;
}
remove(id: number) {
return `This action removes a #${id} user`;
}
}
Your new endpoints are immediately available:
GET /users # Get all users
GET /users/:id # Get one user
POST /users # Create user
PATCH /users/:id # Update user
DELETE /users/:id # Delete user
Test them in your browser:
- Visit http://localhost:3000/users
- You'll see:
This action returns all users
CLI Options and Flags
Useful flags:
# Generate without spec (test) file
nest g service users --no-spec
# Generate in a specific directory
nest g controller admin/users
# Dry run (preview without creating files)
nest g module products --dry-run
# Generate with flat structure (no folder)
nest g service auth --flat
# Skip module import update
nest g controller users --no-flat --skip-import
Examples:
# Create admin feature in admin folder
nest g resource admin/users
# Result:
src/
admin/
users/
users.controller.ts
users.service.ts
users.module.ts
# Create a DTO class
nest g class dto/create-product --no-spec
# Result:
src/
dto/
create-product.ts
# Create an authentication guard
nest g guard auth/auth
# Result:
src/
auth/
auth.guard.ts
auth.guard.spec.ts
Understanding Generated File Patterns
Notice the CLI follows consistent patterns:
// Pattern 1: Feature folder structure
src/
users/
dto/ # Data Transfer Objects
create-user.dto.ts
update-user.dto.ts
entities/ # Database entities
user.entity.ts
users.controller.ts # HTTP routes
users.service.ts # Business logic
users.module.ts # Module definition
users.controller.spec.ts # Controller tests
users.service.spec.ts # Service tests
// Pattern 2: Naming conventions
users.controller.ts # <feature>.controller.ts
users.service.ts # <feature>.service.ts
users.module.ts # <feature>.module.ts
create-user.dto.ts # <action>-<feature>.dto.ts
user.entity.ts # <feature>.entity.ts
Why these patterns matter:
- Team members know exactly where to find code
- Files are automatically organized by feature
- Imports and exports follow predictable paths
- Easier to maintain as project grows
Common Development Workflows
Let's explore typical day-to-day workflows when developing with NestJS:
Workflow 1: Adding a New Feature
# Day 1: Create the feature structure
nest g resource products
# You get:
# - products.module.ts
# - products.controller.ts
# - products.service.ts
# - DTOs and entities
# - Test files
# Day 2: Implement business logic
# Edit products.service.ts with actual logic
# Day 3: Add validation
# Edit DTOs with validation decorators
# Day 4: Add database integration
# Implement entity and repository
# Day 5: Write tests
# Update .spec.ts files with real tests
Workflow 2: Modifying Existing Code
# Server is running with: npm run start:dev
# You edit a file:
# src/users/users.service.ts
# Hot reload happens automatically:
# [Nest] File change detected...
# [Nest] Application restarted successfully
# Test immediately in browser/Postman
# No manual server restart needed!
Workflow 3: Debugging Issues
# Option 1: Use console.log (simple)
@Injectable()
export class UsersService {
findAll() {
console.log('findAll called'); // Quick debug
return [];
}
}
# Option 2: Use debug mode with breakpoints
npm run start:debug
# Then attach VS Code debugger:
# 1. Set breakpoints in your code
# 2. Press F5 in VS Code
# 3. Debugger attaches automatically
Workflow 4: Running Tests
# Run all tests once
npm run test
# Watch mode (re-run on changes)
npm run test:watch
# Coverage report
npm run test:cov
# End-to-end tests
npm run test:e2e
# Test specific file
npm run test users.service.spec.ts
Example test output:
$ npm run test
PASS src/users/users.service.spec.ts
UsersService
✓ should be defined (15ms)
✓ should create a user (8ms)
✓ should find all users (5ms)
Test Suites: 1 passed, 1 total
Tests: 3 passed, 3 total
Time: 2.345s
Workflow 5: Preparing for Production
# Step 1: Run linting
npm run lint
# Step 2: Run tests
npm run test
# Step 3: Build the application
npm run build
# Check the output:
dist/
main.js
app.module.js
app.controller.js
app.service.js
# All TypeScript compiled to JavaScript
# Step 4: Test production build
npm run start:prod
# Step 5: Deploy dist/ folder to server
Project Organization Best Practices
As your project grows, organization becomes crucial. Let's explore best practices:
Feature-Based Structure
src/
common/ # Shared utilities
decorators/
guards/
interceptors/
filters/
pipes/
interfaces/
constants/
config/ # Configuration
database.config.ts
app.config.ts
users/ # User feature
dto/
entities/
users.controller.ts
users.service.ts
users.module.ts
products/ # Product feature
dto/
entities/
products.controller.ts
products.service.ts
products.module.ts
auth/ # Authentication feature
strategies/
guards/
auth.controller.ts
auth.service.ts
auth.module.ts
main.ts # Entry point
app.module.ts # Root module
Shared Module Pattern
// src/common/common.module.ts
import { Module, Global } from "@nestjs/common";
import { LoggerService } from "./logger.service";
import { ConfigService } from "./config.service";
@Global() // Available everywhere without importing
@Module({
providers: [LoggerService, ConfigService],
exports: [LoggerService, ConfigService],
})
export class CommonModule {}
// Now any module can use these services without importing CommonModule
@Injectable()
export class UsersService {
constructor(
private logger: LoggerService, // Available globally
private config: ConfigService // Available globally
) {}
}
Core Module Pattern
// src/core/core.module.ts
import { Module } from "@nestjs/common";
import { DatabaseModule } from "./database/database.module";
import { CacheModule } from "./cache/cache.module";
import { QueueModule } from "./queue/queue.module";
@Module({
imports: [DatabaseModule, CacheModule, QueueModule],
exports: [DatabaseModule, CacheModule, QueueModule],
})
export class CoreModule {}
// Import once in AppModule
@Module({
imports: [CoreModule, UsersModule, ProductsModule],
})
export class AppModule {}
Environment Configuration Pattern
// src/config/configuration.ts
export default () => ({
port: parseInt(process.env.PORT, 10) || 3000,
database: {
host: process.env.DATABASE_HOST || "localhost",
port: parseInt(process.env.DATABASE_PORT, 10) || 5432,
username: process.env.DATABASE_USER,
password: process.env.DATABASE_PASSWORD,
database: process.env.DATABASE_NAME,
},
jwt: {
secret: process.env.JWT_SECRET,
expiresIn: process.env.JWT_EXPIRES_IN || "1h",
},
});
// Usage in main.ts
import { ConfigService } from "@nestjs/config";
async function bootstrap() {
const app = await NestFactory.create(AppModule);
const configService = app.get(ConfigService);
const port = configService.get & lt;
number > "port";
await app.listen(port);
}
Common Misconceptions
❌ Misconception: "I need to restart the server after every change"
Reality: Development mode with hot reload automatically restarts when files change.
Why this matters: Manual restarts waste time and break your flow.
Example:
# ❌ Wrong workflow:
# 1. Edit file
# 2. Ctrl+C to stop server
# 3. npm run start:dev
# 4. Wait for startup
# 5. Test change
# ✅ Right workflow:
# 1. npm run start:dev once at beginning
# 2. Edit files
# 3. Save
# 4. Changes apply automatically
# 5. Test immediately
❌ Misconception: "Generated code is production-ready"
Reality: Generated code is a starting point - you must implement actual logic.
Why this matters: CLI generates boilerplate, not business logic.
Example:
// Generated code (not production-ready):
@Injectable()
export class UsersService {
create(createUserDto: CreateUserDto) {
return "This action adds a new user"; // Just a placeholder!
}
}
// Production code (what you need to write):
@Injectable()
export class UsersService {
constructor(
@InjectRepository(User)
private usersRepository: Repository<User>
) {}
async create(createUserDto: CreateUserDto): Promise<User> {
// Actual implementation
const existingUser = await this.usersRepository.findOne({
where: { email: createUserDto.email },
});
if (existingUser) {
throw new ConflictException("Email already exists");
}
const user = this.usersRepository.create(createUserDto);
return await this.usersRepository.save(user);
}
}
❌ Misconception: "I should modify files in node_modules/"
Reality: NEVER modify node_modules - changes are lost when you reinstall.
Why this matters: node_modules is generated from package.json and should be treated as read-only.
Example:
# ❌ Wrong: Editing installed packages
node_modules/@nestjs/common/decorators/core/module.decorator.d.ts
# Changes lost on: npm install
# ✅ Right: Create your own files
src/common/decorators/custom-module.decorator.ts
# Your code, under version control
❌ Misconception: "main.ts shouldn't be changed"
Reality: main.ts is where you configure your application - you'll enhance it frequently.
Why this matters: Global configuration like CORS, validation, and middleware goes in main.ts.
Example:
// Basic main.ts (initial):
async function bootstrap() {
const app = await NestFactory.create(AppModule);
await app.listen(3000);
}
// Enhanced main.ts (real projects):
async function bootstrap() {
const app = await NestFactory.create(AppModule);
// Enable CORS
app.enableCors({
origin: process.env.FRONTEND_URL,
credentials: true,
});
// Global validation
app.useGlobalPipes(
new ValidationPipe({
whitelist: true,
forbidNonWhitelisted: true,
transform: true,
})
);
// Global exception filter
app.useGlobalFilters(new HttpExceptionFilter());
// API prefix
app.setGlobalPrefix("api/v1");
// Swagger documentation
const config = new DocumentBuilder()
.setTitle("My API")
.setVersion("1.0")
.build();
const document = SwaggerModule.createDocument(app, config);
SwaggerModule.setup("api/docs", app, document);
await app.listen(3000);
}
❌ Misconception: "All my code should go in the src/ root"
Reality: Organize code into feature folders for maintainability.
Why this matters: Flat structures become unmanageable as projects grow.
Example:
# ❌ Bad: Everything in src/ root
src/
users.controller.ts
users.service.ts
products.controller.ts
products.service.ts
orders.controller.ts
orders.service.ts
auth.controller.ts
auth.service.ts
create-user.dto.ts
create-product.dto.ts
# 50+ files in one folder - chaos!
# ✅ Good: Feature-based organization
src/
users/
users.controller.ts
users.service.ts
users.module.ts
dto/
products/
products.controller.ts
products.service.ts
products.module.ts
dto/
# Clear structure, easy to navigate
Troubleshooting Common Setup Issues
Problem: "nest: command not found"
Symptoms: After installing CLI, nest command doesn't work
Common Causes:
- CLI not installed globally (80%)
- Terminal not restarted (15%)
- npm global path not in system PATH (5%)
Solution:
# Step 1: Verify installation
npm list -g @nestjs/cli
# If not installed:
npm install -g @nestjs/cli
# Step 2: Close and reopen terminal
# Step 3: Try again
nest --version
# If still not working, use npx:
npx @nestjs/cli new my-project
Problem: "Port 3000 is already in use"
Symptoms: Error: listen EADDRINUSE: address already in use :::3000
Common Causes:
- Another NestJS instance running (60%)
- Other application using port 3000 (30%)
- Zombie process from crash (10%)
Solution:
# Option 1: Find and kill the process
# macOS/Linux:
lsof -i :3000
kill -9 <PID>
# Windows:
netstat -ano | findstr :3000
taskkill /PID <PID> /F
# Option 2: Use a different port
# Edit src/main.ts:
await app.listen(3001); // Use port 3001 instead
# Option 3: Use environment variable
await app.listen(process.env.PORT || 3000);
# Then run: PORT=3001 npm run start:dev
Problem: "Cannot find module '@nestjs/common'"
Symptoms: Import errors, application won't start
Common Causes:
- Dependencies not installed (90%)
- node_modules corrupted (10%)
Solution:
# Step 1: Install dependencies
npm install
# Step 2: If still failing, clean install
rm -rf node_modules package-lock.json
npm install
# Step 3: Verify installation
npm list @nestjs/common
# Should show version number
Problem: "Hot reload not working"
Symptoms: Changes don't reflect, must restart manually
Common Causes:
- Running in production mode (70%)
- File not saved (20%)
- TypeScript compilation error (10%)
Solution:
# Verify you're using dev mode
ps aux | grep nest
# Should see: nest start --watch
# If not, restart with:
npm run start:dev
# Check for TypeScript errors in output:
[Nest] Error: Cannot find module...
# Fix the error and save again
Problem: "Module not found" after generating
Symptoms: Generated module can't be imported
Common Causes:
- Module not imported in parent module (95%)
- Wrong import path (5%)
Solution:
// Check app.module.ts
@Module({
imports: [
UsersModule, // Make sure your module is imported
],
})
export class AppModule {}
// Verify import path:
import { UsersModule } from "./users/users.module"; // ✅ Correct
import { UsersModule } from "./users/users"; // ❌ Wrong
Performance Implications
Development Mode Performance
Development mode trade-offs:
# Development mode (npm run start:dev)
Startup time: 2-5 seconds
Hot reload: 1-2 seconds per change
Memory usage: 100-150 MB
# Production mode (npm run start:prod)
Startup time: 0.5-1 second
No hot reload: N/A
Memory usage: 50-80 MB
Why development is slower:
- TypeScript compilation on every change
- Source maps generated for debugging
- Additional logging and error details
- File watching overhead
This is normal and expected! Development mode optimizes for developer experience, not performance.
Build Performance
Optimizing build times:
// tsconfig.json - Faster builds
{
"compilerOptions": {
"incremental": true, // Only recompile changed files
"skipLibCheck": true, // Skip type checking .d.ts files
"noEmit": false, // Generate JavaScript
"removeComments": true // Smaller output files
}
}
// Typical build times:
// Small project (< 50 files): 5-10 seconds
// Medium project (100-200 files): 15-30 seconds
// Large project (500+ files): 1-2 minutes
Reducing Startup Time
// Lazy load modules (covered in future articles)
@Module({
imports: [
// Load immediately:
CoreModule,
AuthModule,
// Lazy load (only when needed):
{
path: "admin",
loadChildren: () => import("./admin/admin.module"),
},
],
})
export class AppModule {}
Check Your Understanding
Quick Quiz
-
What command creates a new NestJS project? <details> <summary>Show Answer</summary>
nest new project-nameThis command:
- Creates project folder
- Generates initial files
- Installs dependencies
- Sets up TypeScript configuration </details>
-
Which file is the entry point of your application? <details> <summary>Show Answer</summary>
src/main.tsis the entry point.It contains the
bootstrap()function that:- Creates the NestJS application
- Configures global settings
- Starts the HTTP server
This is always the first file executed when your app starts. </details>
-
What's the difference between
npm run startandnpm run start:dev? <details> <summary>Show Answer</summary>npm run start (production mode):
- Runs once without watching files
- Faster startup
- No hot reload
- Use for production deployments
npm run start:dev (development mode):
- Watches files for changes
- Automatically restarts on changes
- Includes source maps for debugging
- Use during development
Always use
start:devwhen developing! </details> -
What does
nest generate resource userscreate? <details> <summary>Show Answer</summary>Creates a complete feature with:
- ✅ users.module.ts - Module definition
- ✅ users.controller.ts - HTTP routes
- ✅ users.service.ts - Business logic
- ✅ DTOs (create-user.dto.ts, update-user.dto.ts)
- ✅ Entity (user.entity.ts)
- ✅ Test files (.spec.ts)
- ✅ CRUD endpoints (if you chose Yes)
- ✅ Automatic import in AppModule
All from one command! </details>
Hands-On Exercise
Challenge: Create a "products" feature with full CRUD operations.
Requirements:
- Generate the products resource
- Add a route that returns all products
- Test it in your browser
- Modify the service to return custom data
<details> <summary>Show Solution</summary>
Step 1: Generate the resource
nest generate resource products
# Choose: REST API
# Choose: Y (generate CRUD)
Step 2: The controller already has routes!
// src/products/products.controller.ts
// GET /products route already exists
@Get()
findAll() {
return this.productsService.findAll();
}
Step 3: Test in browser
Visit: http://localhost:3000/products
You'll see: "This action returns all products"
Step 4: Modify service with custom data
// src/products/products.service.ts
@Injectable()
export class ProductsService {
private products = [
{ id: 1, name: "Laptop", price: 999 },
{ id: 2, name: "Mouse", price: 25 },
{ id: 3, name: "Keyboard", price: 75 },
];
findAll() {
return this.products;
}
findOne(id: number) {
return this.products.find((p) => p.id === id);
}
}
Step 5: Test again
Visit: http://localhost:3000/products
Now you see: [{"id":1,"name":"Laptop","price":999},...]
Explanation:
- Resource generator created complete structure
- Controller handles HTTP routing
- Service contains the data and logic
- Changes reflect immediately with hot reload </details>
Summary: Key Takeaways
🎯 Project Setup Essentials:
- Install NestJS CLI globally:
npm i -g @nestjs/cli - Create projects:
nest new project-name - Always use
npm run start:devduring development - Hot reload saves time - no manual restarts needed
📁 Project Structure:
src/main.ts- Application entry pointsrc/app.module.ts- Root module that imports everythingsrc/app.controller.ts- Sample HTTP routessrc/app.service.ts- Sample business logic- Feature folders (users/, products/) organize code by domain
🛠️ CLI Power Tools:
nest generate resource- Creates complete featuresnest generate controller- Creates controllersnest generate service- Creates servicesnest generate module- Creates modules- Add
--no-specto skip test files - Use
--dry-runto preview changes
⚡ Development Workflow:
- Start with:
npm run start:dev - Edit files and save
- Hot reload applies changes automatically
- Test immediately in browser
- Build for production:
npm run build
🏗️ Best Practices:
- Organize by feature, not by type
- Use CLI generators to maintain consistency
- Keep controllers thin - logic goes in services
- Import feature modules in AppModule
- Never modify node_modules/
⚠️ Remember:
- Generated code is boilerplate - implement real logic
- Development mode is slower (but worth it for hot reload)
- Port 3000 is default - change if needed
- CLI requires Node 18+ and npm 9+
Practical Exercises:
- Create a simple todo API with CRUD operations
- Build a blog API with posts and comments
- Practice generating different types of components
Pro Tips:
- Keep your dev server running all day
- Use the CLI for everything - don't create files manually
- Explore the generated code - it teaches best practices
- Check the logs - they show exactly what's happening
Ready to build real routes and handle HTTP requests? Let's continue to the Controllers article!
Version Information
Tested with:
- Node.js: v18.x, v20.x, v22.x
- NestJS CLI: v10.x
- npm: v9.x, v10.x
System Requirements:
- Node.js 18 or higher (required)
- npm 9 or higher (recommended)
- 4 GB RAM minimum
- 1 GB free disk space
Known Issues:
- ⚠️ Windows: Use PowerShell or Git Bash (not CMD)
- ⚠️ macOS: May need
sudofor global npm installs - ⚠️ Linux: Configure npm global path without sudo
CLI Versions:
- NestJS CLI 10.x - Current stable
- Breaking changes from v9: None significant
- Backwards compatible with NestJS 9.x projects