Nest.js简单入门p3

简介: Nest.js简单入门p3

Nest.js简单入门p3

add pagination 添加分页

创建一个新的dto

nest g class common/dto/pagination-query.dto --no-spec

pagination-query.dto:

import { IsOptional, IsPositive } from 'class-validator';

export class PaginationQueryDto {
  @IsOptional()
  @IsPositive()
  limit: number;

  @IsOptional()
  @IsPositive()
  offset: number;
}

我们在main.ts中的管道中加上transformOptions: {enableImplicitConversion: true,}这能让我们传入的值始终为number

修改coffees.controller.ts:

@Get('')
findAll(@Query() paginationQuery: PaginationQueryDto): Promise<Coffee[]> {
  return this.coffeesService.findAll(paginationQuery);
}

修改coffes.service.ts:

//输出所有的coffee
findAll(paginationQuery: PaginationQueryDto) {
  const { limit, offset } = paginationQuery;
  return this.coffeeRepository.find({
    relations: ['flavors'],
    skip: offset,
    take: limit,
  });
}

演示:

// localhost:3000/coffees?limit=1  这时只会打印一个
[
    {
        "id": 3,
        "name": "#coffee #3",
        "brand": "Nest",
        "flavors": [
            {
                "id": 1,
                "name": "demo"
            },
            {
                "id": 2,
                "name": "nice"
            },
            {
                "id": 3,
                "name": "sweet"
            },
            {
                "id": 4,
                "name": "node"
            }
        ]
    }
]

//localhost:3000/coffees?offset=1  这时会打(印总的数量-1)个
[
    {
        "id": 4,
        "name": "#coffee #4",
        "brand": "Nest4",
        "flavors": [
            {
                "id": 1,
                "name": "demo"
            },
            {
                "id": 2,
                "name": "nice"
            },
            {
                "id": 3,
                "name": "sweet"
            },
            {
                "id": 4,
                "name": "node"
            }
        ]
    },
    {
        "id": 5,
        "name": "#coffee #5",
        "brand": "Nest5",
        "flavors": [
            {
                "id": 1,
                "name": "demo"
            },
            {
                "id": 2,
                "name": "nice"
            },
            {
                "id": 3,
                "name": "sweet"
            },
            {
                "id": 4,
                "name": "node"
            },
            {
                "id": 5,
                "name": "cando"
            }
        ]
    }
]

nest与typeorm的transaction(事务)

事务的概念与作用

比如有两个表:账号表和信息表,而这两个表之间有关联。因此呢,我们在新增一个表的数据的时候就得给另外一个表也同时新增数据。然而这是两步操作(即第一步我要新增账号表数据,第一步在新增信息表数据)。假若其中有一个操作失败了呢?如账号新增成功,而信息失败了。如果不处理就会导致,这个账号没有信息的。因此事务就发生作用了。在一个事务中,必须要都成功才算是新增成功。否则都算失败。

应用事务

设想一下你有一个新的业务:为用户推荐喜欢口味的咖啡。

这时候,我们就需要使用transaction(事务)来进行。

创建一个文件

nest g class events/entities/event.entity --no-spec

event.entity.ts:

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

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

  @Column()
  type: string;

  @Column()
  name: string;

  @Column('json')
  payload: Record<string, any>;
}

在coffees.moudle.ts中导入

imports: [TypeOrmModule.forFeature([Coffee, Flavor, Event])],

我们也需要在coffee.entity.ts中添加recommendation附加列:

@Column({ default: 0 })
recommendation: number;

在service文件中添加内容:

constructor(
    //...
    private readonly connection: DataSource,
  ) {}
//...
async recommendCoffe(coffee: Coffee) {
  const queryRunner = this.connection.createQueryRunner();

  await queryRunner.connect();
  await queryRunner.startTransaction();
}

为了能够代码的健壮性,我们需要添加try catch

