Fork me on GitHub

nestjs入门学习总结(三):集成typeorm并实现一个curd操作

typeorm熟悉

TypeORM 是一个ORM框架,它可以运行在 NodeJS、Browser、Cordova、PhoneGap、Ionic、React Native、Expo 和 Electron 平台上,可以与 TypeScript 和 JavaScript (ES5,ES6,ES7,ES8)一起使用。

什么是实体?

实体是一个映射到数据库表(或使用 MongoDB 时的集合)的类。 你可以通过定义一个新类来创建一个实体,并用@Entity()来标记:

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

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

    @Column()
    firstName: string;

    @Column()
    lastName: string;

    @Column()
    isActive: boolean;
}
  1. 每个实体必须至少有一个主列。 有几种类型的主要列:
@PrimaryColumn() 创建一个主列,它可以获取任何类型的任何值。

@PrimaryGeneratedColumn() 创建一个主列,该值将使用自动增量值自动生成。

@PrimaryGeneratedColumn("uuid") 创建一个主列,该值将使用uuid自动生成。 Uuid 是一个独特的字符串 id。 你不必在保存之前手动分配其值,该值将自动生成。
  1. enum 列类型

使用带枚举值的数组:

export type UserRoleType = "admin" | "editor" | "ghost",

@Entity()
export class User {

    @PrimaryGeneratedColumn()
    id: number;

    @Column({
        type: "enum",
        enum: ["admin", "editor", "ghost"],
        default: "ghost"
    })
    role: UserRoleType
}
  1. 列选项

列选项定义实体列的其他选项,在@ Column上指定列选项

@Column({
    type: "varchar",
    length: 150,
    unique: true,
    // ...
})
name: string;

几个常用的列选项

type: ColumnType - 列类型。
name: string - 数据库表中的列名。
length: number - 列类型的长度。 例如,如果要创建varchar(150)类型
nullable: boolean - 在数据库中使列NULL或NOT NULL。 默认情况下,列是nullable:false
update: boolean - 指示"save"操作是否更新列值。如果为false,则只能在第一次插入对象时编写该值。 默认值为"true"
select: boolean - 定义在进行查询时是否默认隐藏此列。 设置为false时,列数据不会显示标准查询。 默认情况下,列是select:true
default: string - 添加数据库级列的DEFAULT值。
unique: boolean - 将列标记为唯一列(创建唯一约束)
comment: string - 数据库列备注,并非所有数据库类型都支持。
enum: string[]|AnyEnum - 在enum列类型中使用,以指定允许的枚举值列表。
  1. mysql的列类型
int, tinyint, smallint, mediumint, bigint, float, double, dec, decimal, numeric, date, datetime, timestamp, time, year, char, varchar, nvarchar, text, tinytext, mediumtext, blob, longtext, tinyblob, mediumblob, longblob, enum, json, binary, geometry, point, linestring, polygon, multipoint, multilinestring, multipolygon, geometrycollection

增删改查操作

  • create - 创建User的新实例。 接受具有用户属性的对象文字,该用户属性将写入新创建的用户对象
const user = repository.create(); // 和 const user = new User();一样
const user = repository.create({
  id: 1,
  firstName: "Timber",
  lastName: "Saw"
}); // 和const user = new User(); user.firstName = "Timber"; user.lastName = "Saw";一样
  • merge - 将多个实体合并为一个实体。
const user = new User();
repository.merge(user, { firstName: "Timber" }, { lastName: "Saw" }); // 和 user.firstName = "Timber"; user.lastName = "Saw";一样
  • save - 保存给定实体或实体数组。 如果该实体已存在于数据库中,则会更新该实体。 如果数据库中不存在该实体,则会插入该实体
await repository.save(user);
await repository.save([category1, category2, category3]);
  • remove - 删除给定的实体或实体数组。
await repository.remove(user);
await repository.remove([category1, category2, category3]);
  • insert - 插入新实体或实体数组。
await repository.insert({
  firstName: "Timber",
  lastName: "Timber"
});
  • update - 通过给定的更新选项或实体 ID 部分更新实体。
