基本数据操作
学习目标
- 简单地整合[nestjs][]框架与typeorm
- 实现基本的CRUD数据操作
- 使用class-validator验证请求数据
- 更换更加快速的fastify适配器
- 使用Thunder Client对测试接口
安装Mysql
实际生产环境中建议使用PostgreSQL,因为教程以学习为主,所以直接使用相对来说比较通用和简单的Mysql
使用以下命令安装Mysql
如果本机不是使用linux(比如使用wsl2),请到mysql官网点击download按钮下载安装包后在chrome查看下载地址,然后在开发机用
wget
下载
如果本机使用MacOS,使用
brew install mysql
,如果本机使用Arch系列,使用sudo pacman -Syy mysql
# 下载镜像包
cd /usr/local/src
sudo wget sudo wget https://repo.mysql.com/mysql-apt-config_0.8.22-1_all.deb
# 添加镜像(其它选项不用管,直接OK就可以)
sudo apt-get install ./mysql-apt-config_0.8.22-1_all.deb
# 升级包列表
sudo apt-get update
# 开始安装,输入密码后,有一个密码验证方式,因为是开发用,所以选择第二个弱验证即可
sudo apt-get install mysql-server
# 初始化,在是否加载验证组件时选择No,在是否禁用远程登录时也选择No
sudo mysql_secure_installation
# 因为是远程SSH连接开发所以需要开启远程数据库链接,如果是本地或者wsl2则不需要开启
mysql -u root -p
CREATE USER 'root'@'%' IDENTIFIED BY '密码';
GRANT ALL PRIVILEGES ON *.* TO 'root'@'%' WITH GRANT OPTION;
FLUSH PRIVILEGES;
接着使用Navicat等客户端就可以连接了
预装依赖
- lodash是常用的工具库
- cross-env用于跨平台设置环境变量
- class-transformer用于对请求和返回等数据进行序列化
- class-validator用于验证请求
dto
等 - typeorm一个TS编写的[node.js][]ORM
- [@nestjs/typeorm][]Nestjs的TypeOrm整合模块
- @nestjs/platform-fastifyFastify适配器,用于替代express
- nestjs-swagger生成open api文档,目前我们使用其
PartialType
函数是UpdateDto
中的属性可选 - fastify-swagger生成Fastify的Open API
~ pnpm add class-transformer \
@nestjs/platform-fastify \
class-validator \
lodash \
@nestjs/swagger \
fastify-swagger \
mysql2 \
typeorm \
@nestjs/typeorm
~ pnpm add @types/lodash cross-env @types/node typescript -D
生命周期
要合理的编写应用必须事先了解清楚整个程序的访问流程,本教程会讲解如何一步步演进每一次访问流,作为第一步课时,我们的访问流非常简单,可以参考下图
文件结构
我们通过整合typeorm来连接mysql实现一 个基本的CRUD应用,首先我们需要创建一下文件结构
建议初学者手动创建,没必要使用CLI去创建,这样目录和文件更加清晰
- 创建模块
- 编写模型
- 编写Repository(如果有需要的话)
- 编写数据验证的DTO
- 编写服务
- 编写控制器
- 在每个以上代码各自的目录下建立一个
index.ts
并导出它们 - 在各自的
Module
里进行注册提供者,导出等 - 在
AppModule
中导入这两个模块
编写好之后的目录结构如下
.
├── app.module.ts # 引导模块
├── config # 配置文件目录
│ ├── database.config.ts # 数据库配置
│ └── index.ts
├── main.ts # 应用启动器
├── modules
├── content # 内容模块目录
│ ├── content.module.ts # 内容模块
│ ├── controllers # 控制器
│ ├── dtos # DTO访问数据验证
│ ├── entities # 数据实体模型
| ├── index.ts
│ ├── repositories # 自定义Repository
│ ├── services # 服务
└── core
├── constants.ts # 常量
├── core.module.ts # 核心模块
├── decorators # 装饰器
└── types.ts # 公共类型
应用编码
在开始编码之前需要先更改一下package.json
和nestjs-cli.json
两个文件
在package.json
中修改一下启动命令,以便每次启动可以自动配置运行环境并兼容windows
环境
"prebuild": "cross-env rimraf dist",
"start": "cross-env NODE_ENV=development nest start",
"start:dev": "cross-env NODE_ENV=development nest start --watch",
"start:debug": "cross-env NODE_ENV=development nest start --debug --watch",
"start:prod": "cross-env NODE_ENV=production node dist/main",
为了在每次重新编译前自动删除上次的产出,在nestjs-cli.json
中配置 "deleteOutDir": true
main.ts
把适配器由express换成更快的fastify,并把监听的IP改成0.0.0.0
方便外部访问.为了在使用class-validator的DTO
类中也可以注入nestjs容器的依赖,需要添加useContainer
// main.ts
import { NestFactory } from '@nestjs/core';
import {
FastifyAdapter,
NestFastifyApplication,
} from '@nestjs/platform-fastify';
import { useContainer } from 'class-validator';
import { AppModule } from './app.module';
async function bootstrap() {
const app = await NestFactory.create<NestFastifyApplication>(
AppModule,
new FastifyAdapter()
);
useContainer(app.select(AppModule), { fallbackOnErrors: true });
await app.listen(3000,'0.0.0.0');
}
bootstrap();
连接配置
创建一个src/config/database.config.ts
文件
export const database: () => TypeOrmModuleOptions = () => ({
// ...
// 此处entites设置为空即可,我们直接通过在模块内部使用`forFeature`来注册模型
// 后续魔改框架的时候,我们会通过自定义的模块创建函数来重置entities,以便给自己编写的CLI使用
// 所以这个配置后面会删除
entities: [],
// 自动加载模块中注册的entity
autoLoadEntities: true,
// 可以在开发环境下同步entity的数据结构到数据库
// 后面教程会使用自定义的迁移命令来代替,以便在生产环境中使用,所以以后这个选项会永久false
synchronize: process.env.NODE_ENV !== 'production',
});
CoreModule
核心模块用于挂载一些全局类服务,比如整合typeorm的``TypeormModule`
注意: 这里不要使用@Global()
装饰器来构建全局模块,因为后面在CoreModule
类中添加一些其它方法
返回值中添加global: true
来注册全局模块,并导出metadata
.
// src/core/core.module.ts
export class CoreModule {
public static forRoot(options?: TypeOrmModuleOptions) {
const imports: ModuleMetadata['imports'] = [TypeOrmModule.forRoot(options)];
return {
global: true,
imports,
module: CoreModule,
};
}
}
在AppModule
导入该模块,并注册数据库连接
// src/app.module.ts
@Module({
imports: [CoreModule.forRoot(database())],
...
})
export class AppModule {}