How to Use NestJS: The Beginner's Guide (First App, Modules, Controllers, Services, and Your First CRUD Resource)
CodexFuse Team · 2026-10-31T03:57:27.257Z · 15 min read
How do you use NestJS?
You use NestJS by installing the Nest CLI, scaffolding a project, and organizing your code into the module/controller/service triangle: controllers receive HTTP requests, services hold the business logic, and modules bundle them into features. This is how to use NestJS end to end, and it is the whole workflow the rest of this guide walks you through.
- Scaffold. Run
nest new my-app, answer the CLI's two prompts (module system and observability), and start the dev server withnpm run start:dev— your API answers athttp://localhost:3000. - Understand the triangle. A request lands on a controller, the controller calls a service, and both are registered inside a module. Once you see this once, every NestJS codebase makes sense.
- Generate features with the CLI.
nest g module cats,nest g controller cats, andnest g service catscreate the files and wire them together — no manual registration. - Build a CRUD resource.
nest g resource productsgenerates the full controller/service/DTO set; you fill in real logic instead of boilerplate. - Validate input. Add
class-validatorand one global pipe, and every request body is checked against your DTO rules automatically. - Ship it. Run
npm run build, keep secrets in environment variables, and deploy the compileddistoutput.
The six steps above are the whole beginner path. The rest of this guide explains what each one is actually doing, so you learn NestJS as a framework rather than a set of incantations. You will build a real JSON API along the way — when it is running, point our free REST API tester at http://localhost:3000 and poke at your endpoints without writing a client.
What is NestJS, and why do beginners pick it?
NestJS is a progressive Node.js framework for building server-side applications, written in TypeScript and heavily inspired by Angular's architecture. Under the hood it runs on Express by default, with Fastify and the new Node.js HTTP adapter available as alternatives (docs.nestjs.com, checked October 30, 2026). Instead of giving you a blank router and letting structure emerge, NestJS gives you the structure up front: every feature is a module, every route lives in a controller, and every piece of reusable logic lives in a service.
That opinionated layout is exactly why beginners are steered toward it. With Express you can put anything anywhere, which means a beginner project becomes a maze the moment it grows past a few routes. NestJS forces the healthy habits — separation of concerns, dependency injection, consistent patterns — before your codebase is big enough to need them. Companies adopt it for the same reason: a NestJS codebase written by five developers looks like it was written by one.
What do you need before you start?
The prerequisites are short but have one detail that catches people out: running a Nest application requires Node.js v20.19 or later (or v22.12+ on the 22.x line), but the Nest CLI's generators — nest new, nest generate — need v22.22.3+, v24.15+, or v26+ (docs.nestjs.com, checked October 30, 2026). The docs recommend the latest active LTS release, which satisfies both requirements at once. Check yours with node --version before installing anything.
Besides Node, you need npm (it comes with Node) and a code editor. Our VS Code beginner guide gets the editor side covered; NestJS projects are plain TypeScript, so any editor with TypeScript support works.
How to install NestJS and create your first app
Install the CLI globally, then let it scaffold the project:
$ npm i -g @nestjs/cli
$ nest new my-first-app
The CLI asks two questions. First, which module system to use: ESM (the default), which ships Vitest as the test runner, or CommonJS, which ships Jest. Pick ESM unless you have a reason not to — it is the modern path. Second, whether to set up NestJS Observe, the official observability platform; in an interactive terminal this defaults to yes, and you can skip the prompt any time with --no-observe (docs.nestjs.com, checked October 30, 2026). New projects are generated with TypeScript's strict mode enabled, which is a beginner-friendly default: the compiler catches type mistakes early instead of letting them become runtime bugs.
The scaffold creates a src/ directory with five core files:
| File | What it is |
|---|---|
main.ts | Entry point. Uses NestFactory to create the app and starts the HTTP listener. |
app.module.ts | The root module of the application. |
app.controller.ts | A basic controller with a single route. |
app.service.ts | A basic service with a single method. |
app.controller.spec.ts | The unit tests for the controller. |
The bootstrap in main.ts is worth reading once, because it is the only framework magic you will ever need to touch:
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module.js';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
await app.listen(process.env.PORT ?? 3000);
}
await bootstrap();
Start the app with npm run start, then open http://localhost:3000 in your browser — you will see "Hello World!". During development use npm run start:dev instead; it watches your files and recompiles on every change. If builds feel slow later, the docs suggest the SWC builder via npm run start -- -b swc (docs.nestjs.com, checked October 30, 2026). Your first app is live, and the scaffold also installed a linter (oxlint) and formatter (Prettier) with ready-made npm run lint and npm run format scripts, so your code stays clean from day one.
How do NestJS controllers, services, and modules fit together?
Here is the whole mental model, and everything in this guide hangs off it. A NestJS feature is a module — a class decorated with @Module() that bundles related code together. Inside the module, controllers handle incoming HTTP requests and map them to functions using decorators like @Get() and @Post(). Services (also called providers) hold the business logic — database calls, calculations, calls to other APIs. A request lands on a controller, the controller asks a service to do the work, and the controller returns the result.
The reason controllers never do the work themselves is testability and reuse. The same service can be injected into three different controllers, a WebSocket gateway, and a background job. If the logic lived in a controller, you would have to copy it everywhere. NestJS enforces this separation structurally, so you never have to decide where code belongs: if it touches HTTP, it is a controller; if it does work, it is a service; if it groups a feature, it is a module.
Dependency injection in plain words
Before you write your first service, you need the one concept NestJS is built on. Dependency injection means a class never creates the objects it needs with new — the framework creates them and hands them in through the constructor. Your controller declares what it needs, and NestJS supplies it.
import { Controller, Get } from '@nestjs/common';
import { CatsService } from './cats.service';
@Controller('cats')
export class CatsController {
constructor(private readonly catsService: CatsService) {}
@Get()
findAll() {
return this.catsService.findAll();
}
}
Notice there is no new CatsService() anywhere. The service class is marked @Injectable() and listed in the module's providers array, which tells NestJS: "you may inject this wherever it is asked for." By default each service is a singleton — one instance shared across the whole app (dev.to first-week guide, 2025). This matters more than it sounds: it means your service can safely hold a database connection or a cache, and every part of the app sees the same instance.
How to create your first controller and service
Stop writing files by hand — the CLI generates them and registers them in the module automatically:
$ nest g module cats
$ nest g controller cats
$ nest g service cats
After these three commands, the module decorator looks like this, and you did not edit it yourself:
import { Module } from '@nestjs/common';
import { CatsController } from './cats.controller';
import { CatsService } from './cats.service';
@Module({
controllers: [CatsController],
providers: [CatsService],
})
export class CatsModule {}
Now give the service something to do. Edit cats.service.ts to hold an in-memory list, and have the controller delegate to it. The @Controller('cats') decorator sets the route prefix, so @Get() on a method answers GET /cats, @Get(':id') answers GET /cats/1, and @Post() with @Body() reads the request body (devbyseb.com beginner guide, 2026). Test your work with npm run start:dev running and a POST to http://localhost:3000/cats — this is where knowing the Postman beginner guide pays off, or use the REST API tester linked at the top of this article.
How to build your first CRUD resource
Once the triangle makes sense, you are ready for the generator's best trick: nest generate resource scaffolds a full REST resource in one command. It asks which transport layer you want — pick REST API for a standard web backend (thecodinganalyst.com, 2022):
$ nest generate resource products
The generator creates the module, controller, service, entity class, and the DTO (Data Transfer Object) files, then registers everything in app.module.ts. DTOs deserve a moment of your attention: they are classes that describe the shape of data crossing the network boundary. NestJS prefers classes over TypeScript interfaces here for a deliberate reason — classes survive compilation, so the framework can inspect them at runtime and validate incoming data against them, which interfaces cannot do (dev.to first-week guide, 2025).
The generated controller already maps the five standard endpoints. Replace the placeholder logic with your own, one route at a time:
| Method + path | Decorator | What it should do |
|---|---|---|
GET /products | @Get() | Return all products. |
GET /products/:id | @Get(':id') | Return one product by id. |
POST /products | @Post() + @Body() | Create a product from the body. |
PATCH /products/:id | @Patch(':id') | Update one product partially. |
DELETE /products/:id | @Delete(':id') | Remove one product. |
If any of these endpoint concepts feel fuzzy, read our plain-English guide to what an API is first — it covers HTTP methods and status codes without assuming prior backend experience.
How to add validation to a NestJS API
A CRUD API that accepts any JSON is a bug factory. NestJS solves input validation with two pieces: the ValidationPipe and DTO classes decorated with rules from the class-validator package. First install the dependencies (docs.nestjs.com/application/validation, checked October 30, 2026):
$ npm i --save class-validator class-transformer
Then bind the pipe once at the application level, so every endpoint is protected from invalid data:
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.useGlobalPipes(new ValidationPipe());
await app.listen(process.env.PORT ?? 3000);
}
await bootstrap();
Now declare the rules on your DTO with decorators:
import { IsEmail, IsNotEmpty, MinLength } from 'class-validator';
export class CreateUserDto {
@IsEmail()
email: string;
@IsNotEmpty()
@MinLength(8)
password: string;
}
When a request arrives with an invalid email or a short password, the pipe rejects it with a 400 response before your controller code ever runs — you write no if checks by hand. This is the wiring the docs themselves prescribe, and it is the correct path (docs.nestjs.com/application/validation, checked October 30, 2026).
One newer alternative is worth knowing about: the same docs now ship a StandardSchemaValidationPipe that validates schemas written with Zod, Valibot, or ArkType instead of decorator classes. Install zod, define the schema, and bind the pipe globally:
$ npm i --save zod
// in main.ts:
app.useGlobalPipes(new StandardSchemaValidationPipe());
// or per-parameter:
@Body({ schema: createUserSchema }) createUserDto: CreateUserDto
Both pipes can coexist — the class-based pipe only validates class-typed parameters, and the schema pipe only validates parameters that declare a schema, so a team can migrate one endpoint at a time (docs.nestjs.com/application/validation, checked October 30, 2026). For a first project, stick with class-validator; reach for the schema pipe when you prefer Zod's API.
NestJS vs Express: which one should you pick?
Most NestJS beginners come from Express, so this comparison belongs in every honest beginner guide. The short answer: Express gives you freedom, NestJS gives you structure, and both run on the same Node.js runtime.
| Express | NestJS | |
|---|---|---|
| Routing | app.get('/cats', handler) — functions and middleware. | @Controller('cats') + @Get() — classes and decorators. |
| Structure | You decide. Folders grow organically. | Modules, controllers, services — enforced by the framework. |
| Dependency injection | Manual — you pass or import what you need. | Built in — declare constructor parameters and it is wired. |
| Language | JavaScript, TypeScript optional. | TypeScript first; strict mode on by default. |
| Under the hood | It is the HTTP layer. | Runs on Express by default (or Fastify, or the node:http adapter). |
| Best for | Small services, prototypes, maximum control. | Teams, growing codebases, long-lived backends. |
Here is the framing to carry with you: NestJS does not replace Express, it organizes it. When people say "NestJS is opinionated," they mean the decisions above are already made for you — and for a beginner, pre-made good decisions beat unlimited freedom (codestax.medium.com getting-started guide, 2026).
What are guards, and how do you test a NestJS app?
Two topics every beginner tutorial stops short of: authorization and testing. A guard is NestJS's answer to "who is allowed to call this route?" It is a class with a canActivate method that returns true or false, attached to a controller or route with @UseGuards(). Authentication asks "who are you?" (handled by libraries like Passport); a guard asks "are you allowed?" — checking roles, ownership, or API keys. You do not need guards on day one, but knowing where they live keeps you from bolting auth logic into controllers later.
Testing is already wired into your scaffold. The CLI generated app.controller.spec.ts next to every controller, and the test runner matches your module-system choice: Vitest for ESM projects, Jest for CommonJS (docs.nestjs.com, checked October 30, 2026). Run npm test and watch the generated spec pass. The beginner habit to build: whenever you add a service method, add a spec that constructs the service directly — constructor injection makes this trivially easy, since you can pass fake dependencies into the constructor with no framework setup.
How do you build and run NestJS in production?
Development mode recompiles on every change, which is the opposite of what you want on a server. The production path is two commands:
$ npm run build # compiles TypeScript into dist/
$ npm run start:prod # runs node dist/main
Three rules make the jump to production uneventful. First, secrets never live in source files: read database URLs and API keys from environment variables, and wire the official @nestjs/config package if you want typed config — never commit a real .env to version control. Second, keep the port configurable with process.env.PORT ?? 3000 (already in your scaffolded main.ts), because hosts assign the port. Third, commit early and often — our GitHub beginner guide covers pushing the project and the CI habits that keep a backend team sane. The rest — logging, health checks, process managers — is ordinary Node.js operations on top of the same app you have been running locally.
Sources
- NestJS official documentation — First steps (setup, CLI prompts, project structure, prerequisites, lint/format scripts, platform adapters): docs.nestjs.com/first-steps (checked October 30, 2026)
- NestJS official documentation — Validation (ValidationPipe, class-validator setup, StandardSchemaValidationPipe, Zod schema example): docs.nestjs.com/application/validation (checked October 30, 2026)
- Node.js version requirements for Nest CLI generators: docs.nestjs.com/first-steps (checked October 30, 2026)
- "My First Week Learning NestJS (Coming from Frontend)" — DI, DTOs, validation walkthrough: dev.to (2025)
- "Getting Started with NestJS: Build Scalable Node.js Applications" — controller/service/module walkthrough: codestax.medium.com (June 2026)
- "Getting started with NestJS" —
nest generate resourceREST CRUD generation: thecodinganalyst.com (2022) - "Beginner's Guide to Building Scalable Node.js Apps with Nest.js" — controller generation and routing basics: devbyseb.com (2026)
Frequently asked questions
Is NestJS hard to learn for beginners?
NestJS has a steeper first week than Express because it asks you to learn modules, decorators, and dependency injection up front — but those concepts are exactly what keep a growing project organized. Developers coming from Angular find it familiar; developers coming from frontend React usually click with the decorator-based routing within the first week (dev.to first-week guide, 2025). Start with the scaffold, one controller, and one service, and add concepts only when you feel their absence.
Do I need to know TypeScript before learning NestJS?
You should know TypeScript basics — types, interfaces, classes, and decorators — because NestJS is TypeScript-first and generates strict-mode projects by default. You do not need to be a TypeScript expert: the framework's patterns (controllers, DTOs, services) teach you the useful subset as you go. The decorators like @Controller() and @Get() are the main new syntax, and they read almost like labels.
Can NestJS replace Express?
NestJS does not replace Express so much as sit on top of it — Express is the default HTTP engine under a NestJS app, and you can swap in Fastify or the built-in node:http adapter when you need more speed (docs.nestjs.com, checked October 30, 2026). Choose Express when you want a minimal, unopinionated starting point; choose NestJS when you want enforced structure, dependency injection, and a codebase that stays navigable as it grows.
How do I connect NestJS to a database?
The standard path is the official database integrations — @nestjs/typeorm, @nestjs/mongoose, and the Prisma integration — installed into your app and exposed through services via dependency injection. Your service receives the repository or Prisma client in its constructor, exactly like it receives any other dependency, so database access follows the same module/service pattern the rest of your app uses. Keep connection strings in environment variables, never in committed code.
Which is better for validation: class-validator or Zod?
The NestJS docs teach class-validator with the global ValidationPipe as the standard path — decorators on DTO classes, one line of setup, every endpoint protected (docs.nestjs.com/application/validation, checked October 30, 2026). The newer StandardSchemaValidationPipe supports Zod, Valibot, and ArkType schemas for teams that prefer schema definitions over decorators. Both pipes can run side by side, so start with class-validator and adopt schemas incrementally if your team prefers Zod.