- 创建项目
- Nest 控制器
- nest 配置路由请求数据
- Nest 服务
- Nest 模块
- 配置静态资源
- 配置模板引擎
- Cookie 的使用
- Session 的使用
- 跨域,前缀路径、网站安全、请求限速
- 管道、守卫、拦截器、过滤器、中间件
- 一例看懂中间件、守卫、管道、异常过滤器、拦截器
- 数据验证
- 配置抽离
- 环境配置
- 文件上传与下载
- 实现图片随机验证码
- 邮件服务
- nest 基于 possport + jwt 做登陆验证
- 对数据库的密码加密:md5 和 bcryptjs
- 角色权限
- 定时任务
- 接入 Swagger 接口文档
- nest 连接 Mongodb
- typeORM 操作 Mysql 数据库
- nest 统一处理数据库操作的查询结果
- 数据库实体设计与操作
- typeorm 增删改查操作
- typeorm 使用事务的 3 种方式
- typeorm 一对一关系设计与增删改查
- typeorm 一对多和多对一关系设计与增删改查
- typeorm 多对多关系设计与增删改查
- nest 连接 Redis
- 集成 redis 实现单点登录
- Q:nestJS 注入其他依赖时为什么还需要导入其 module
文章来源于网络收集而来,版权归原创者所有,如有侵权请及时联系!
typeORM 操作 Mysql 数据库
mac 中,直接使用 brew install mysql
安装 mysql,然后启动服务 brew services start mysql
查看服务已经启动 ps aux | grep mysql
Nest 操作 Mysql 官方文档: https://docs.nestjs.com/techniques/database
npm install --save @nestjs/typeorm typeorm mysql
配置数据库连接地址
// src/config/typeorm.ts const { MYSQL_HOST, MYSQL_PORT, MYSQL_USER, MYSQL_PASSWORD, MYSQL_DATABASE } = process.env; const config = { type: 'mysql', host: MYSQL_HOST, port: MYSQL_PORT, username: MYSQL_USER, password: MYSQL_PASSWORD, database: MYSQL_DATABASE, synchronize: process.env.NODE_ENV !== 'production', // 生产环境不要开启 autoLoadEntities: true, // 如果为 true,将自动加载实体(默认:false) keepConnectionAlive: true, // 如果为 true,在应用程序关闭后连接不会关闭(默认:false) retryDelay: 3000, // 两次重试连接的间隔(ms)(默认:3000) retryAttempts: 10, // 重试连接数据库的次数(默认:10) dateStrings: 'DATETIME', // 转化为时间 timezone: '+0800', // +HHMM -HHMM // 自动需要导入模型 entities: ['dist/**/*.entity{.ts,.js}'], }; export default config;
// app.module.ts 中配置 import { resolve, join } from 'path'; import { ConfigModule, ConfigService } from 'nestjs-config'; import { TypeOrmModule } from '@nestjs/typeorm'; @Module({ imports: [ // 加载配置文件目录 ConfigModule.load(resolve(__dirname, 'config', '**/!(*.d).{ts,js}')), // 连接 mysql 数据库 TypeOrmModule.forRootAsync({ useFactory: (config: ConfigService) => config.get('typeorm'), inject: [ConfigService], }), ], controllers: [], providers: [], }) export class AppModule implements NestModule {}
配置实体 entity
// photo.entity.ts import { Column, Entity, ManyToMany, OneToMany, PrimaryGeneratedColumn, } from 'typeorm'; import { PostsEntity } from './post.entity'; @Entity('photo') export class PhotoEntity { // @PrimaryGeneratedColumn() // id: number; // 标记为主列,值自动生成 @PrimaryGeneratedColumn('uuid') id: string; // 该值将使用 uuid 自动生成 @Column({ length: 50 }) url: string; // 多对一关系,多个图片对应一篇文章 @ManyToMany(() => PostsEntity, (post) => post.photos) posts: PostsEntity; } import { Column, Entity, OneToMany, PrimaryGeneratedColumn } from 'typeorm'; import { PhotoEntity } from './photo.entity'; export type UserRoleType = 'admin' | 'editor' | 'ghost'; export type postStatus = 1 | 2 | 3; // mysql 的列类型: type /** * 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 */ /** * ColumnOptions 中可用选项列表: * length: number - 列类型的长度。 例如,如果要创建 varchar(150)类型,请指定列类型和长度选项。 width: number - 列类型的显示范围。 仅用于 MySQL integer types(opens new window) onUpdate: string - ON UPDATE 触发器。 仅用于 MySQL (opens new window). nullable: boolean - 在数据库中使列 NULL 或 NOT NULL。 默认情况下,列是 nullable:false。 update: boolean - 指示"save"操作是否更新列值。如果为 false,则只能在第一次插入对象时编写该值。 默认值为"true"。 select: boolean - 定义在进行查询时是否默认隐藏此列。 设置为 false 时,列数据不会显示标准查询。 默认情况下,列是 select:true default: string - 添加数据库级列的 DEFAULT 值。 primary: boolean - 将列标记为主要列。 使用方式和 @ PrimaryColumn 相同。 unique: boolean - 将列标记为唯一列(创建唯一约束)。 comment: string - 数据库列备注,并非所有数据库类型都支持。 precision: number - 十进制(精确数字)列的精度(仅适用于十进制列),这是为值存储的最大位数。仅用于某些列类型。 scale: number - 十进制(精确数字)列的比例(仅适用于十进制列),表示小数点右侧的位数,且不得大于精度。 仅用于某些列类型。 zerofill: boolean - 将 ZEROFILL 属性设置为数字列。 仅在 MySQL 中使用。 如果是 true,MySQL 会自动将 UNSIGNED 属性添加到此列。 unsigned: boolean - 将 UNSIGNED 属性设置为数字列。 仅在 MySQL 中使用。 charset: string - 定义列字符集。 并非所有数据库类型都支持。 collation: string - 定义列排序规则。 enum: string[]|AnyEnum - 在 enum 列类型中使用,以指定允许的枚举值列表。 你也可以指定数组或指定枚举类。 asExpression: string - 生成的列表达式。 仅在 MySQL (opens new window) 中使用。 generatedType: "VIRTUAL"|"STORED" - 生成的列类型。 仅在 MySQL (opens new window) 中使用。 hstoreType: "object"|"string" -返回 HSTORE 列类型。 以字符串或对象的形式返回值。 仅在 Postgres 中使用。 array: boolean - 用于可以是数组的 postgres 列类型(例如 int []) transformer: { from(value: DatabaseType): EntityType, to(value: EntityType): DatabaseType } - 用于将任意类型 EntityType 的属性编组为数据库支持的类型 DatabaseType。 注意:大多数列选项都是特定于 RDBMS 的,并且在 MongoDB 中不可用 */ @Entity('posts') export class PostsEntity { // @PrimaryGeneratedColumn() // id: number; // 标记为主列,值自动生成 @PrimaryGeneratedColumn('uuid') id: string; // 该值将使用 uuid 自动生成 @Column({ length: 50 }) title: string; @Column({ length: 18 }) author: string; @Column({ type: 'longtext', default: null }) content: string; @Column({ default: null }) cover_url: string; @Column({ default: 0 }) type: number; @Column({ type: 'text', default: null }) remark: string; @Column({ type: 'enum', enum: [1, 2, 3], default: 1, }) status: postStatus; @Column({ type: 'timestamp', default: () => 'CURRENT_TIMESTAMP' }) create_time: Date; @Column({ type: 'timestamp', default: () => 'CURRENT_TIMESTAMP', }) update_time: Date; @Column({ type: 'enum', enum: ['admin', 'editor', 'ghost'], default: 'ghost', select: false, // 定义在进行查询时是否默认隐藏此列 }) role: UserRoleType; // 一对多关系,一篇文章对应多个图片 // 在 service 中查询使用 .find({relations: ['photos]}) 查询文章对应的图片 @OneToMany(() => PhotoEntity, (photo) => photo.posts) photos: []; }
参数校验
Nest 与 class-validator 配合得很好。这个优秀的库允许您使用基于装饰器的验证。装饰器的功能非常强大,尤其是与 Nest 的 Pipe 功能相结合使用时,因为我们可以通过访问 metatype
信息做很多事情,在开始之前需要安装一些依赖。
npm i --save class-validator class-transformer
// posts.dto.ts import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; import { IsNotEmpty, IsNumber, IsString } from 'class-validator'; export class CreatePostDto { @IsNotEmpty({ message: '文章标题必填' }) readonly title: string; @IsNotEmpty({ message: '缺少作者信息' }) readonly author: string; readonly content: string; readonly cover_url: string; @IsNotEmpty({ message: '缺少文章类型' }) readonly type: number; readonly remark: string; }
在控制器对应的 Module 中配置 Model
import { Module } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; import { PostsService } from './posts.service'; import { PostsController } from './posts.controller'; import { PostsEntity } from './entities/post.entity'; @Module({ imports: [TypeOrmModule.forFeature([PostsEntity])], controllers: [PostsController], providers: [PostsService], }) export class PostsModule {}
在服务里面使用 @InjectRepository 获取数据库 Model 实现操作数据库
// posts.services.ts import { HttpException, HttpStatus, Injectable } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { Repository, Not, Between, Equal, Like, In } from 'typeorm'; import * as dayjs from 'dayjs'; import { CreatePostDto } from './dto/create-post.dto'; import { UpdatePostDto } from './dto/update-post.dto'; import { PostsEntity } from './entities/post.entity'; import { PostsRo } from './interfaces/posts.interface'; @Injectable() export class PostsService { constructor( @InjectRepository(PostsEntity) private readonly postsRepository: Repository<PostsEntity>, ) {} async create(post: CreatePostDto) { const { title } = post; const doc = await this.postsRepository.findOne({ where: { title } }); console.log('doc', doc); if (doc) { throw new HttpException('文章标题已存在', HttpStatus.BAD_REQUEST); } return { data: await this.postsRepository.save(post), message: '创建成功', }; } // 分页查询列表 async findAll(query = {} as any) { // eslint-disable-next-line prefer-const let { pageSize, pageNum, orderBy, sort, ...params } = query; orderBy = query.orderBy || 'create_time'; sort = query.sort || 'DESC'; pageSize = Number(query.pageSize || 10); pageNum = Number(query.pageNum || 1); console.log('query', query); const queryParams = {} as any; Object.keys(params).forEach((key) => { if (params[key]) { queryParams[key] = Like(`%${params[key]}%`); // 所有字段支持模糊查询、%%之间不能有空格 } }); const qb = await this.postsRepository.createQueryBuilder('post'); // qb.where({ status: In([2, 3]) }); qb.where(queryParams); // qb.select(['post.title', 'post.content']); // 查询部分字段返回 qb.orderBy(`post.${orderBy}`, sort); qb.skip(pageSize * (pageNum - 1)); qb.take(pageSize); return { list: await qb.getMany(), totalNum: await qb.getCount(), // 按条件查询的数量 total: await this.postsRepository.count(), // 总的数量 pageSize, pageNum, }; } // 根据 ID 查询详情 async findById(id: string): Promise<PostsEntity> { return await this.postsRepository.findOne({ where: { id } }); } // 更新 async update(id: string, updatePostDto: UpdatePostDto) { const existRecord = await this.postsRepository.findOne({ where: { id } }); if (!existRecord) { throw new HttpException(`id 为${id}的文章不存在`, HttpStatus.BAD_REQUEST); } // updatePostDto 覆盖 existRecord 合并,可以更新单个字段 const updatePost = this.postsRepository.merge(existRecord, { ...updatePostDto, update_time: dayjs().format('YYYY-MM-DD HH:mm:ss'), }); return { data: await this.postsRepository.save(updatePost), message: '更新成功', }; } // 删除 async remove(id: string) { const existPost = await this.postsRepository.findOne({ where: { id } }); if (!existPost) { throw new HttpException(`文章 ID ${id} 不存在`, HttpStatus.BAD_REQUEST); } await this.postsRepository.remove(existPost); return { data: { id }, message: '删除成功', }; } }
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论