zoukankan      html  css  js  c++  java
  • NestJS TypeORM Pagination with MySQL

    NestJS TypeORM Pagination - Demo

    When working on an API, sometimes, we have to paginate our data in order to provide a good user experience. In this tutorial, we will be doing a NestJS TypeORM Pagination example. The example will be a products API.

    Setting up a NestJS Project

    > npm install -g @nestjs/cli
    
    > nest new products-api
    

    Adding TypeORM

    If we're doing pagination, we most probably have a lot of data. And when having a lot of data, we also most probably are storing that data inside a database. To interact with our database we will use TypeORM.

    Let's install and setup the module:

    > yarn add @nestjs/typeorm typeorm mysql
    

    I am using MySQL for this example, you can use whichever database you want, you just have to install the correct driver.

    Next, let's import the module in our root module, app.module.ts:

    import { Module } from '@nestjs/common'
    import { AppController } from './app.controller'
    import { AppService } from './app.service'
    import { TypeOrmModule } from '@nestjs/typeorm'
    
    @Module({
      imports: [
        TypeOrmModule.forRoot({
          type: 'mysql',
          host: 'localhost',
          port: 3306,
          username: 'root',
          password: 'password',
          database: 'products',
          entities: [],
          synchronize: true,
        }),
      ],
      controllers: [AppController],
      providers: [AppService],
    })
    export class AppModule {}
    

    Creating the Products Module

    For this module, we will create a small products module. In order to make thing simple, a product will have on an id, name, price, createdAt field.

    import { Column, Entity, PrimaryGeneratedColumn } from "typeorm";
    
    @Entity()
    export class Product {
        @PrimaryGeneratedColumn()
        id: number;
    
        @Column()
        name: string;
    
        @Column()
        price: number;
    }
    

    Then, we will add it to the entities array in our TypeORM configuration to sync our database and create the corresponding table:

    // ...imports
    import { Product } from './products/products.entity'
    
    @Module({
      imports: [
        // ...code
        TypeOrmModule.forRoot({
          // ...code,
          entities: [Product],
        }),
      ],
      // ...code
    })
    export class AppModule {}
    

    In order to consume this entity, we will create a ProductsService, with a create and findAll methods:

    import { Injectable } from "@nestjs/common";
    import { Product } from "./product.entity";
    import { InjectRepository } from "@nestjs/typeorm";
    import { Repository } from "typeorm";
    import { CreateProductDto } from "./dto/CreateProduct.dto";
    
    @Injectable()
    export class ProductsService {
      constructor(
        @InjectRepository(Product)
        private readonly productsRespository: Repository<Product>
      ) {}
    
      findAll(): Promise<Product[]> {
        return this.productsRespository.find()
      }
    
      create(productDto: CreateProductDto): Promise<Product> {
        return this.productsRespository.save(productDto)
      }
    }
    

    We have injected the ProductRepository and we're finding or saving new products, pretty simple so far:

    Furthermore, we're using a DTO as the request body type, CreateProductDto. Let's add it to the project:

    export class CreateProductDto {
      name: string
      price: number
    }
    

    Next, we need to create a controller that will use this service to handle the requests:

    import { Product } from "./product.entity";
    import { ProductsService } from "./products.service";
    import { Post, Get, Controller, Body } from "@nestjs/common";
    import { CreateProductDto } from "./dto/CreateProduct.dto";
    
    @Controller('products')
    export class ProductsController {
      constructor(
        private readonly productsService: ProductsService
      ) {}
    
      @Get()
      findAll(): Promise<Product[]> {
        return this.productsService.findAll()
      }
    
      @Post()
      create(@Body() productDto: CreateProductDto): Promise<Product> {
        return this.productsService.create(productDto)
      }
    }
    

    Now we will bring everything together inside a products module:

    import { Module } from "@nestjs/common";
    import { ProductsService } from "./products.service";
    import { ProductsController } from "./products.controller";
    import { TypeOrmModule } from "@nestjs/typeorm";
    import { Product } from "./product.entity";
    
    @Module({
      imports: [TypeOrmModule.forFeature([Product])],
      controllers: [ProductsController],
      providers: [ProductsService],
    })
    export class ProductsModule {}
    

    Finally, we'll import the module in our root module:

    // ...imports
    import { ProductsModule } from './products/products.module'
    
    @Module({
      imports: [
        // ...code
        ProductsModule,
      ],
      // ...code
    })
    export class AppModule {}
    

    If we start the server using npm run start:dev, we should ba able to create a new product and fetch all the products:

    NestJS TypeORM Pagination

    There are multiple ways to implement pagination, in this tutorial, we'll implement pagination using SQL offset and limit.

    The limit is the amount of items we'll get back.

    The offset is the amount of data we will skip, so for the first page we will skip 0, and for the second page, we will skip the limit, etc...

    // dto/pagination.dto.ts
    export class PaginationDto {
      page: number
      limit: number
    }
    
    // dto/paginated-products-result.dto.ts
    import { Product } from "../product.entity";
    
    export class PaginatedProductsResultDto {
      data: Product[]
      page: number
      limit: number
      totalCount: number
    }
    

    First of all, we add the PaginationDto as the type of our request params object. And pass the data of the findAll service methods. Our method return type should change to Promise<PaginatedProductsResultDto>

    // ... imports
    import { /* ...named imports */ Query } from "@nestjs/common";
    import { PaginationDto } from "./dto/Pagination.dto";
    
    @Controller('products')
    export class ProductsController {
      constructor(
        private readonly productsService: ProductsService
      ) {}
    
      @Get()
      findAll(@Query() paginationDto: PaginationDto): Promise<PaginatedProductsResultDto> {
        paginationDto.page = Number(paginationDto.page)
        paginationDto.limit = Number(paginationDto.limit)
    
        return this.productsService.findAll({
          ...paginationDto,
          limit: paginationDto.limit > 10 ? 10 : paginationDto.limit
        })
      }
      // ... code
    }
    

    Even though our DTO specifies that the page and limit properties are numbers, the query params will be strings. Therefore we need to explicitly convert these values. In addition, we’re also handling negative numbers using Math.abs.

    When passing the pagination data to the service method, we’re overriding the limit to force it to be bellow 10. This number depends on your use case.

    Next, we will implement the pagination in the service layer using the TypeORM query builder:

    // ...imports
    import { PaginationDto } from "./dto/Pagination.dto";
    import { PaginatedProductsResultDto } from "./dto/PaginatedProductsResult.dto";
    
    @Injectable()
    export class ProductsService {
      // ...code
    
      async findAll(paginationDto: PaginationDto): Promise<PaginatedProductsResultDto> {
        const skippedItems = (paginationDto.page - 1) * paginationDto.limit;
    
        const totalCount = await this.productsRespository.count()
        const products = await this.productsRespository.createQueryBuilder()
          .orderBy('createdAt', "DESC")
          .offset(skippedItems)
          .limit(paginationDto.limit)
          .getMany()
    
        return {
          totalCount,
          page: paginationDto.page,
          limit: paginationDto.limit,
          data: products,
        }
      }
      // ...code
    }
    

    Conclusion

    We did basic pagination, there are a few improvements that can be done. We need to validate the products data when creating new items, we can do that in the CreateProductDto using the classvalidator. Likewise, we can also validate the pagination data in the PaginationDto and remove the custom validation from the controller method.

  • 相关阅读:
    java Vamei快速教程02 方法和数据成员
    java Vamei快速教程01
    二叉树
    高效通信模型之
    高效通信模型之
    线程间通信与同步
    线程
    进程
    C++面试知识点总结
    windows下UDP服务器和客户端的实现
  • 原文地址:https://www.cnblogs.com/matt1985/p/13855500.html
Copyright © 2011-2022 走看看