Giter Club home page Giter Club logo

nestjs-complete-guide's Introduction

NestJS: The Complete Developer's Guide

Nest Logo

A progressive Node.js framework for building efficient and scalable server-side applications.

NPM Version Package License NPM Downloads CircleCI Coverage Discord Backers on Open Collective Sponsors on Open Collective Support us


Table of Contents

No. Section
1 Course Introduction
2 Scratch
3-5 NEST CLI: CRUD generator
6 Modules, Dependency Injection
7 Car Value Project
8-9 Persisting Data with TypeORM in Nest
10 Custom Data Serialization
11 Authentication From Scratch
12 Unit Testing
13 Integration Testing
14 Managing App Configuration
15 Relations with TypeORM
16 A Basic Permissions System
17 Query Builders with TypeORM
18 Production Deployment

Section-1: Course Introduction

  • This repository contains the entire NestJs learning journey that is created By (Stephen Grider).
  • Build full featured backend APIs incredibly quickly with Nest.
  • TypeORM, and Typescript. Includes testing and deployment!.
  • Deploy a feature-complete app to production
  • Write integration and unit tests to ensure your code is working
  • Use an API client to manually test your app
  • Make your code more reusable and testable with dependency injection
  • Use decorators to dramatically simplify your code
  • Build authentication and permissions systems from scratch
  • Tie different types of data together with TypeORM relationships
  • Use Guards to prevent unauthorized users from gaining access to sensitive data
  • Model your app's data using TypeORM entities

FAQ

Where can I find this course??

You can find it on Udemy here: NestJS - Stephen Grider - Udemy


Section-2: Scratch

1- Package Dependencies

^ Install Package Description
1 @nestjs/[email protected] Contains vast majority of functions, classes, etc, that we need from NEST
2 @nestjs/[email protected]
3 @nestjs/[email protected] Lets Nest use ExpressJs for handling http requests
4 [email protected] Helps make decorators work.
5 [email protected] We write nest apps with typescript

2- Typescript Compiler options

{
  "compilerOptions": {
    "module": "CommonJS",
    "target": "es2017",
    "experimentalDecorators": true,
    "emitDecoratorMetadata": true
  }
}

3- Create nest module and controller

Parts of Nest

Start the Nest app

$ npx ts-node-dev src/main.ts

Naming Convention

Naming Convention


Section 3-5: NEST CLI: CRUD generator

1- Create Nest Project

$ nest new <name> [options]
// <name>	The name of the new project
  • Generate a module (nest g mo) to keep code organized and establish clear boundaries (grouping related components)
  • Generate a controller (nest g co) to define CRUD routes (or queries/mutations for GraphQL applications)
  • Generate a service (nest g s) to implement & isolate business logic
  • Generate an entity class/interface to represent the resource data shape
  • Generate Data Transfer Objects (or inputs for GraphQL applications) to define how the data will be sent over the network

Pipes

Validation Pipe

validation pipe

Automatic Validation

automatic validation

Finally to use pipe to validate body data in our project, we have some small steps:

  • Tell nest to use global validation, HOW?

    import { ValidationPipe } from "@nestjs/common";
    app.useGlobalPipes(new ValidationPipe());

    in 'main.ts' file we import ValidationPipe from common. then we use it inside bootstrap function:

  • Create A DTO Class inside dtos folder for required validation: Call it for example: "CreateMessageDto" and use some npm packages class-validator and class-transformer to make use of decorators to validate your fields.

    # dtos/create-message-dto.ts
    import { IsString } from 'class-validator';
    export class CreateMessageDto {
      @IsString()
      content: string;
    }
  • Inside your controller go to the post method and make the body to be a dto object .

    import { CreateMessageDto } from './dtos/create-message-dto';
    @Post()
      createMessage(@Body() body: CreateMessageDto) {
        console.log(body);
      }

Difference between Service and Repository

Service and Repository 1 Service and Repository 2


Dependency Injection in our project between (controller-service-repository)