async recommendCoffe(coffee: Coffee) {
  const queryRunner = this.connection.createQueryRunner();

  await queryRunner.connect();
  await queryRunner.startTransaction();
  try {
    coffee.recommendation++;

    const recommendEvent = new Event();
    recommendEvent.name = 'recommend_coffee';
    recommendEvent.type = 'coffee';
    recommendEvent.payload = { coffeeId: coffee.id };

    await queryRunner.manager.save(coffee);
    await queryRunner.manager.save(recommendEvent);

    await queryRunner.commitTransaction();
  } catch (err) {
    await queryRunner.rollbackTransaction();
  } finally {
    await queryRunner.release();
  }
}

使用索引

我们可以通过索引来快速访问 数据库 表中的特定信息

在entity文件中用@Index()装饰器来指定索引,在列上使用或者在类上使用,传入一个数组。

event.entity.ts

import { Column, Entity, Index, PrimaryGeneratedColumn } from 'typeorm';

@Index(['name', 'type'])
@Entity()
export class Event {
  @PrimaryGeneratedColumn()
  id: number;

  @Column()
  type: string;

  @Index()
  @Column()
  name: string;

  @Column('json')
  payload: Record<string, any>;
}

typeorm migration(迁移) - (待完善。。。)

创建一个ormconfig.js文件

module.exports = {
  type: 'postgres',
  host: 'localhost',
  port: 54322,
  username: 'postgres',
  password: '123456',
  database: 'postgres',
  entities: ['dist/**/*.entity.js'],
  migrations: ['dist/migrations/*.js'],
  cli: {
    migrationsDir: 'src/migrations',
  }
}

生成一个迁移文件:

yarn typeorm migration:create ./src/migration/CoffeeRefactor

我们在coffee.entity.ts中将name改为title,需要注意的是:由于开启了synchronize,所以如果不使用数据库迁移的话会将原有的name列的数据删除,而数据库迁移则保护了这些数据(相当于重命名列)。这就是数据库迁移的作用

1660471755457-CoffeeRefactor.ts(生成的迁移文件),修改up和down方法:

import { MigrationInterface, QueryRunner } from 'typeorm';

export class CoffeeRefactor1660471755457 implements MigrationInterface {
  public async up(queryRunner: QueryRunner): Promise<void> {
    await queryRunner.query(
      `ALTER TABLE "coffee" RENAME COLUMN "name" TO "title"`,
    );
  }

  public async down(queryRunner: QueryRunner): Promise<void> {
    await queryRunner.query(
      `ALTER TABLE "coffee" RENAME COLUMN "title" TO "name"`,
    );
  }
}

我们还需要重新进行构建,确定TypeOrm cli能够在/dist找到迁移文件:

yarn run build

随后我们进行迁移:

控制nest模块的封装

创建一个新的文件夹和module,service文件:

nest g mo coffee-rating
nest g s coffee-rating 

coffee-rating.module.ts:

import { Module } from '@nestjs/common';
import { CoffeesModule } from 'src/coffees/coffees.module';
import { CoffeeRatingService } from './coffee-rating.service';

@Module({
  imports: [CoffeesModule],
  providers: [CoffeeRatingService],
})
export class CoffeeRatingModule {}

coffee-rating.service.ts:

import { Injectable } from '@nestjs/common';
import { CoffeesService } from 'src/coffees/coffees.service';

@Injectable()
export class CoffeeRatingService {
  constructor(private readonly coffeesService: CoffeesService) {}
}

但是现在运行会报错:Error: Nest can't resolve dependencies of the CoffeeRatingService (?). Please make sure that the argument CoffeesService at index [0] is available in the CoffeeRatingModule context.

我们需要在coffees.module.ts中将其设置为exports:

import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { Event } from 'src/events/entities/event.entity';
import { CoffeesController } from './coffees.controller';
import { CoffeesService } from './coffees.service';
import { Coffee } from './entities/coffee.entities';
import { Flavor } from './entities/flavor.entity';

