NestJS
100 challenges · 0 mastered
NestJS is a progressive Node.js framework for building efficient, scalable server-side applications using TypeScript. It is heavily inspired by Angular and uses the same core concepts like modules, decorators, and dependency injection.
If Angular is the framework you use to build the front of a restaurant, NestJS is the same blueprint applied to the kitchen (the backend) -- same layout, same rules, just on the server.
By default NestJS uses Express as its HTTP server platform, but you can swap it for Fastify for higher performance. NestJS abstracts the platform so most of your code stays the same.
import { NestFactory } from '@nestjs/core';
import { FastifyAdapter, NestFastifyApplication } from '@nestjs/platform-fastify';
const app = await NestFactory.create<NestFastifyApplication>(
AppModule,
new FastifyAdapter(),
);NestJS is the driver, and Express or Fastify is the car. You can change cars without learning to drive again.
You use the Nest CLI command 'nest new project-name', similar to how Angular uses 'ng new'. The CLI scaffolds a ready-to-run project with a sample module, controller, and service.
npm i -g @nestjs/cli
nest new my-app
cd my-app
npm run start:devJust like 'ng new' builds an Angular skeleton for you, 'nest new' builds a backend skeleton you can run immediately.
The entry point is main.ts, which uses NestFactory to create an application instance from the root module and starts listening on a port. It is the backend equivalent of Angular's main.ts that bootstraps the root module.
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
await app.listen(3000);
}
bootstrap();main.ts is the ignition key -- it starts the whole engine from the root module, exactly like Angular's bootstrap.
The core building blocks are Modules, Controllers, and Providers (services). Modules group related code, controllers handle incoming requests, and providers contain business logic.
Modules are rooms, controllers are the receptionists taking requests, and providers are the workers doing the actual job behind the scenes.
A decorator is a special function prefixed with @ that attaches metadata to classes, methods, or parameters, telling NestJS how to treat them. NestJS uses the exact same TypeScript decorators concept as Angular.
@Controller('users')
export class UsersController {
@Get()
findAll() {
return [];
}
}A decorator is a sticky label on a box that tells the warehouse 'this is fragile' or 'this goes to shipping' -- just like @Component or @Injectable in Angular.
Express is a minimal, unopinionated HTTP library, while NestJS is a full framework that adds structure, dependency injection, modules, and TypeScript support on top of Express. NestJS gives you architecture out of the box; Express leaves it to you.
Express is a pile of LEGO bricks; NestJS is a LEGO kit with instructions and labeled bags so everyone builds the same way.
A controller is a class decorated with @Controller that handles incoming HTTP requests and returns responses. It defines routes and delegates business logic to services.
@Controller('cats')
export class CatsController {
@Get()
findAll(): string {
return 'This returns all cats';
}
}A controller is the receptionist at a desk: it takes the request, figures out who should handle it, and hands back the reply.
You pass a path prefix to the @Controller decorator and a path to the HTTP method decorator. The final route is the prefix joined with the method path.
@Controller('users')
export class UsersController {
@Get('profile')
getProfile() {
return 'profile';
}
}
// Maps to GET /users/profileThe @Controller path is the street name and the @Get path is the house number -- together they form the full address.
NestJS provides @Get, @Post, @Put, @Patch, @Delete, @Options, @Head, and @All decorators. Each maps a method to the matching HTTP verb.
@Post()
create() { return 'created'; }
@Delete(':id')
remove() { return 'deleted'; }These decorators are like signs on a door saying which kind of knock (GET, POST, DELETE) the room responds to.
You use the @Param decorator to extract values from the URL path. You can grab a single named parameter or the whole params object.
@Get(':id')
findOne(@Param('id') id: string) {
return `User ${id}`;
}@Param is like reading the table number off a customer's ticket so you know which table to serve.
You use the @Query decorator to extract query string values from the URL. You can read a specific key or the entire query object.
@Get()
findAll(@Query('page') page: string) {
return `Page ${page}`;
}
// GET /users?page=2Query params are the optional notes after the '?' -- like saying 'I want page 2' when asking for a list.
You use the @Body decorator to extract the parsed request body, usually typed with a DTO class. NestJS parses the JSON body automatically.
@Post()
create(@Body() createUserDto: CreateUserDto) {
return createUserDto;
}@Body is opening the package a customer mailed in and reading what's inside.
A provider is any class that can be injected as a dependency, such as services, repositories, or factories. Providers are marked with the @Injectable decorator and managed by Nest's IoC container.
@Injectable()
export class CatsService {
findAll() {
return ['Tom', 'Felix'];
}
}A provider is a worker the framework keeps on staff and hands to whoever needs them -- just like an Angular service.
It marks a class as a provider that can be managed and injected by Nest's dependency injection system. Without it, Nest cannot resolve the class as a dependency.
@Injectable()
export class LoggerService {
log(msg: string) {
console.log(msg);
}
}@Injectable is the badge that lets a worker into the building so the framework knows it can assign them to teams.
Controllers should stay thin and focus on handling requests, while services hold reusable business logic. This separation makes code testable, reusable, and easier to maintain.
@Controller('cats')
export class CatsController {
constructor(private catsService: CatsService) {}
@Get()
findAll() {
return this.catsService.findAll();
}
}The receptionist (controller) shouldn't cook the food -- they pass the order to the kitchen (service).
You declare the service as a constructor parameter, and Nest automatically provides an instance. This is the same constructor-based dependency injection used in Angular.
constructor(private readonly usersService: UsersService) {}You just ask for the service in the constructor and the framework delivers it, like ordering a part and it arrives ready to use.
You add the provider to the 'exports' array of its module so other modules that import it can use it. Without exporting, the provider stays private to its own module.
@Module({
providers: [UsersService],
exports: [UsersService],
})
export class UsersModule {}Exporting a service is like putting a tool on the shared shelf so other rooms can borrow it.
You add the provider class to the 'providers' array of the @Module decorator. Nest then makes it available for injection within that module.
@Module({
controllers: [CatsController],
providers: [CatsService],
})
export class CatsModule {}Listing a provider in 'providers' is like adding a worker to the room's staff roster.
A module is a class decorated with @Module that organizes related controllers, providers, imports, and exports into a cohesive feature unit. Every NestJS app has at least a root module.
@Module({
controllers: [CatsController],
providers: [CatsService],
})
export class CatsModule {}A module is a room that holds everything for one feature -- just like an Angular NgModule groups related components and services.
The root module, usually named AppModule, is the top-level module that Nest uses to build the application graph. It imports all feature modules.
@Module({
imports: [UsersModule, AuthModule],
})
export class AppModule {}The root module is the main floor plan that connects all the rooms together, like Angular's AppModule.
The @Module decorator accepts controllers, providers, imports, and exports. Controllers handle requests, providers are injectables, imports bring in other modules, and exports share providers.
@Module({
imports: [],
controllers: [],
providers: [],
exports: [],
})
export class FeatureModule {}It's like a room's setup form: who works here (providers), who answers the door (controllers), which rooms we connect to (imports), and what we lend out (exports).
You export providers from one module and import that module into another. The importing module can then inject the exported providers.
@Module({
providers: [AuthService],
exports: [AuthService],
})
export class AuthModule {}One room lends a tool by exporting it, and another room borrows it by importing the room -- like sharing Angular services across modules.
A feature folder usually contains a module file, a controller file, a service file, and a dto folder, plus optional entities. This keeps each domain self-contained and easy to navigate.
users/
users.module.ts
users.controller.ts
users.service.ts
dto/create-user.dto.ts
entities/user.entity.tsEach feature gets its own labeled drawer with everything it needs inside, instead of scattering files everywhere.
You add the module class to the 'imports' array of the @Module decorator. This makes the imported module's exported providers available.
@Module({
imports: [UsersModule],
controllers: [OrdersController],
})
export class OrdersModule {}Importing a module is like connecting a hallway between two rooms so one can use what the other lends out.