Never Do this

BAD: Message Service creates its own copy of MessagesRepository

export class MessagesService {
  messagesRepo: MessagesRepository;
  constructor() {
    // DO NOT DO THAT in real apps: use Dependency Injection
    this.messagesRepo = new MessagesRepository();
  }
}

Lets Solve this with Dependency Injection.

DI

Instead, Do this

Better: MessagesService receives its dependency.

export class MessagesService {
  messagesRepo: MessagesRepository;
  constructor(repo: MessagesRepository) {
    this.messagesRepo = repo;
  }
}

BEST SOLUTION: REPOSITORY PATTERN

Read this First: Implementing a Generic Repository Pattern Using NestJS

MessagesService receives its dependency, and it doesn't specifically required 'Message Repository' you can change your datasource in a flexible way.

interface Repository {
  findOne(id: string);
  findAll();
  create(content: string);
}
export class MessagesService {
  messagesRepo: Repository;
  constructor(repo: Repository) {
    this.messagesRepo = repo;
  }
}

DI DI DI

Few more notes about DI

  • Stephen Grider says:

last thing I want to touch on is the fact that as you start to look at this code, you might really start to say, what is the benefit? What have we really gained here? What is the big benefit to all the stuff we've done? Who cares about using dependency injection? Well, to be honest with you, sometimes I kind of agree with you not going to lie. Sometimes in invest making use of dependency injection feels like we are just kind of jumping through extra hoops without really gaining a whole lot.

So if you feel that way, totally fine. But I can't tell you without a doubt, is that testing your application when you're making use of dependency injection and its entire inversion of control technique, testing your app is going to be far easier, a lot easier. So eventually when we start writing tests around our application, we're going to see that testing individual classes inside of our app is going to be very simple and straightforward compared to if we were not making use of inversion of control and dependency injection. So just keep in mind that is really the payout. The payout of all this stuff is once we start writing tests. So that's kind of got a corollary to it, I'm assuming that you are interested in testing and you want to do testing.



Section-6: Modules, Dependency Injection

Project Structure

Structure

1- Create Nest Project

$ nest new di

2-Generate four modules by cli: [computer, cpu, power, disk]

$ nest genrate module $(name)

3-Generate three services by cli: [cpu, power, disk]

$ nest genrate service $(name)

4-Generate The computer controller

$ nest genrate controller computer

Share code between different modules.

Di

DI inside one single module

Di

DI between different modules

Di

Wrap Up

Di Revision



Section-7: Car Value Project

Project Structure

Structure Structure Structure


Section-8-9: Persisting Data with TypeORM in Nest

TypeORM

How to create an entity ?

create an entity

//1) create user.entity.ts

import { Entity, Column, PrimaryGeneratedColumn } from "typeorm";

@Entity()
export class User {
  @PrimaryGeneratedColumn()
  id: number;

  @Column()
  email: string;

