最近在探究Nest,发现这东西还不错,分享给大家,上一篇文章
Nest探究(一):Nest
已经大致说明了一下Nest的结构以及Nest简单的使用,而这篇文章将会介绍Nest数据库的相关操作,如有错误,还请各位大佬指出!
连接数据库
既然是后端的项目,连接数据库是必不可少的,这里我选择更为熟悉的MySQL数据库。
而Nest当中,有很多连接数据库的
ORM
(对象关系映射器),如
TypeORM
、
Sequelize
、
Prisma
,在此我选择
TypeORM
,
Sequelize
实际上与
koa
一致,是使用
sqly
语句,来对数据库进行操作,而
TypeORM
它本身是由
TypeScript
写的,对
Nest
的支持相对较好,而且
Nest
也提供了
@nestjs/typeorm
包,对于一些常用的增删改查sql语句也是进行了函数封装,开箱即用,比较方便;
Prisma
呼声很高,有兴趣的同学可以自行查看,在这里我使用
TypeORM
。
在此之前,我先解释一下,什么是
ORM
?
举个例子,我们有如下一张表,
+----+--------+------------+
| id | title | text |
+----+-------------+--------------+
| 1 | 标题 | 内容文本 |
+----+--------+------------+
而这张表的每一行,可以使用一个javaScript
对象来表示
id: 1,
title:"标题",
content:"内容文本"
ORM
就是这样,把关系数据库的结构映射到对象上。而我们只要传入这些结构的javaScript
对象,数据库就会自行对应存储数据,是不是简单方便?接下来,我们看看如何使用TypeORM
。
首先我们需要安装依赖
npm install --save @nestjs/typeorm
其次使用TypeORM
连接mysql
数据库,也需要安装依赖
npm install --save typeorm mysql2
首先需要在项目根目录下创建两个文件.env
文件和.env.prod
文件,存储的分别是开发环境和线上环境不同的环境变量。
// 数据库地址
DB_HOST=localhost
// 数据库端口
DB_PORT=3306
// 数据库登录名
DB_USER=root
// 数据库登录密码
DB_PASSWD=root
// 数据库名字
DB_DATABASE=demo
.env.prod
中的是上线要用的数据库信息,如果你的项目要上传到线上管理,为了安全性考虑,建议这个文件添加到.gitignore
中。
接着在根目录下创建一个文件夹config
(与src
同级),然后再创建一个env.ts
用于根据不同环境读取相应的配置文件。
import * as fs from 'fs';
import * as path from 'path';
const isProd = process.env.NODE_ENV === 'production';
function parseEnv() {
const localEnv = path.resolve('.env');
const prodEnv = path.resolve('.env.prod');
if (!fs.existsSync(localEnv) && !fs.existsSync(prodEnv)) {
throw new Error('缺少环境配置文件');
const filePath = isProd && fs.existsSync(prodEnv) ? prodEnv : localEnv;
return { path:filePath };
export default parseEnv();
而nest
带有环境配置
yarn add @nestjs/config
@nestjs/config
默认会从项目根目录载入并解析.env
文件,从.env
文件和process.env
合并环境变量键值对,forRoot()
方法注册了 ConfigService
提供者,后者提供了一个 get()
方法来读取这些解析/合并的配置变量。要注入ConfigService
,需要在需要使用的地方先导入ConfigModule
。
而在app.module
中使用了ConfigModule.forRoot()
,将isGlobal
设置为true
,在其他地方使用时不需要做任何事,表示全局使用,此时就可以在全局范围内使用process.env.xxx
读取全局变量
在app.module.ts
文件中使用@nestjs/config
进行全局配置,以及使用@nestjs/typeorm
提供的TypeOrmModule
连接数据库如下
在app.module.ts使用
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { ConfigModule } from '@nestjs/config';
@Module({
imports: [
ConfigModule.forRoot({
isGlobal: true
TypeOrmModule.forRootAsync({
useFactory: () => ({
type: 'mysql',
host: process.env.DATABASE_HOST,
port: +process.env.DATABASE_PORT, // 来自process.env的每个值都是字符串,前面加+转数字
username: process.env.DATABASE_USER,
password: process.env.DATABASE_PASSWORD,
database: process.env.DATABASE_NAME,
autoLoadEntities: true, // 自动加载模块 推荐
// entities: [path.join(__dirname, '/../**/*.entity{.ts,.js}')], // 不推荐
synchronize: true // 开启同步,生产中要禁止
controllers: [],
providers: []
export class AppModule {}
上文代码中forRootAsync
使用了TypeORM
的异步工程模式,这样可以解决imports
的顺序问题,也就是说,使用了forRootAsync
,可以不用在意imports
这个数组中使用TypeOrmModule
的顺序.
因为每个创建的实体必须在连接选项中进行注册,所以TypeORM
提供了autoLoadEntities
来自动加载创建的数据库实体,使用这种方式也比较推荐,也可以使用entities: [path.join(__dirname, '/../**/*.entity{.ts,.js}')]
的方式,这表示,指定包含所有实体的整个目录,该目录下所有实体都将被加载.
Entity(实体)
使用@Entity()
来标记通过定义一个新类创建的实体,装饰器根据编写的类,通过映射自动生成一个SQL表,以及他们包含的元数据,其中包含
Column(普通列)
用@Colimn()
来标记普通列
为列指定类型:
@Column("int")
/@Column({ type: "int" })
:数字类型
还需要指定其他参数:
@Column("varchar", { length: 200 })
:字符串类型,长度为200
@Column({ type: "int", length: 200 })
:数字类型,长度为200
列选项定义实体列的其他选项。 你可以在@Column()
上指定列选项:
title: string
: 数据库表中的列名。
unique: true
:将列标记为唯一列,里面的值不可重复
nullable: boolean
: 在数据库中使列NULL
或NOT NULL
。 默认情况下,列是nullable:false
。
更多的请看TypeORM中文文档
每个实体都必须要有一个主列
用@PrimaryColumn()
来标记主列,需要给它手动分配值
用@PrimaryGeneratedColumn()
来标记主列,该值将使用自动增量值自动生成
用@PrimaryGeneratedColumn('uuid')
来标记主列,该值将使用uuid
(通用唯一标识符)自动生成,uuid
可以被认为是唯一的
uuid
是让分布式系统中的所有元素都能有唯一的辨识信息,而不需要通过中央控制端来做辨识信息的指定。如此一来,每个人都可以创建不与其它人冲突的uuid
。在这样的情况下,就不需考虑数据库创建时的名称重复问题
uuid
的标准型式包含32个16进制数字,以连字号分为五段,形式为8-4-4-4-12
的32
个字符,如:550e8400-e29b-41d4-a716-446655440000
。
OneToOne(一对一关系)
使用OneToOne
指明一对一的关系,在一对一关系中,表A
中的一条记录,只能关联表B
中的一条记录。比如:每一个用户都只能有一个用户档案,
在用户表的实体当中使用OneToOne
指明一对一的关系,并使用档案表实体作为用户表的某个属性。使用@ JoinColumn
定义外键,并允许自定义连接列名和引用的列名。
@JoinColumn
必须在且只在关系的一侧的外键上, 设置@JoinColumn
在该表的实体中,该表将会包含一个relation id
和目标实体表的外键。记住,不能同时在二者的entity
中使用。
OneToMany(一对多关系)
使用OneToMany
指明一对多的关系,在一对多关系中,表A
中的一条记录,可以关联表B
中的一条或多条记录。比如:每一个文章分类都可以对应多篇文章,反过来一篇文章只能属于一个分类,这种分类表和文章表的关系就是一对多的关系。
在一
,即分类表的实体当中使用OneToMany
指明一对多的关系,并使用多
即文章表的实体作为一
的某个属性。
在多
,即文章表的实体当中使用ManyToOne
指明关系,并使用一
即分类表的实体作为多
的某个属性。使用@ JoinColumn
定义外键,并允许自定义连接列名和引用的列名。
TypeORM在处理“一对多”的关系时, 将一
的主键作为多
的外键,即@ManyToOne
装饰的属性;这样建表时用最少的数据表操作代价,避免数据冗余,提高效率。
ManyToMany(多对多关系)
@ManyToMany()
指明多对多关系,多对多是表A
的一条记录关联表B
的多个记录,而表B
的一条记录也可以关联表A
的多条记录的关系。比如:一篇文章可以对应多个板块,而一个板块下面也有多个文章。
@JoinTable
用于描述“多对多”关系, @JoinTable()
是@ManyToMany()
关系所必需的,
通过配置joinColumns
和inverseJoinColumns
来自定义中间表
的列名称。
保存这种关系,需要启动级联cascade
@ManyToMany((type) => Role, (role) => role.users, { cascade: true })
使用TypeORM
进行注册实体,首先我们创建user
用户实体。
Role
是角色实体
import {
Entity,
Column,
JoinTable,
ManyToMany,
PrimaryGeneratedColumn,
CreateDateColumn,
UpdateDateColumn
} from 'typeorm';
import { Role } from './role.entity';
@Entity()
export class User {
@PrimaryGeneratedColumn()
id: number;
@Column()
username: string;
@Column()
password: string;
@JoinTable({ name: 'user_roles' })
* 关系类型,返回相关实体引用
* cascade: true,插入和更新启用级联,也可设置为仅插入或仅更新
* ['insert']
@ManyToMany((type) => Role, (role) => role.users, { cascade: true })
roles: Role[];
@CreateDateColumn()
createAt: Date;
@UpdateDateColumn()
updateAt: Date;
创建角色实体
import {
Entity,
Column,
ManyToMany,
PrimaryGeneratedColumn,
CreateDateColumn,
UpdateDateColumn
} from 'typeorm';
import { User } from './user.entity';
@Entity()
export class Role {
@PrimaryGeneratedColumn()
id: number;
@Column()
name: string;
@ManyToMany((type) => User, (user) => user.roles)
users: User[];
@CreateDateColumn()
createAt: Date;
@UpdateDateColumn()
updateAt: Date;
在查找关联表的信息时,需要注意:
使用relations
表示需要加载主体,如下是一个查询用户列表所有数据的代码
方式一:relations: { roles: true }
方式二:relations: ['roles']
TypeOrm
其中有封装好的操作数据库的函数,代码奉上。
async create(createArticleDto: CreateArticleDto) {
const article = await this.articleRepository.create({
...createArticleDto
return await this.articleRepository.save(article);
async getArticleList(paginationsQuery: PaginationQueryDto) {
const { limit, offset } = paginationsQuery;
return await this.articleRepository.find({
skip: offset,
take: limit
async findOneById(id: number) {
return await this.articleRepository.findOneBy({ id });
async update(id: number, updateArticleDto: UpdateArticleDto) {
const article = await this.articleRepository.preload({
...updateArticleDto
if (!article) {
throw new NotFoundException(`${id} not found`);
return await this.articleRepository.save(article);
async remove(id: number) {
const article = await this.articleRepository.findOneBy({ id });
if (!article) {
throw new NotFoundException(`${id} not found`);
return await this.articleRepository.remove(article);
这里我们只是讲解了TypeOrm
的数据库连接和基本操作,下一篇我们将会讲到一些更加复杂的管道配置和各种装饰器。
我是小白,我们一起学习!