What is NestJS ?
Nest is framework to build efficient, scalable Node.js server side applications. We can simply use Typescript and concepts like OOPS, Functional Programming are very basic things you should know.
Why to use Nestjs ?
NestJS is built around modularity thus it can organize the code into separate modules, making it easier to understand, maintain and scale.
Connect
Repository
Table of Contents
Understanding basic setup
1.1 Setting up project
1.2 Module creation
1.3 ModuleController and services 2.1 [Controller] 2.2 [Services]
1. Basics
Setting up project
$ npm i -g @nestjs/cli
$ nest new project-name
This is sufficient to setup our NestJS project which would give us few node modules along with some boilerplate files, an 'src' directory with core files.
src
|_app.controller.spec.ts
|_app.controller.ts
|_app.module.ts
|_app.service.ts
|_main.ts
app.controller.ts : Basic controller with single route app.controller.spec.ts : Unit tests for controller app.module.ts : Root module of the application app.service.ts : Basic service with single method main.ts : Entry file of application using 'NestFactory' to create Nest application instance
NOTE : We won't be going deep dive into the controllers , services , modules , just gain hands-on asap along with few insights like how under the hood things NestJS help us make our application scalable.
Module creation
You can create a module by just using you CLI
nest g module auth #auth module
nest g module user #user module
This would create a 'auth' folder along with auth.module.ts ( controller, services you have to create manually otherwise you can simply create it with nest g resource auth' to get all the files and folders structured with basic CRUD ) same goes with 'user'
auth
|_auth.module.ts
|_auth.controller.ts
|_auth.service.ts
Remember every module can be shared with other modules. Imagine sharing a service from 'user' module to auth service to create a user, to do that we need to export UserService provider by adding it in module's export array.
import { Module } from '@nestjs/common';
import { UserService } from './user.service';
@Module({
providers:[UserService],
exports:[UserService]
})
export class UserModule {}
Why are we doing this ? We can simply import the service right ?
No. Because it may lead to increased memory usage and if we import it from the modules then we can simply use the same instance of the UserModule to import the UserService, also it makes the shared resources consistent all across the application.
Note : We can import and export modules as well! Go try , think of a problem where you need a 'common' module and use it in other modules.
Understanding Dependency injection !!
Remember we had a 'user' service import in 'auth'
Yes we studied about 'shared instance' but dependency injection means calling the 'service' ( injecting ) when we need it.
Inside our 'auth.module.ts' we would subscribe the service
Inside our 'auth.service.ts' we would inject the dependency
providers: [UserService]: You tell Nest to create the instance.
exports: [UserService]: You share that instance with other modules.
imports: [UserModule] (in Auth): You "subscribe" to those shared services.
constructor(private user: UserService): You finally inject it into the specific file where you want to write logic.
Controller and Service
Controller
We use controllers to handle the incoming request and send the desired response to the end user
To create a controller let us use
nest g controller user
this would generate us user.controller.ts and user.controller.spec.ts and update the user.module.ts with controllers:[UserController];
Service
We use service to write the the business logic
Are you curious like why is there a decorator @Injectable ?
Because it serves the sole purpose of injecting the dependency.
Suppose to perform db operations which you want to implement in your 'user' service just to perform Basic CRUD
Our Decorator during runtime serves the db service to ensure we can easily inject it and perform the business logic.
@Injectable()
export class UserService {
constructor(private dbService: DbService) {}
// Nest sees the metadata: "Ah, this needs a DbService. I'll go grab the one from the database module."
}
What would happen without the @Injectable decorator ?
Nest has no idea what 'DbService' is at runtime. It cannot "inject" it.
// NO DECORATOR
export class UserService {
constructor(private dbService: DbService) {}
}
Let us create a Dummy service and import it in Controller
import { Injectable } from '@nestjs/common';
@Injectable()
export class UserService{
sign(){
return(`Hi, I'm signup`)
}
}
Now importing the business logic 'sign()' from service into controller at route '/signup-msg'
import { Controller, Post } from '@nestjs/common';
import { UserService } from './user.service';
@Controller('user')
export class userController {
constructor(private readonly userService: UserService){}
@Post('signup-msg')
getSignupmsg(){
return this.userService.sign();
}
}
Open your postman try it on the endpoint
'localhost:3000/usersignup-msg'
So, now let us go do some CRUD operations inside our application.
NOTE : I'm not writing down the code for connecting your database with Prisma because there are high chances of version mismatch just make sure you have a prismaService that would help us perform db operations in other modules.
Understanding DTOs and Class validator in NestJS
DTOs ( Data transfer Objects ) are used to structure the data and validate when we sent across the network. We have 'class-validator' package to perform heavy-lifting for us.
Installing class-validator
npm i class-validator class-transformer
Creating and validating DTOs
Inside our 'auth' folder create a DTO folder, create files named loginUser.dto.ts, registerUser.dto.ts which would have all the validation rules we need for authentication.
import { IsEmail, IsString } from "class-validator";
export class LoginDto {
@IsEmail()
email! : string;
@IsString()
password! : string;
}
import { Role } from "@prisma/client";
import { IsEmail, IsEnum, IsOptional, IsString} from "class-validator";
export class RegisterDto {
@IsString()
fname! : string;
@IsString()
lname! : string;
@IsEmail()
email! : string;
@IsString()
password! : string;
@IsEnum(Role)
@IsOptional()
role?: Role;
}
Above, @IsEmail(),@IsString(),@IsEnum() are decorators provided by 'class-validator'.This tells us that we should have valid email id and valid password in form of string also you can add your own regex pattern for password. Note : We've enum roles in our prisma schema below is our schema
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "postgresql"
}
model User {
id String @id @default(uuid())
fname String
lname String
email String @unique
password String
createdAt DateTime @default(now())
role Role @default(USER)
course Course[]
refreshTokens RefreshToken[]
}
model RefreshToken {
id String @id @default(uuid())
userId String
tokenHash String
isRevoked Boolean @default(false)
user User @relation(fields: [userId], references: [id])
}
model Course {
id String @id @default(uuid())
name String
description String
userId String
price Int
user User @relation(fields: [userId], references: [id])
}
enum Role {
ADMIN
USER
}
Using DTOs in controllers
Now let us use the DTOs in controller to validate our request we are sending, @body decorator will help us use the DTO more efficiently.
import { Body, Controller, Post } from '@nestjs/common';
import { AuthService } from './auth.service';
import { RegisterDto } from './dto';
@Controller('auth')
export class AuthController {
constructor(private authService: AuthService) {}
@Post('signup')
signup(@Body() dto: RegisterDto ) {
return this.authService.signup(dto);
}
}
Global validation pipe
The whitelist: true option will trim the properties that don't have any decorators in the DTO.
In DTO we have only this mentioned,
{
"email",
"password",
"role"
}
so if we try to send extra info it would trim it and send the essentials
{
"email",
"password",
"role",
"salary" // this won't be sent if we have 'whitelist:true'
}
Top comments (0)