@Module({
  imports: [TypeOrmModule.forFeature([Coffee, Flavor, Event])],
  controllers: [CoffeesController],
  providers: [CoffeesService],
  exports: [CoffeesService],
})
export class CoffeesModule {}

这样我们就可以在CoffeeRatingModule中的任何地方使用CoffeesService,简而言之,这就是封装

provides

value based provide

provids:[CoffeesService]其实是一种简写形式,相当于:

provids:[
    {
        provide: CoffeesService
        useClass: CoffeesService
    }
]

所以我们可以在provides中添加一些自定义类:

import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { Event } from 'src/events/entities/event.entity';
import { CoffeesController } from './coffees.controller';
import { CoffeesService } from './coffees.service';
import { Coffee } from './entities/coffee.entities';
import { Flavor } from './entities/flavor.entity';

class MockCoffeeService {}

@Module({
  imports: [TypeOrmModule.forFeature([Coffee, Flavor, Event])],
  controllers: [CoffeesController],
  providers: [{ provide: CoffeesService, useValue: new MockCoffeeService() }],
  exports: [CoffeesService],
})
export class CoffeesModule {}

使用Token与provides

我们使用一组useValue作为Token的提供:

coffees.module.ts:

providers: [
    CoffeesService,
    { provide: 'COFFEE_BRANDS', useValue: ['buddy brew', 'nescafe'] },
  ],

service中使用@Inject()装饰器注入:

  constructor(
    @InjectRepository(Coffee)
    private readonly coffeeRepository: Repository<Coffee>,
    @InjectRepository(Flavor)
    private readonly flavorRepository: Repository<Flavor>,
    private readonly connection: DataSource,
    @Inject('COFFEE_BRANDS') coffeeBrands: string[],
  ) {}

为了避免以后重构文件出错,我们可以新建一个文件,然后使用export的变量,coffees.constants.ts:

export const COFFEE_BRANDS = 'COFFEE_BRANDS';

其他的文件也将字符串改为变量:

//...
{ provide: COFFEE_BRANDS, useValue: ['buddy brew', 'nescafe'] },
//...
@Inject(COFFEE_BRANDS) coffeeBrands: string[],
//...

我们也可以在constructor中写个测试:

console.log(coffeeBrands);

可以看到启动的时候打印出Token:[ 'buddy brew', 'nescafe' ]

使用useClass进行provide

useClass支持动态创建provide

//...
class ConfigService {}
class DevelopmentConfigService {}
class ProductionConfigService {}
//...
{
      provide: ConfigService,
      useClass:
        process.env.NODE_ENV === 'development'
          ? DevelopmentConfigService
          : ProductionConfigService,
    },
//...

useFactory

{ provide: COFFEE_BRANDS, useFactory: () => ['buddy brew', 'nescafe'] },

更好的实现:

//...
@Injectable()
export class CoffeeBrandsFactory {
  create() {
    // do something
    return ['buddy brew', 'nescafe'];
  }
}
//...
CoffeeBrandsFactory,
    {
      provide: COFFEE_BRANDS,
      useFactory: (brandsFactory: CoffeeBrandsFactory) =>
        brandsFactory.create(),
      inject: [CoffeeBrandsFactory],
    },
//...

异步:

    {
      provide: COFFEE_BRANDS,
      useFactory: async (connection: DataSource): Promise<string[]> => {
        // const coffeeBrands = await connection.query('SELECT * ...');
        const coffeeBrands = await Promise.resolve(['buddy brew', 'nescafe']);
        return coffeeBrands;
      },
      inject: [DataSource],
    },

动态模块

至此,我们使用的模块都是静态的,为了在使用模块的时候有更多的灵活性,我们使用动态的模块

env文件

我们可能会在不同环境中使用nest,因此,我们需要使用.env文件。在nodejs中,env文件一般为应用程序配置的键值对。

添加config包

yarn add @nestjs/config