await repository.update({ firstName: "Timber" }, { firstName: "Rizzrak" });
// 执行 UPDATE user SET firstName = Rizzrak WHERE firstName = Timber

await repository.update(1, { firstName: "Rizzrak" });
// 执行 UPDATE user SET firstName = Rizzrak WHERE id = 1
  • delete -根据实体 id, ids 或给定的条件删除实体:
await repository.delete(1);
await repository.delete([1, 2, 3]);
await repository.delete({ firstName: "Timber" });
  • count - 符合指定条件的实体数量。对分页很有用。
    const count = await repository.count({ firstName: "Timber" });

  • find - 查找指定条件的实体。

const timbers = await repository.find({ firstName: "Timber" });
  • findAndCount - 查找指定条件的实体。还会计算与给定条件匹配的所有实体数量,但是忽略分页设置 (skip 和 take 选项)。
const [timbers, timbersCount] = await repository.findAndCount({ firstName: "Timber" });
  • findByIds - 按 ID 查找多个实体。
const users = await repository.findByIds([1, 2, 3]);

findOne - 查找匹配某些 ID 或查找选项的第一个实体。
const user = await repository.findOne(1);
const timber = await repository.findOne({ firstName: "Timber" });

find查询选项

  • select - 表示必须选择对象的哪些属性
userRepository.find({ select: ["firstName", "lastName"] });
  • where -查询实体的简单条件。
userRepository.find({ where: { firstName: "Timber", lastName: "Saw" } });
  • order - 选择排序
userRepository.find({
    order: {
        name: "ASC",
        id: "DESC"
    }
});
  • skip - 偏移(分页)
userRepository.find({
    skip: 5
});
  • take - limit (分页) - 得到的最大实体数。
userRepository.find({
    take: 10
});
  • find 选项的完整示例:
userRepository.find({
    select: ["firstName", "lastName"],
    relations: ["profile", "photos", "videos"],
    where: {
        firstName: "Timber",
        lastName: "Saw"
    },
    order: {
        name: "ASC",
        id: "DESC"
    },
    skip: 5,
    take: 10,
    cache: true
});

集成typeorm并连接数据库

为了与 SQL和 NoSQL 数据库集成,Nest 提供了 @nestjs/typeorm 包。Nest 使用TypeORM是因为它是 TypeScript 中最成熟的对象关系映射器( ORM )。因为它是用 TypeScript 编写的,所以可以很好地与 Nest 框架集成。

  1. 安装typeorm和mysql2
yarn add @nestjs/typeorm typeorm mysql2
  1. 将TypeOrmModule导入到appModule中
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';

@Module({
  imports: [
    TypeOrmModule.forRoot({
      type: 'mysql',
      host: 'localhost',
      port: 3306,
      username: 'root',
      password: 'kerry123',
      database: 'db_cloud_collect',
      // entities: [],
      // 自动载入实体
      autoLoadEntities: true,
      // 默认:false,如果为true,自动载入的模型将同步,禁止生产环境使用,否则数据将会丢失
      synchronize: true,
    }),
  ],
})
export class AppModule {}

forRoot() 方法支持所有TypeORM包中createConnection()函数暴露出的配置属性。

  1. 异步配置

使用 forRootAsync() 函数,异步传递模块选项,而不是事先传递它们

import { Module } from '@nestjs/common';
import { ConfigService, ConfigModule } from '@nestjs/config';
import { TypeOrmModule } from '@nestjs/typeorm';

@Module({
  imports: [
    ConfigModule.forRoot({
      //全局使用,无需在其他模块中导入它
      isGlobal: true,
      // 自定义 env 文件路径,默认情况下,程序在应用程序的根目录中查找.env文件
      envFilePath: ['.env'],
    }),
    // 异步配置
    TypeOrmModule.forRootAsync({
      inject: [ConfigService],
      useFactory: async (configService: ConfigService) => ({
        type: 'mysql',
        host: configService.get('DB_HOST'),
        port: configService.get('DB_PORT'),
        username: configService.get('DB_USER'),
        password: configService.get('DB_PASSWORD'),
        database: configService.get('DB_DATABASE'),
        // entities: [__dirname + '/**/*.entity{.ts,.js}'],
        autoLoadEntities: true,
        synchronize: true,
      }),
    })
  ]
})
export class AppModule {}

