npm init -y npm install @nestjs/[email protected] @nestjs/[email protected] @nestjs/[email protected] [email protected] [email protected]
| Library | Description |
|---|---|
| @nestjs/common | Contains vast majority of functions, classes, etc, that we need from Nest |
| @nestjs/core | The core runtime of NestJS that powers dependency injection, module loading, and lifecycle management |
| @nestjs/platform-express | Lets Nest use Express JS for handling HTTP requests |
| reflect-metadata | Helps make decorators work. Tons more on this in just a minute! |
| typescript | We write Nest apps with Typescript. |
{ "compilerOptions": { "module": "commonjs", "target": "es2017", "experimentalDecorators": true, "emitDecoratorMetadata": true } }
| Setting | Description |
|---|---|
| "experimentalDecorators": true | Enables the use of decorators (@Something) in TypeScript. Without this, NestJS decorators (like @Controller()) would throw errors. |
| "emitDecoratorMetadata": true | Works together with the reflect-metadata library. Emits extra type information about classes and methods at runtime, which NestJS uses for dependency injection. |
flowchart LR
A[Request]
G[Response]
subgraph Server
direction LR
B[**Pipe**<br/><br/>Validate data<br/>contained in<br/>the request]
B --> C[**Guard**<br/><br/>Make sure<br/>the user is<br/>authenticated]
C --> D[**Controller**<br/><br/>Route the<br/>request to a<br/>particular<br/>function]
D --> E[**Service**<br/><br/>Run some<br/>business logic]
E --> F[**Repository**<br/><br/>Access a<br/>database]
end
A --> Server
direction RL
Server --> G
%% Define style classes
classDef bigText font-size:30px;
%% Apply them
class A,B,C,D,E,F,G bigText
| Part | Description |
|---|---|
| Pipes | Validates incoming data |
| Guards | Handles authentication |
| Controllers | Handles incoming requests |
| Services | Handles data access and business logic |
| Repositories | Handles data stored in a DB |
| Modules | Groups together code |
| Filters | Handles errors that occur during request handling |
| Interceptors | Adds extra logic to incoming requests or outgoing responses |
src/main.ts)import { Controller, Module, Get } from "@nestjs/common"; import { NestFactory } from "@nestjs/core"; @Controller() class AppController { @Get() getRootRoute() { return "hi there!"; } } @Module({ controllers: [AppController], }) class AppModule {} async function bootstrap() { const app = await NestFactory.create(AppModule); await app.listen(3000); } bootstrap();
npx ts-node-dev src\main.ts
Nest application successfully startedlocalhost:3000main.ts
function bootstrapapp.controller.ts
class AppController {}app.module.ts
class AppModule {}name.type_of_thing.tsapp.controller.tsimport { Controller, Get } from "@nestjs/common"; @Controller() export class AppController { @Get() getRootRoute() { return "hi there!"; } }
app.module.tsimport { Module } from "@nestjs/common"; import { AppController } from "./app.controller"; @Module({ controllers: [AppController], }) export class AppModule {}
app.controller.tsimport { NestFactory } from "@nestjs/core"; import { AppModule } from "./app.module"; async function bootstrap() { const app = await NestFactory.create(AppModule); await app.listen(3000); } bootstrap();
app.controller.tsimport { Controller, Get } from "@nestjs/common"; @Controller("/app") export class AppController { @Get("/hi") getRootRoute() { return "hi there!"; } @Get("/bye") getByeThere() { return "bye there!"; } }
npm i -g @nestjs/cli
nest new project_name
npm run start:dev
nest generate module messages
import { NestFactory } from "@nestjs/core"; import { MessagesModule } from "./messages/messages.module"; async function bootstrap() { const app = await NestFactory.create(MessagesModule); await app.listen(3000); } bootstrap();
nest generate controller messages/messages --flat
--flat says dont create folder called controllersimport { Controller, Get, Post } from "@nestjs/common"; @Controller("messages") export class MessagesController { @Get() listMessages() {} @Post() createMessage() {} @Get("/:id") getMessage() {} }
Test using Postman or Thunder Client (vscode extension) for success status code
import { Controller, Get, Post, Body, Param } from "@nestjs/common"; @Controller("messages") export class MessagesController { @Get() listMessages() {} @Post() createMessage(@Body() body: any) { console.log(body); } @Get("/:id") getMessage(@Param("id") id: string) { console.log(id); } }
Test for console log
npm install class-validator class-transformer
import { NestFactory } from '@nestjs/core'; + import { ValidationPipe } from '@nestjs/common'; import { MessagesModule } from './messages/messages.module'; async function bootstrap() { const app = await NestFactory.create(MessagesModule); + app.useGlobalPipes(new ValidationPipe()); await app.listen(3000); } bootstrap();
export class CreateMessageDto { content: string; }
+ import { IsString } from 'class-validator'; export class CreateMessageDto { + @IsString() content: string; }
How just adding dto type works?
When ts compiles to js. Type information is lost but Because of emitDecoratorMetadata in tsconfig, some of type information is also converted into javascript
import { Controller, Get, Post, Body, Param } from '@nestjs/common'; + import { CreateMessageDto } from './dtos/create-message.dto'; @Controller('messages') export class MessagesController { @Get() listMessages() {} @Post() + createMessage(@Body() body: CreateMessageDto) { console.log(body); } @Get('/:id') getMessage(@Param('id') id: string) { console.log(id); } }
Services and Repositories end up having similar method names
src/messages/messages.repository.tsimport { readFile, writeFile } from "fs/promises"; export class MessagesRepository { async findOne(id: string) { const contents = await readFile("messages.json", "utf8"); const messages = JSON.parse(contents); return messages[id]; } async findAll() { const contents = await readFile("messages.json", "utf8"); const messages = JSON.parse(contents); return messages; } async create(content: string) { const contents = await readFile("messages.json", "utf8"); const messages = JSON.parse(contents); const id = Math.floor(Math.random() * 999); messages[id] = { id, content }; await writeFile("messages.json", JSON.stringify(messages)); } }
src/messages/messages.service.tsimport { MessagesRepository } from "./messages.repository"; export class MessagesService { messagesRepo: MessagesRepository; constructor() { // Service is creating its own dependencies // DONT DO THIS ON REAL APPS // USE DEPENDENCY INJECTION this.messagesRepo = new MessagesRepository(); } findOne(id: string) { return this.messagesRepo.findOne(id); } findAll() { return this.messagesRepo.findAll(); } create(content: string) { return this.messagesRepo.create(content); } }
src\messages\messages.controller.tsimport { Controller, Get, Post, Body, Param, + NotFoundException, } from "@nestjs/common"; import { CreateMessageDto } from "./dtos/create-message.dto"; + import { MessagesService } from "./messages.service"; @Controller("messages") export class MessagesController { + messagesService: MessagesService; + constructor() { + // DONT DO THIS ON REAL APP + // USE DEPENDENCY INJECTION + this.messagesService = new MessagesService(); + } @Get() listMessages() { + return this.messagesService.findAll(); } @Post() createMessage(@Body() body: CreateMessageDto) { + return this.messagesService.create(body.content); } @Get("/:id") + async getMessage(@Param("id") id: string) { + const message = await this.messagesService.findOne(id); + + if (!message) { + throw new NotFoundException("message not found"); + } + + return message; + } }
messages.json with empty json object{}
Test using Postman or Thunder Client (vscode extension) for success Create and Read.
src/messages/messages.repository.tsimport { Injectable } from "@nestjs/common"; @Injectable() export class MessagesRepository { // ... }
src/messages/messages.service.tsimport { Injectable } from "@nestjs/common"; @Injectable() export class MessagesService { constructor(public messagesRepo: MessagesRepository) {} // shorthand // ... }
src\messages\messages.module.ts// ...imports import { MessagesService } from "./messages.service"; import { MessagesRepository } from "./messages.repository"; @Module({ controllers: [MessagesController], providers: [MessagesService, MessagesRepository], // Add This })
src\messages\messages.controller.ts// ... @Controller("messages") export class MessagesController { constructor(public messagesService: MessagesService) {} // ... }
Test using Postman or Thunder Client (vscode extension) for success Create and Read.
Create new Project
Concept of Project
There is a computer (module with controller)
Computer has CPU (module with some service) and Disk (module with some service)
Both CPU and Disk depend on PowerSupply (module with some service)
Power supply is independent
CPU depend on PowerSupply
Disk depend on PowerSupply
Computer depend on CPU and Disk
nest new di
Delete everything inside src except main
Generate Computer module, cpu module, disk module, power module
nest g module computer nest g module cpu nest g module disk nest g module power
nest g service cpu nest g service disk nest g service power
nest g controller computer
import { NestFactory } from "@nestjs/core"; import { ComputerModule } from "./computer/computer.module"; async function bootstrap() { const app = await NestFactory.create(ComputerModule); await app.listen(process.env.PORT ?? 3000); } bootstrap();
Steps: From one service to another service
- Add @Injectable() to service1
- In module, add service1 in list of providers
- In service2 constructor, initialize service1
Steps: From service1 of module1 to service2 of module2
- Add @Injectable() to service1 (this is already done while generating service)
- In module1, add service1 to list of providers (also done) and list of exports (not done)
- In module2, import module1 and add module1 in list of imports
- In service2 constructor, initialize service1
src\power\power.service.tsimport { Injectable } from "@nestjs/common"; @Injectable() export class PowerService { supplyPower(watts: number) { console.log(`Supplying ${watts} worth of power.`); } }
src\power\power.module.tsimport { Module } from "@nestjs/common"; import { PowerService } from "./power.service"; @Module({ providers: [PowerService], exports: [PowerService], // add this }) export class PowerModule {}
src\cpu\cpu.module.tsimport { Module } from "@nestjs/common"; import { CpuService } from "./cpu.service"; import { PowerModule } from "../power/power.module"; // add this @Module({ imports: [PowerModule], // add this providers: [CpuService], }) export class CpuModule {}
src\cpu\cpu.service.tsimport { Injectable } from "@nestjs/common"; import { PowerService } from "../power/power.service"; // add this @Injectable() export class CpuService { constructor(private powerService: PowerService) {} // add this }
src\cpu\cpu.service.tsimport { Injectable } from "@nestjs/common"; import { PowerService } from "../power/power.service"; @Injectable() export class CpuService { constructor(private powerService: PowerService) {} compute(a: number, b: number) { console.log("Drawing 10 watts of power from Power Service"); this.powerService.supplyPower(10); return a + b; } }
src\disk\disk.module.tsimport { Module } from "@nestjs/common"; import { DiskService } from "./disk.service"; import { PowerModule } from "../power/power.module"; // add this @Module({ imports: [PowerModule], // add this providers: [DiskService], }) export class DiskModule {}
src\disk\disk.service.tsimport { Injectable } from "@nestjs/common"; import { PowerService } from "../power/power.service"; // add this @Injectable() export class DiskService { constructor(private powerService: PowerService) {} // add this }
src\disk\disk.service.tsimport { Injectable } from "@nestjs/common"; import { PowerService } from "../power/power.service"; @Injectable() export class DiskService { constructor(private powerService: PowerService) {} getData() { console.log("Drawing 20 watts of power from PowerService"); this.powerService.supplyPower(20); return "data!"; } }
src\cpu\cpu.module.tsimport { Module } from "@nestjs/common"; import { CpuService } from "./cpu.service"; import { PowerModule } from "../power/power.module"; @Module({ imports: [PowerModule], providers: [CpuService], exports: [CpuService], // add this }) export class CpuModule {}
src\disk\disk.module.tsimport { Module } from "@nestjs/common"; import { DiskService } from "./disk.service"; import { PowerModule } from "../power/power.module"; @Module({ imports: [PowerModule], providers: [DiskService], exports: [DiskService], // add this }) export class DiskModule {}
src\computer\computer.module.tsimport { Module } from "@nestjs/common"; import { ComputerController } from "./computer.controller"; import { CpuModule } from "../cpu/cpu.module"; // add this import { DiskModule } from "../disk/disk.module"; // add this @Module({ imports: [CpuModule, DiskModule], // add this controllers: [ComputerController], }) export class ComputerModule {}
src\computer\computer.controller.tsimport { Controller } from "@nestjs/common"; import { CpuService } from "../cpu/cpu.service"; // add this import { DiskService } from "../disk/disk.service"; // add this @Controller("computer") export class ComputerController { constructor( // add this private cpuService: CpuService, private diskService: DiskService ) {} }
src\computer\computer.controller.tsimport { Controller, Get } from "@nestjs/common"; import { CpuService } from "../cpu/cpu.service"; import { DiskService } from "../disk/disk.service"; @Controller("computer") export class ComputerController { constructor( private cpuService: CpuService, private diskService: DiskService ) {} // add this @Get() run() { return [this.cpuService.compute(1, 2), this.diskService.getData()]; } }