Task App Structure

  1. TaskModule
    • TasksController
    • TasksService
    • Status ValidationPipe
    • TasksEntity
    • TasksRepository
  2. AuthModule
    • AuthController
    • AuthService
    • UserEntity
    • UserRepository
    • JwtStrategy
    • ...
  3. EndPoints
    • TasksController: /tasks
      • GET /tasks getAllTasks()
      • GET /tasks/:id getTaskById()
      • POST /tasks createTask()
      • DELETE /tasks/:id deleteTask()
      • PATCH /tasks/:id/ updateTaskStatus()
    • AuthController: /auth
      • POST /auth/register register()
      • POST /auth/login login()
      • POST /auth/logout logout()
    • UserController: /users
      • GET /users/:id getUserById()
      • POST /users createUser()
      • PATCH /users/:id updateUser()
      • DELETE /users/:id deleteUser()
  4. Nestjs:
    • Modules
    • Controllers
    • Services and Providers
    • Controller-to-Service
    • Validation using Nestjs Pipes
  5. Architechture
    • REST API
    • CRUD operation
    • Error handling
    • DTO (data transfer object)
    • System modularity
    • Backend best practices
    • Configure management
    • Logging
    • Security
  6. demo flow: main call NestFactory(appmodule) => appmodule(controller, service) **decorator: @Module({ imports: [], controllers: [AppController], providers: [AppService], }) export class AppModule {}

decorator Module({truyen prop nay vao class phia duoi})

  1. NestJs Module
  • Singletons
  • a folder per module
  • define: @Module({})
  • props:
    • providers:[]
    • controllers:[]
    • exports:[]
    • imports:[]
    • example
    • ForumModule
      • PostModule
      • CommentModule
        • UserProfileModule
      • AuthModule =>
    @Module({
        providers:[ForumService],
        controllers:[ForumController],
        imports: [
            PostModule,
            CommentModule,
            AuthModule
        ],
        exports:[ForumService]
    })
    export class ForumModule{}
  1. NestJs Controller
  • handle request, return response
  • bound to path
  • dependency injection
  • define:
@Controller('/tasks')
export class TaskController{
    //handle request here
    @Get()
    getAllTasks(){
        return 'get all tasks';
    }
    @Post()
    createTask(){
        return 'create task';
    }
}

Flow 2: HTTP request => controller(endpoint, request data) handle something => communicate service (interact with database) => reponse value (wrap in HTTP response) => return to client

  1. NestJs Provider
  • denpendency injection (@Injectable())
  • Service:
    • defined as provider (not all providers are services)
    • singletons: share with source of truth
    • be called from controller to interact with database
    • defined:
    @Module({
        cotrollers:[TaskController],
        providers:[TasksService, LoggerService]
    })
  1. install uuid
  2. controller: @Body(key?: string) <=> req.body / req.body[key] => input key nếu ko muốn get cả object
  3. DTO (data transfer object) data flow: http request send object {title, description} => controller recive object {title, description} => call service function => service: add data to db, return result to controller => controller send response to client **object {title, description}: DTO (not a model) **using class **example:
class CreateShippingDto{
    orderId: string;
    shippingAddress: string;
    requiredSignature: boolean;
}

=> túm lại: dùng DTO này để việc truyền data giữa các layer được thống nhất hơn.

  1. Pipes (same middlewares?)
  • process arguments before calling controller
  • perform data transformation or validation
  • return original or modified data
  • throw exception if data is invalid
  • can be asynchronous
  • ValidationPipe: validate dto data, ParseIntPipe: convert string to number
  • CustomPipe:
  • use pipes:
    • handle-level: @UsePipes(ValidationPipe)
    • parameter-level: @Param('id') id: string, @UsePipes(ValidationPipe)
    • global pipes: app.useGlobalPipes(somePipe)
  • chose level?
    • parameter: slimmer, cleaner. However: extra code, hard maintain
    • handler: more code, greate benefits:
      • do not require extra code at param level
      • easy maintain
      • indentify params
      • promote usage DTOs