将从默认位置(项目根目录)载入并解析一个.env文件,从.env文件拿到环境变量键值对,并将结果存储到一个可以通过ConfigService访问的私有结构。forRoot()方法注册了ConfigService提供者,后者提供了一个get()方法来读取这些解析/合并的配置变量。

我们在根目录下创建一个.env文件,添加一下键值对

DB_HOST=localhost
DB_PORT=3306
DB_USER=root
DB_PASSWORD=123434
DB_DATABASE=db_test

当然,要使用ConfigModule和ConfigService,我们需要先安装@nestjs/config,这是Nest 提供的一个开箱即用的包,创建一个 ConfigModule ,它暴露一个 ConfigService ,根据 $NODE_ENV 环境变量加载适当的 .env 文件。

yarn add @nestjs/config
  1. 实体注册并使用
在user.module.ts模块中导入TypeOrmModule.forFeature并对实体进行注册,这样,我们就可以使用 @InjectRepository()装饰器将 UsersRepository 注入到 UsersService 中

// user.module.ts

import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { UsersService } from './users.service';
import { UsersController } from './users.controller';
import { User } from './user.entity';

@Module({
  imports: [TypeOrmModule.forFeature([User])],
  providers: [UsersService],
  controllers: [UsersController],
})
export class UsersModule {}


// users.service.ts

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

@Injectable()
export class UsersService {
  constructor(
    @InjectRepository(User)
    private usersRepository: Repository<User>
  ) {}

  findAll(): Promise<User[]> {
    return this.usersRepository.find();
  }

  findOne(id: string): Promise<User> {
    return this.usersRepository.findOne(id);
  }

  async remove(id: string): Promise<void> {
    await this.usersRepository.delete(id);
  }
}

实现一个curd操作

现在我们熟悉了typeorm的基本操作,并在项目中将typeorm框架集成了进来,下面我们来实现一个curd操作

在根目录下创建一个link文件夹,然后目录下创建以下文件:
link.module.ts
link.controller.ts
link.service.ts
link.entity.ts

实体类

// link.entity.ts
import { Entity, Column, PrimaryGeneratedColumn } from 'typeorm';

@Entity('tb_link')
export class LinkEntity {
  @PrimaryGeneratedColumn()
  id: number;

  @Column()
  name: string;

  @Column()
  url: string;
}

服务类

// link.service.ts
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { CreateLinkDto } from './dto/create-link.dto';
import { UpdateLinkDto } from './dto/update-link.dto';
import { LinkEntity } from './link.entity';

@Injectable()
export class LinkService {
  constructor(
    @InjectRepository(LinkEntity)
    private linksRepository: Repository<LinkEntity>,
  ) {}

  async create(createLinkDto: CreateLinkDto) {
    //  this.linksRepository.create 相当于 const link = new LinkEntity();
    const link = await this.linksRepository.create({
      ...createLinkDto,
    });
    // 保存给定实体或实体数组。如果该实体已存在于数据库中,则会更新该实体。如果数据库中不存在该实体,则会插入该实体。
    const created = await this.linksRepository.save(link);
    return created;
  }

  async findAll() {
    return this.linksRepository.find();
  }

  async findOne(id: number) {
    return await this.linksRepository.findOne({
      where: {
        id,
      },
    });
  }

  async update(id: number, updateLinkDto: UpdateLinkDto) {
    const link = await this.linksRepository.findOne({
      where: {
        id,
      },
    });
    // 合并实体
    const newLink = this.linksRepository.merge(link, updateLinkDto);
    return await this.linksRepository.save(newLink);
  }

  async remove(id: number) {
    const link = await this.linksRepository.findOne({
      where: {
        id,
      },
    });
    return await this.linksRepository.remove(link);
  }
}