  @Column()
  password: string;
}
//2) update users.module.ts
+ import { TypeOrmModule } from '@nestjs/typeorm';
+ import { User } from './user.entity';
@Module({
  + imports: [TypeOrmModule.forFeature([User])],
})
export class UsersModule {}
//3) update parent: app.module.ts
+ import { User } from './users/user.entity';
@Module({
  imports: [
    TypeOrmModule.forRoot({
      type: 'sqlite',
      database: 'db.sqlite',
    + entities: [User],
      synchronize: true,
    }),
    UsersModule,
    ReportsModule,
  ],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule {}
``

TypeOrm: Repository Api

More about: Repository Api

TypeOrm

How to call the repository api and dependency injection in our service ?

Use Repository

//1) Update users.service.ts

+ import { Repository } from 'typeorm';
+ import { InjectRepository } from '@nestjs/typeorm';
+ import { User } from './user.entity';

@Injectable()
export class UsersService {
  + constructor(@InjectRepository(User) private repo: Repository<User>) {}
  //this @InjectRepository(User) for purpose of dependency injection with generics.

  create(email: string, password: string) {
    const user = this.repo.create({ email, password });
    return this.repo.save(user);
  }
}

//2) Update users.controller.ts

+ import { UsersService } from './users.service';

@Controller('auth')
export class UsersController {
  + constructor(private usersService: UsersService) {}
  @Post('/signup')
  createUser(@Body() body: CreateUserDto) {
    + this.usersService.create(body.email, body.password);
  }
}

No hooks works in save api.

Save and Create


Section 10 - Custom Data Serialization

How to hide data in Nest

1- Nest Recommended Solution

  1. In 'user.entity.ts'
+ import { Exclude } from 'class-transformer';
  // Add Exclude Decorator to the field that it must be hidden.
  + @Exclude()
  @Column()
  password: string;
  1. In 'user.controller.ts'
import { UseInterceptors, ClassSerializerInterceptor } from "@nestjs/common";
// Add this decorator in your route.
+ @UseInterceptors(ClassSerializerInterceptor)
  @Get('/:id')
  async findUser(@Param('id') id: string) {
    const user = await this.usersService.findOne(+id);
    if (!user) {
      throw new NotFoundException('User not found');
    }
    return user;
  }

2- More Complicated (much more flexible) Solution.

What's the difference between Interceptor vs Middleware vs Filter in Nest.js?

Interceptor Interceptor

Our First Interceptor

create new interceptor 'serialize.interceptor.ts'

import {
  UseInterceptors,
  NestInterceptor,
  ExecutionContext,
  CallHandler,
} from "@nestjs/common";
import { Observable } from "rxjs";
import { map } from "rxjs/operators";
import { plainToClass } from "class-transformer";

export class SerializeInterceptor implements NestInterceptor {
  intercept(
    context: ExecutionContext,
    handler: CallHandler<any>
  ): Observable<any> | Promise<Observable<any>> {
    return handler.handle().pipe(
      map((data: any) => {
        return plainToInstance(this.dto, data, {
          excludeExtraneousValues: true,
        });
      })
    );
  }
}

create new decorator to make use of our interceptor

export function Serialize(dto: any) {
  return UseInterceptors(new SerializeInterceptor(dto));
}

Use it in our route or, or in our controller

@Serialize(UserDto)
@Get('/:id')
findUser(){}

How to make decorator accepts a class only ? (type safety)

to avoid this @Serialize('Hello World')

Use interface for class definition

interface ClassConstructor {
  new (...args: any[]): {};
}
function Serialize(dto: ClassConstructor){...}


Section-11: Authentication From Scratch

Create new Service 'Auth Service' to split out the authentication process from user service.

This is the module after adding Auth Service.

Auth

Create a new service class and inject it to the module 'auth.service.ts'

Then it will be able to use UserService as a di.

// auth.service.ts

import { Injectable } from "@nestjs/common";

@Injectable()
export class AuthService {
  constructor(private usersService: UsersService) {}
}
// users.module.ts
@Module({
  + providers: [UsersService, AuthService],
})
export class UsersModule {}

Signup flow : Hashing password with Salt.

Auth

Signup functionality in the auth service class.

export class AuthService {
  async signup(email: string, password: string) {
    // Check if user email in use
    const users = await this.usersService.find(email);
    if (users.length) {
      throw new BadRequestException("This email is already use.");
    }

    // Hash the user password
    // First: Generate the Salt
    const salt = randomBytes(8).toString("hex");

    // Hash the salt and the password together using scrypt function as a buffer.
    const hash = (await scrypt(password, salt, 32)) as Buffer;

    // Join the hashed result and the salt together.
    // you will split it in the sign in function to get the salt and hash.
    // then with the login password you will hash it again with the given salt.
    // then compare them if they equal = Authenticated user.
    const result = `${salt}.${hash.toString("hex")}`;

    // Create a new user and save it
    const user = await this.usersService.create(email, result);

    // return created user.
    return user;
  }

  async signin(email: string, password: string) {
    // Check if user email exist.
    const [user] = await this.usersService.find(email);
    if (!user) {
      throw new BadRequestException("This email not exist.");
    }

    // Check if password is correct.
    const [salt, storedHash] = user.password.split(".");
    const hash = (await scrypt(password, salt, 32)) as Buffer;
    if (storedHash !== hash.toString("hex")) {
      throw new BadRequestException("Wrong Password.");
    }

    return user;
  }
}

Common Auth System Features.

Auth

How to create your own custom Param Decorator

import { createParamDecorator, ExecutionContext } from "@nestjs/common";

export const CurrentUser = createParamDecorator(
  (data: any, context: ExecutionContext) => {
    return "Current User Custom Decorator";
  }
);

Now, to make a Decorator to use to get current user, we need two things

  1. Session object to get the current userId, easy with context object.
  2. UserService class to get access to repo, hard because the dependency injection system.

Decorator

Solution for this problem: Create an Interceptor to hold those things, and make it inside di container and then decorator can use it.

Decorator

How to connect our create-user interceptor to controllers

1- Controller Scoped.

Hint, Very tedious if we will use this interceptor in more than one controller.

Custom Interceptor

Steps:

1- Create Interceptor to add the current user to the request.

import {
  NestInterceptor,
  ExecutionContext,
  CallHandler,
  Injectable,
} from "@nestjs/common";
import { Observable } from "rxjs";
import { UsersService } from "../users.service";

@Injectable()
export class CurrentUserInterceptor implements NestInterceptor {
  constructor(private userService: UsersService) {}
  async intercept(
    context: ExecutionContext,
    handler: CallHandler
  ): Promise<Observable<any>> {
    const request = context.switchToHttp().getRequest();
    const { userId } = request.session;

    if (userId) {
      const user = await this.userService.findOne(+userId);
      request.currentUser = user;
    }

    return handler.handle();
  }
}

2- Inject the interceptor to the providers of 'users.module.ts'

//users.module.ts
@Module({
  ...,
  providers: [..., CurrentUserInterceptor],
});

3- Use the interceptor in our controller.

//users.controller.ts
@UseInterceptors(CurrentUserInterceptor)
export class UsersController {}

2- Globally Scoped.

Only single instance to all controllers.

Custom Interceptor

How to apply it globally?

// in users.module.ts

+ import { APP_INTERCEPTOR } from '@nestjs/core';

@Module({
  ...,
  providers: [
    ...,
    {
      provide: APP_INTERCEPTOR,
      useClass: CurrentUserInterceptor,
    },
  ],
})
export class UsersModule {}

How to make a Guard: to reject requests to certain handlers if the user is not signed in.

Guard

As well as interceptors: we can apply guard in different locations:

  1. Globally for all controllers.
  2. Controller scope.
  3. Single Handler Scope.

Guard

Steps to make our AuthGuard.

1- Make your guard dir, and create 'auth.guard.ts'

import {
  CanActivate,
  ExecutionContext,
  UnauthorizedException,
} from "@nestjs/common";

export class AuthGuard implements CanActivate {
  canActivate(context: ExecutionContext) {
    const request = context.switchToHttp().getRequest();
    const { userId } = request.session;

    if (!userId) {
      throw new UnauthorizedException(
        "Your are not authorized for this resource."
      );
    }

    return userId;
  }
}

2- In our controller:

import { UseGuards } from "@nestjs/common";
import { AuthGuard } from "../guards/auth.guard";
// @UseGuards(AuthGuard) // apply guard for controller
export class UsersController {
  @Get("/whoami")
  @UseGuards(AuthGuard) // apply guard for single handler
  whoAmI(@CurrentUser() user: User) {
    return user;
  }
}


Section-12: Unit Testing

Unit Testing

To make test of our 'Auth Service' functionality (signup, signin) we should have:

  1. A Copy of UsersService to use [find(), create()].
  2. The UsersService depends on 'UsersRepo', So it should have copy of it.
  3. The UsersRepo depends on 'SQLITE' and its configuration.

Unit Testing

To make our test more straightforward we will use a trick

We're going to use the dependency injection system to avoid having to create all these different dependencies.

Create your own fake 'UsersService'

Unit Testing Unit Testing Unit Testing

import { Test } from "@nestjs/testing";
import { AuthService } from "./auth.service";
import { User } from "./user.entity";
import { UsersService } from "./users.service";

it("can create an instance of auth service", async () => {
  //Create a fake copy of users service.
  const fakeUsersService: Partial<UsersService> = {
    find: () => Promise.resolve([]),
    create: (email: string, password: string) =>
      Promise.resolve({ id: 1, email, password } as User),
  };
  const module = await Test.createTestingModule({
    providers: [
      AuthService,
      {
        provide: UsersService,
        useValue: fakeUsersService,
      },
    ],
  }).compile();

  const service = module.get(AuthService);

  expect(service).toBeDefined();
});

Unit Testing Unit Testing



Section-13: Integration Testing

Integration Testing Integration Testing

To solve the problem of : 'Test module cannot use session or pipe because these are created in main.ts file not in the app module... We can do this: '

First the ValidationPipe.

  1. Remove uses in the main.ts file

// Remove this block of code in bootstrap function >> main.ts
app.useGlobalPipes(
  new ValidationPipe({
    whitelist: true,
  })
);
  1. Add new provider in AppModule file.

import { ValidationPipe } from '@nestjs/common';
import { APP_PIPE } from '@nestjs/core';

providers: [
    ...,
    {
      provide: APP_PIPE,
      useValue: new ValidationPipe({ whitelist: true }),
    },
  ],

Second, Globally Scoped Middleware: 'Cookie-Session'.

  1. Remove uses in the main.ts file

// Remove this block of code in bootstrap function >> main.ts
app.use(
  cookieSession({
    keys: ["secret-key"],
  })
);
  1. Create new method in AppModule Class.

export class AppModule {
  configure(consumer: MiddlewareConsumer) {
    consumer.apply(cookieSession({ keys: ["secret-key"] })).forRoutes("*");
  }
}

Create two versions of our DB , one for development and another one for testing.

Testing Database Testing Database

You can solve this by using NODE_ENV variable to identify which environment you work on.

    TypeOrmModule.forRoot({
      type: 'sqlite',
    + database: process.env.NODE_ENV === 'test' ?
      'test.sqlite' : 'db.sqlite',
      ...
    }),

Recommended Solution: Environment Config.

App Config

Go to next Section to read more



Section-14: Managing App Configuration

1- Install @nestjs/config npm package

$ npm install @nestjs/config

App Config App Config

Stephen Grider Says:

Let me tell you what you and I are going to do. We're going to kind of forget the rules set up by the Dotenv docs people, we're not going to very strictly follow their rules. Instead, we're going to take the Nest recommendation. We are going to have more than one different file. We're going to have one specifically to be used during development of our application and one during testing of our application. So these different files are going to provide some different database names for this database property right here. We'll have one file. It says use a database name of DB_SQLITE. And then the other file will say use a database name of something like Test.SQLite or something like that. So, again, we are not strictly following the recommendations of env dOCS because I don't know, I'm just not really a big fan of that library myself, as you can tell, with all this invariant configuration stuff.

Create two files:

  1. .env.development

  2. .env.test

Then make your database configuration based on environment

import { ConfigModule, ConfigService } from '@nestjs/config';

@Module({
  imports: [
    // Global your config variables.
    // choose which file compiler use based on NODE_ENV variable.
    ConfigModule.forRoot({
      isGlobal: true,
      envFilePath: `.env.${process.env.NODE_ENV}`,
    }),

    // Add ConfigService to dependency container of typeorm module.
    TypeOrmModule.forRootAsync({
      inject: [ConfigService],
      useFactory: (config: ConfigService) => {
        return {
          type: 'sqlite',
          database: config.get<string>('DB_NAME'),
          entities: [User, Report],
          synchronize: true,
        };
      },
    }),
    // Comment out this part.
    // TypeOrmModule.forRoot({
    //   type: 'sqlite',
    //   database: process.env.NODE_ENV === 'test' ? 'test.sqlite' : 'db.sqlite',
    //   entities: [User, Report],
    //   synchronize: true,
    // }),
    ...,
  ],
  ...
})

Section-15: Relations with TypeORM

Requires Knowledge for Associations:

Relations Relations

Steps to create an association in nestjs and typeorm:

Relations

Types of Relationships:

One-to-One Relationship:

It’s a relationship where a record in one entity (table) is associated with exactly one record in another entity (table).

  • Country - capital city: Each country has exactly one capital city. Each capital city is the capital of exactly one country.

Relations

One-to-many / Many-to-one Relationship:

This kind of relationship means that one row in a table (usually called the parent table) can have a relationship with many rows in another table (usually called child table).

  • One customer may make several purchases, but each purchase is made by a single customer.

Relations

Many-to-Many Relationship:

By definition, a many-to-many relationship is where more than one record in a table is related to more than one record in another table.

  • A typical example of a many-to many relationship is one between students and classes. A student can register for many classes, and a class can include many students.

Relations


Our App: Relationship between Reports and users:

One to many relationship.

Relations

OneToMany and ManyToOne Decorators.

Relations

// in user.entity.ts
// Doesn't make a change in our Database

class User {
  @OneToMany(() => Report, (report) => report.user)
  reports: Report[];
}
// in report.entity.ts
// Create user_id column in reports table in Database.

class Report {
  @ManyToOne(() => User, (user) => user.reports)
  user: User;
}
// in reports.service.ts

export class ReportsService {
  create(reportDto: CreateReportDto, user: User) {
    const report = this.repo.create(reportDto);
    report.user = user; // assign user to the report
    return this.repo.save(report);
  }
}
// in reports.controller.ts

@Controller("reports")
export class ReportsController {
  @Post()
  @UseGuards(AuthGuard)
  @Serialize(ReportDto)
  // to hide some information of user.: Serialize it --> hide password property
  createReport(@Body() body: CreateReportDto, @CurrentUser() user: User) {
    return this.reportsService.create(body, user);
  }
}

ManyToOne: Create user_id column in reports table in Database.

Relations

Important you should read these articles:


Section-16: A Basic Permissions System

What is the difference between Authentication and Authorization?

Role-1

Now we will build the Authorization in our App.

Very similar to our AuthGuard that we build here: AuthGuard Implementation

Role-1

Creation of AdminGuard.

import { CanActivate, ExecutionContext } from "@nestjs/common";

export class AdminGuard implements CanActivate {
  canActivate(context: ExecutionContext) {
    const request = context.switchToHttp().getRequest();
    if (!request.currentUser) {
      return false;
    }

    return request.currentUser.admin;
  }
}

Steps to make our AdminGuard.

1- Create 'admin.guard.ts' in src/guards directory.

import { CanActivate, ExecutionContext } from "@nestjs/common";

export class AdminGuard implements CanActivate {
  canActivate(context: ExecutionContext) {
    const request = context.switchToHttp().getRequest();

    if (!request.currentUser || !request.currentUser.admin) {
      throw new UnauthorizedException(
        "Your are Unauthorized for this resource."
      );
    }

    return true;
  }
}

2- In our reports controller:

import { UseGuards } from "@nestjs/common";
import { AdminGuard } from "../guards/admin.guard";

export class ReportsController {
  @Patch("/:id")
  @UseGuards(AdminGuard)
  approveReport(@Param("id") id: string, @Body() body: ApproveReportDto) {
    return this.reportsService.changeApproval(+id, body.approved);
  }
}

Why this AdminGuard does not working ?

The issue is : The Guards execute before the interceptors, and we get our currentUser from the interceptor.

The request Life Cycle:

Role-1

note: The interceptor can be executed before or after the Request Handler.

Role-1

Solution: We need to take our CurrentUserInterceptor and turn it into a middleware instead to be executed before the guards.

Role-1

So, now we will create the CurrentUserMiddleware instead of CurrentUserInterceptor

1- Create 'current-user.middleware.ts' file inside users/middlewares directory

import { Injectable, NestMiddleware } from "@nestjs/common";
import { Request, Response, NextFunction } from "express";
import { UsersService } from "../users.service";

@Injectable()
export class CurrentUserMiddleware implements NestMiddleware {
  constructor(private usersService: UsersService) {}

  async use(req: Request, res: Response, next: NextFunction) {
    const { userId } = req.session || {};

    if (userId) {
      const user = await this.usersService.findOne(+userId);

      // @ts-ignore
      // this is cause an error: we will solve it later...
      req.currentUser = user;
    }

    next();
  }
}

2- In users module class:

first remove all stuff related to CurrentUserInterceptor

// remove all this lines::
import { CurrentUserInterceptor } from './interceptors/current-user.interceptor';
import { APP_INTERCEPTOR } from '@nestjs/core';

@Module({
  // remove this dependency
  {
  provide: APP_INTERCEPTOR,
  useClass: CurrentUserInterceptor,
  }
});

Now, Apply our middleware in configure method

import { Module, MiddlewareConsumer } from "@nestjs/common";
import { CurrentUserMiddleware } from "./middlewares/current-user.middleware";

export class UsersModule {
  configure(consumer: MiddlewareConsumer) {
    consumer.apply(CurrentUserMiddleware).forRoutes("*");
  }
}

Now we still have a problem in our Middleware class: 'req.currentUser':

Property 'currentUser' does not exist on type 'Request'.

Solution: by applying this code, now we add a new property to Request Interface of Express namespace.

declare global {
  namespace Express {
    interface Request {
      currentUser?: User;
    }
  }
}

Section-17: Query Builders with TypeORM

// get-estimate.dto.ts
// Create this dto to validate the query string and transform it to the needed datatype.

import {
  IsLatitude,
  IsLongitude,
  IsNumber,
  IsString,
  Max,
  Min,
} from "class-validator";
import { Transform } from "class-transformer";

export class GetEstimateDto {
  @IsString()
  make: string;

  @IsString()
  model: string;

  @Transform(({ value }) => parseInt(value))
  @IsNumber()
  @Min(1930)
  @Max(2050)
  year: number;

  @Transform(({ value }) => parseFloat(value))
  @IsLongitude()
  lng: number;

  @Transform(({ value }) => parseFloat(value))
  @IsLatitude()
  lat: number;

  @Transform(({ value }) => parseInt(value))
  @IsNumber()
  @Min(0)
  @Max(1_000_000)
  mileage: number;
}
// reports.service.ts
// filter reports and get average of the top 3 results.

  createEstimate({ make, model, lng, lat, year, mileage }: GetEstimateDto) {
    return this.repo
      .createQueryBuilder()
      .select('AVG(price)', 'price')
      .where('approved IS TRUE')
      .andWhere('make = :make', { make })
      .andWhere('model = :model', { model })
      .andWhere('lng - :lng BETWEEN -5 AND  5', { lng })
      .andWhere('lat - :lat BETWEEN -5 AND  5', { lat })
      .andWhere('year - :year BETWEEN -3 AND  3', { year })
      .orderBy('ABS(mileage - :mileage)', 'DESC')
      .setParameters({ mileage })
      .limit(3)
      .getRawOne();
  }
// reports.controller.ts

@Get()
  getEstimate(@Query() query: GetEstimateDto) {
    return this.reportsService.createEstimate(query);
  }

Section-18: Production Deployment

Production

To use our config variables in AppModule class:

HUM, You are right, we will use our dependency injection container.

export class AppModule {
  constructor(private configService: ConfigService) {}

  configure(consumer: MiddlewareConsumer) {
    consumer
      .apply(cookieSession({ keys: [this.configService.get("COOKIE_KEY")] }))
      .forRoutes("*");
  }
}

Understanding the Synchronize Flag in TypeORM.

Synchronize makes the entity sync with the database every time you run your application. Hence, whenever you add columns to an entity, create a new table by creating a new entity, or remove columns from an existing table by modifying an entity (made a migrations) it will automatically update the database once the server is started.

Synchronize

Dangers of Synchronize.

Even Though synchronization is a good option to synchronize your entity with the database, it is unsafe for production databases. Therefore migrations can be an alternative solution for safer migrations in production databases.

Stephen Grider Says about synchronize:

What kind of problem could ever possibly arise?

Well, I want you to imagine a scenario where maybe you are making some changes to your code and maybe by mistake you delete some property off your user entity.

Let's imagine that you then saved your entire project and deployed it off to a production environment. When your application starts up on that production server, it's going to connect to your production database type forums, then going to see your updated user entity. And it's going to notice that it no longer has a password property.

So typeorm is going to automatically go into your production database and delete that entire password column. When that delete occurs, you have now lost data inside of your application. That is obviously really, really bad and definitely not something we would ever want to do.

So the downside to using this synchronize flag of true is that you can very easily accidentally lose data inside of your production database the next time you deploy your app. So even though it can be really handy to use during development, because we can very quickly make changes to our database just by changing our different entity files, I highly recommend that you do not make use of synchronize true as soon as you start deploying your application to a production environment. Synchronize

Theory Behind Migrations

It is crucial to think about the structure of our database carefully. Even if we do that, the requirements that our application has to meet change. Because of the above, we rarely can avoid having to modify the structure of our database. When doing that, we need to be careful not to lose any existing data.

With database migrations, we can define a set of controlled changes that aim to modify the structure of the data. They can include adding or removing tables, changing columns, or changing the data types, for example. While we could manually run SQL queries that make the necessary adjustments, this is not the optimal approach. Instead, we want our migrations to be easy to repeat across different application environments.

Also, we need to acknowledge that modifying the structure of the database is a delicate process where things can go wrong and damage the existing data. Fortunately, writing database migrations includes committing them to the repository. Therefore, they can undergo a rigorous review before merging to the master branch. In this article, we go through the idea of migrations and learn how to perform them with TypeORM.

Read More

Synchronize Synchronize

Creating and Running Migrations During Development.

Synchronize Synchronize Synchronize

Prepare our app for migrations:

1- Create in root directory a new folder 'db' and create in it a new file 'data-source.ts'

import { DataSource, DataSourceOptions } from "typeorm";

export const dataSourceOptions: DataSourceOptions = {
  type: "sqlite",
  database: "test.sqlite",
  entities: ["dist/**/*.entity.js"],
  migrations: ["dist/db/migrations/*.js"],
};

const dataSource = new DataSource(dataSourceOptions);
export default dataSource;

2- in our 'app.module.ts' lets use our dataSourceOptions.

import { dataSourceOptions } from '../db/data-source';

// Remove all stuff related to TypeOrmModule and put this new import

@Module({
    imports: [
    ...,
    TypeOrmModule.forRoot(dataSourceOptions),
  ],
});

3- To CREATE Migration: Put these scripts in 'package.json'

  "scripts": {
    "typeorm": "npm run build && npx typeorm -d dist/db/data-source.js",
    "migration:generate": "npm run typeorm -- migration:generate",
    "migration:run": "npm run typeorm -- migration:run",
    "migration:revert": "npm run typeorm -- migration:revert"
  }

4- To Create a new Migration run this command:

$ npm run migration:generate -- db/migrations/newMigration

nestjs-complete-guide's People

Contributors

yousefshabaneg avatar

Stargazers

 avatar

Watchers

 avatar

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.