DEV Community

Cover image for NestJS for beginners
FLUX
FLUX

Posted on

NestJS for beginners

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

  1. Understanding basic setup

    1.1 Setting up project

    1.2 Module creation

    1.3 Module

  2. Controller and services 2.1 [Controller] 2.2 [Services]

1. Basics

Setting up project

$ npm i -g @nestjs/cli
$ nest new project-name
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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 {}
Enter fullscreen mode Exit fullscreen mode

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.
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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."
}
Enter fullscreen mode Exit fullscreen mode

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) {} 
}
Enter fullscreen mode Exit fullscreen mode

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`)
    }
}
Enter fullscreen mode Exit fullscreen mode

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();
        }
}

Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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;
}
Enter fullscreen mode Exit fullscreen mode
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;
}
Enter fullscreen mode Exit fullscreen mode

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
}
Enter fullscreen mode Exit fullscreen mode

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);
  }
}
Enter fullscreen mode Exit fullscreen mode

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'
}
Enter fullscreen mode Exit fullscreen mode

Things to be updated and will continue soon..

1. Basic Hashing

2. Config Setup

3. Passport & Jwt

4. Guards

5. Testing

Top comments (0)