控制器

我们在LinkController中通过类构造函数注入LinkService,这样我就可以在controller中使用LinkService提供的方法了

// link.controller.ts
import {
  Controller,
  Get,
  Post,
  Body,
  Patch,
  Param,
  Delete,
} from '@nestjs/common';
import { LinkService } from './link.service';
import { CreateLinkDto } from './dto/create-link.dto';
import { UpdateLinkDto } from './dto/update-link.dto';

@Controller('link')
export class LinkController {
  constructor(private readonly linkService: LinkService) {}

  @Post()
  create(@Body() createLinkDto: CreateLinkDto) {
    return this.linkService.create(createLinkDto);
  }

  @Get()
  findAll() {
    return this.linkService.findAll();
  }

  @Get(':id')
  findOne(@Param('id') id: string) {
    return this.linkService.findOne(+id);
  }

  @Patch(':id')
  update(@Param('id') id: string, @Body() updateLinkDto: UpdateLinkDto) {
    return this.linkService.update(+id, updateLinkDto);
  }

  @Delete(':id')
  remove(@Param('id') id: string) {
    return this.linkService.remove(+id);
  }
}

模块

编写好controller和service后,将其注册到模块中

// link.module.ts

import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { LinkService } from './link.service';
import { LinkController } from './link.controller';
import { LinkEntity } from './link.entity';

@Module({
  controllers: [LinkController],
  providers: [LinkService],
  // 使用forFeature方法进行实体注册,这样,我们就可以使用 @InjectRepository()装饰器将 UsersRepository 注入到 UsersService 中
  imports: [TypeOrmModule.forFeature([LinkEntity])],
})
export class LinkModule {}

导入根模块

最后,将我们的LinkModule模块导入到根模块AppModule中

import { Module } from '@nestjs/common';
import { ConfigService, ConfigModule } from '@nestjs/config';
import { TypeOrmModule } from '@nestjs/typeorm';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { LinkModule } from './link/link.module';

@Module({
  imports: [
    ConfigModule.forRoot({
      //全局使用,无需在其他模块中导入它
      isGlobal: true,
      // 自定义 env 文件路径,默认情况下,程序在应用程序的根目录中查找.env文件
      envFilePath: ['.env'],
    }),
    // 异步配置
    TypeOrmModule.forRootAsync({
      inject: [ConfigService],
      useFactory: async (configService: ConfigService) => ({
        type: 'mysql',
        host: configService.get('DB_HOST'),
        port: configService.get('DB_PORT'),
        username: configService.get('DB_USER'),
        password: configService.get('DB_PASSWORD'),
        database: configService.get('DB_DATABASE'),
        // entities: [__dirname + '/**/*.entity{.ts,.js}'],
        // 自动载入实体
        autoLoadEntities: true,
        // 默认:false,如果为true,自动载入的模型将同步,禁止生产环境使用,否则数据将会丢失
        synchronize: true,
      }),
    }),
    LinkModule,
  ],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule {}

接口请求

全部接口遵循restful风格

{
    "name": "带林这此111122222",
    "url": "http://drqiat.tz/pqdnmyw"
}
{
    "name": "带林这此111122222",
    "url": "http://drqiat.tz/pqdnmyw"
}

项目源码

代码已经上传到github中,欢迎大家star,持续更新,如有任何问题可以联系我v:sky201208(注明来意)

https://github.com/fozero/cloud-collect-nestjs

参考阅读

关于我&前端&node进阶交流学习群

大家好,我是阿健Kerry,一个有趣且乐于分享的人,前小鹏汽车、货拉拉高级前端工程师,长期专注前端开发,如果你对前端&Node.js 学习进阶感兴趣的话(后续有计划也可以),可以关注我,加我微信【sky201208】,拉你进交流群一起交流、学习共同进步,群内氛围特别好,定期会组织技术分享~

posted @ 2023-07-13 23:26  fozero  阅读(1067)  评论(0编辑  收藏  举报