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: ()=>({})
})
相关文章
|
5天前
|
前端开发 机器人 API
前端大模型入门(一):用 js+langchain 构建基于 LLM 的应用
本文介绍了大语言模型(LLM)的HTTP API流式调用机制及其在前端的实现方法。通过流式调用,服务器可以逐步发送生成的文本内容,前端则实时处理并展示这些数据块,从而提升用户体验和实时性。文章详细讲解了如何使用`fetch`发起流式请求、处理响应流数据、逐步更新界面、处理中断和错误,以及优化用户交互。流式调用特别适用于聊天机器人、搜索建议等应用场景,能够显著减少用户的等待时间,增强交互性。
|
28天前
|
JavaScript 前端开发 小程序
一小时入门Vue.js前端开发
本文是作者关于Vue.js前端开发的快速入门教程,包括结果展示、参考链接、注意事项以及常见问题的解决方法。文章提供了Vue.js的基础使用介绍,如何安装和使用cnpm,以及如何解决命令行中遇到的一些常见问题。
一小时入门Vue.js前端开发
|
8天前
|
存储 JavaScript 前端开发
前端开发:Vue.js入门与实战
【10月更文挑战第9天】前端开发:Vue.js入门与实战
|
9天前
|
自然语言处理 JavaScript 前端开发
JavaScript高级——ES6基础入门
JavaScript高级——ES6基础入门
12 1
|
13天前
|
Web App开发 JSON JavaScript
深入浅出:Node.js后端开发入门与实践
【10月更文挑战第4天】在这个数字信息爆炸的时代,了解如何构建一个高效、稳定的后端系统对于开发者来说至关重要。本文将引导你步入Node.js的世界,通过浅显易懂的语言和逐步深入的内容组织,让你不仅理解Node.js的基本概念,还能掌握如何使用它来构建一个简单的后端服务。从安装Node.js到实现一个“Hello World”程序,再到处理HTTP请求,文章将带你一步步走进Node.js的大门。无论你是初学者还是有一定经验的开发者,这篇文章都将为你打开一扇通往后端开发新世界的大门。
|
16天前
|
机器学习/深度学习 自然语言处理 前端开发
前端大模型入门:Transformer.js 和 Xenova-引领浏览器端的机器学习变革
除了调用API接口使用Transformer技术,你是否想过在浏览器中运行大模型?Xenova团队推出的Transformer.js,基于JavaScript,让开发者能在浏览器中本地加载和执行预训练模型,无需依赖服务器。该库利用WebAssembly和WebGPU技术,大幅提升性能,尤其适合隐私保护、离线应用和低延迟交互场景。无论是NLP任务还是实时文本生成,Transformer.js都提供了强大支持,成为构建浏览器AI应用的核心工具。
|
16天前
|
前端开发 JavaScript 安全
JavaScript代码混淆入门
JavaScript代码混淆是Web应用安全防护的重要一环,通过一系列技术手段提高代码的防护能力。正确应用混淆策略不仅能有效阻止或延缓恶意攻击,还能在一定程度上保护开发者的核心技术和商业秘密。然而,需要注意的是,混淆并非绝对安全,应将其视为整体安全策略的一部分,结合其他防御措施共同构建坚固的安全防线。
39 0
|
18天前
|
数据采集 JavaScript 前端开发
【逆向基础】JS逆向入门:小白也可以看懂
【逆向基础】JS逆向入门:小白也可以看懂
|
2月前
|
JavaScript API 图形学
一个案例带你从零入门Three.js,深度好文!
【8月更文挑战第1天】本教程无需任何Threejs知识!本教程以入门为主,带你快速了解Three.js开发
77 2
一个案例带你从零入门Three.js,深度好文!
|
2月前
|
JavaScript 前端开发 NoSQL
使用Node.js进行后端开发入门
【8月更文挑战第10天】恭喜你完成了Node.js后端开发的入门之旅!这只是个开始,Node.js的世界远比这广阔。随着你对Node.js的深入学习和实践,你将能够构建更复杂、更强大的后端应用。不断探索、学习和实践,你将在Node.js的道路上越走越远。