并且在app.moudle.ts中的imports中使用ConfigModule

imports: [//...
    ConfigModule.forRoot(),//...
]

这样它将能够从默认位置加载和解析我们的.env文件

.env:

DATABASE_USER=postgres
DATABASE_PASSWORD=123456
DATABASE_NAME=postgres
DATABASE_PORT=54322
DATABASE_HOST=localhost

记得在gitignore中添上*.env

app.module.ts:

//...
TypeOrmModule.forRoot({
  type: 'postgres',
  host: process.env.DATABASE_HOST,
  port: +process.env.DATABASE_PORT,//记得转一下,因为port需要传入number类型,而DATABASE_PORT为字符串
  username: process.env.DATABASE_NAME,
  password: process.env.DATABASE_PASSWORD,
  database: process.env.DATABASE_NAME,
  autoLoadEntities: true,
  synchronize: true,
}),
//...

我们可以指定路径或者忽略env文件

//指定路径
ConfigModule.forRoot({
    envFilePath: '.environment'
})
// 忽略
ConfigModule.forRoot({
    ignoreEnvFile: true
})

joi验证配置

yarn add @hapi/joi
npm i --save-dev @types/hapi__joi

app.module.ts

ConfigModule.forRoot({
      validationSchema: Joi.object({
        DATABASE_HOST: Joi.required(),
        DATABASE_PORT: Joi.number().default(54322),
      }),
    }),

这样env文件必须要有host和port才能运行项目

记得导入Joi包要这样导入

import * as Joi from '@hapi/joi';

异步添加配置

TypeOrmModule.forRootAsync({
    useFactory: ()=>({})
})
相关文章
|
1月前
|
JavaScript API 图形学
一个案例带你从零入门Three.js,深度好文!
【8月更文挑战第1天】本教程无需任何Threejs知识!本教程以入门为主,带你快速了解Three.js开发
63 2
一个案例带你从零入门Three.js,深度好文!
|
4月前
|
存储 JavaScript 前端开发
【JavaScript技术专栏】JavaScript基础入门:变量、数据类型与运算符
【4月更文挑战第30天】本文介绍了JavaScript的基础知识,包括变量(var、let、const)、数据类型(Number、String、Boolean、Undefined、Null及Object、Array)和运算符(算术、赋值、比较、逻辑)。通过实例展示了如何声明变量、操作数据类型以及使用运算符执行数学和逻辑运算。了解这些基础知识对初学者至关重要,是进阶学习JavaScript的关键。
37 0
|
1月前
|
JavaScript 前端开发 NoSQL
使用Node.js进行后端开发入门
【8月更文挑战第10天】恭喜你完成了Node.js后端开发的入门之旅!这只是个开始,Node.js的世界远比这广阔。随着你对Node.js的深入学习和实践,你将能够构建更复杂、更强大的后端应用。不断探索、学习和实践,你将在Node.js的道路上越走越远。
|
1月前
|
Web App开发 JavaScript 前端开发
Node.js 入门
【8月更文挑战第4天】Node.js 入门
52 1
|
2月前
|
SQL 前端开发 JavaScript
前端三剑客之JavaScript基础入门
前端三剑客之JavaScript基础入门
|
3月前
|
XML JSON 前端开发
JavaScript入门宝典:核心知识全攻略(下)
JavaScript入门宝典:核心知识全攻略(下)
|
3月前
|
JavaScript 前端开发 UED
JavaScript入门宝典:核心知识全攻略(上)
JavaScript入门宝典:核心知识全攻略(上)
|
3月前
|
JavaScript
three.js入门第一个案例
three.js入门第一个案例
|
4月前
|
JavaScript
学习Node.js入门范例
然后,cmd中运行命令node E:/Test/server.js
35 2
|
3月前
|
缓存 前端开发 JavaScript
【JavaScript】JavaScript 中的闭包:从入门到精通
【JavaScript】JavaScript 中的闭包:从入门到精通
82 0