Video

Watch the full video on YouTube:

NestJS First Project

Creating Your First NestJS Project

Now that your environment is set up, it is time to create your first NestJS project. This guide walks through the entire process including handling common issues that arise when using pnpm as your package manager.

Step 1: Scaffold the Project

NestJS provides a CLI tool that scaffolds a new project with a sensible default structure. You can use it directly without installing it globally by using pnpm dlx:

pnpm dlx @nestjs/cli new my-nestjs-api

The CLI will ask you which package manager to use. Even though you have pnpm installed, the NestJS CLI might not detect it automatically. Select npm for now and we will adjust later if needed. The important thing is to get the project scaffolded.

Troubleshooting pnpm Creation Issues

If you encounter errors when creating a NestJS project with pnpm, you are not alone. This is a known issue that occurs because the NestJS CLI has specific expectations about the package manager workflow.

The solution: Use pnpm dlx instead of installing the CLI globally. The pnpm dlx command downloads and executes the package in a temporary environment, avoiding conflicts with globally installed packages.

pnpm dlx @nestjs/cli new my-nestjs-api

If you still face issues, you can also install the CLI globally with npm and use it that way:

npm install -g @nestjs/cli
nest new my-nestjs-api

Once the project is created, you can delete the node_modules folder and package-lock.json, then run pnpm install to use pnpm instead.

Step 2: Understand the Build Approval

When you scaffold a NestJS project, you will notice it asks for permission to install dependencies and create the project. NestJS CLI asks this because it needs to run shell commands to set up the project. Always review what the CLI is about to do, but in this case it is safe to approve.

Step 3: Run the Development Server

Once the project is created, navigate into the directory and start the development server:

cd my-nestjs-api
pnpm run start:dev

The start:dev command uses watch mode, which means the server will automatically restart whenever you make changes to your source files. This provides a fast feedback loop during development.

You should see output similar to:

[Nest] INFO  - Nest application successfully started

Step 4: Verify It Works

Open your browser and navigate to:

http://localhost:3000

You should see a “Hello World!” message. Congratulations, your NestJS application is running.

Understanding the Project Structure

The scaffolded NestJS project has a clean and organized structure. Here are the most important files and folders:

my-nestjs-api/
├── src/
│   ├── app.controller.ts      # Basic controller with a root route
│   ├── app.controller.spec.ts # Tests for the controller
│   ├── app.module.ts          # Root module of the application
│   ├── app.service.ts         # Basic service with business logic
│   └── main.ts                # Application entry point
├── test/
│   ├── app.e2e-spec.ts        # End to end tests
│   └── jest-e2e.json          # Jest configuration for e2e tests
├── nest-cli.json              # NestJS CLI configuration
├── package.json               # Dependencies and scripts
├── tsconfig.json              # TypeScript configuration
├── tsconfig.build.json        # TypeScript config for production builds
└── eslint.config.js           # ESLint configuration

src/main.ts

This is the entry point of your application. It creates a NestJS application using the NestFactory and starts listening on a configurable port.

import { NestFactory } from "@nestjs/core"
import { AppModule } from "./app.module"

async function bootstrap() {
  const app = await NestFactory.create(AppModule)
  await app.listen(process.env.PORT ?? 3000)
}
bootstrap()

src/app.module.ts

The root module imports controllers and providers to bootstrap the application. Every NestJS application needs at least one module.

import { Module } from "@nestjs/common"
import { AppController } from "./app.controller"
import { AppService } from "./app.service"

@Module({
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule {}

src/app.controller.ts

Controllers define routes and handle incoming HTTP requests. The scaffolded controller exposes a single GET / endpoint.

import { Controller, Get } from "@nestjs/common"
import { AppService } from "./app.service"

@Controller()
export class AppController {
  constructor(private readonly appService: AppService) {}

  @Get()
  getHello(): string {
    return this.appService.getHello()
  }
}

src/app.service.ts

Services contain the business logic of your application. They are injectable and can be used by controllers and other services.

import { Injectable } from "@nestjs/common"

@Injectable()
export class AppService {
  getHello(): string {
    return "Hello World!"
  }
}

What We Built

In this guide we scaffolded a NestJS project, resolved common pnpm related issues, started the development server, and explored the default project structure. This foundation will serve as the starting point for building real API endpoints in the coming parts of this series.

Recommendations

This post is part of a NestJS API development series. Check out the other entries:

NestJS API Development Guide 02: RequirementsNestJS API Development Guide 04: Git Repository Setup