*****series này dạy postgresql - lười cài nên dùng mysql ^^ 15. TypeORM 16. Active Record and Data Mapper

  • Active Record:
    • extends baseEntity
    • have method: find, findOne,...
    • for simple
  • Data Mapper:
    • declare class entity
    • set up Repository: extends Repository
    • for maintain ** app.module : use TypeOrmModule.forRoot ** task.module : use TypeOrmModule.forFeature([UserEntity, TaskEntity])
  1. authentication: verify who are you
  2. authorization: give somebody permission depend on their indentity
  3. JWT = header (meta data: type, hashing algorithm) + payload (data: user, ...) + signature (encoded header, encoded payload, secret key)
  4. Sử dụng JWT trong nestjs
  • install: @nestjs/jwt, passport, passport-jwt
  • import auth.module
imports:[
  PassportModule.register({defaultStrategy: 'jwt'}),
  JwtModule.register({
    secret: 'đặt 1 private key ở đây',
    signOptions:{
      expiresIn: 3600, //thời gian hết hạn của token
    }
  })],
  • get token: const accessToken: string = this.jwtService.sign(payload);
  • verify token:
    • install: @types/passport-jwt
@Injectable()
export class JwtStrategy extends PassportStrategy(Strategy){
    constructor(@InjectRepository(UsersRepository)
    private userRepository: UsersRepository){
        super({
            secretOrKey: 'private', //add lại secret key ở đây
            jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),//extract token từ (bearer) header
        })
    }

    async validate(payload:JwtPayload): Promise<string>{ //method overload từ PassportStrategy,
    // sẽ verify token, decode => payload
        const{username} = payload;
        const user: User = await this.userRepository.findOne({username});
        if(!user){
            throw new UnauthorizedException();
        }

        return "user"; //cái này sẽ trả về request => request này trả về object tên là user ????
        //2 dòng này để loại pasword ra khỏi request trả về
        const { password, ...result } = user;
        return result;
    }
}
+  @UseGuards(AuthGuard()) => đặt middleware này để verify token trước rồi nào cần authorization
  • decorator để lấy data từ request sau khi verify token
export const GetUser = createParamDecorator((_data, ctx: ExecutionContext):User=>{
    const req = ctx.switchToHttp().getRequest(); //get data từ request
    return req.user; //chỉ lấy user từ token
})
  1. Relationship
@ManyToOne(_type=>User, user=>user.tasks, {eager: false})
    user: User;

{eager: true} => eager loading: load data from other table

  1. @Exclude
@ManyToOne(_type=>User, user=>user.tasks, {eager: false})
    @Exclude({toPlainOnly: true})
    user: User;

đoạn này loại bỏ cột user trong task khi trả về plain object: json cần config thêm: nestInterceptor - instanceToPlain (file: transform.inerceptor)

  1. Socket.io - app chat ERD DB CHAT SIMPLE
  2. Custom AuthGuard
import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common';
import { Observable } from 'rxjs';

@Injectable()
export class AuthGuard implements CanActivate {
  canActivate(
    context: ExecutionContext,
  ): boolean | Promise<boolean> | Observable<boolean> {
    const request = context.switchToHttp().getRequest();//get request từ http
    console.log(request.handshake);
    return true;
  }
}
//use: @UseGuards(AuthGuard)
  1. Dùng configService trong Module
import { ConfigModule, ConfigService } from "@nestjs/config"

export class JwtConfig{ //tạo class base
    static getJwtConfig(configService: ConfigService){ //phương thức static, init ConfigService
      return { //return giá trị cần sử dụng
        secret: configService.get('SECRET_ACCESSTOKEN'),
          signOptions:{
            expiresIn: 36000,
          }
      }
  }
}
  
  export const jwtConfigAsync = { //dùng useFactory để inject ConfigService
    imports:[ConfigModule],
    useFactory: async (configService: ConfigService): Promise<JwtConfig>=>JwtConfig.getJwtConfig(configService),
    inject: [ConfigService]
  }

  //sử dụng
  JwtModule.registerAsync(jwtConfigAsync),
  1. Lỗi EntityRepository deprecated: tạo các file sau để dùng CustomDecorator
  • typeorm-ex.decorator.ts
  • typeorm-ex.module.ts
  1. Upload file
  • install: multer
  • middleware:
 @UseInterceptors(FileInterceptor('avatar', {
        storage: diskStorage({
            destination: join(resolve(),'/public/avatars'),
            filename:  (_req, _res, cb)=> cb(null, v4() + '.png')
        })
    }))