本篇将介绍如何建立 NestJs 的数据库连接、并使用数据库联表查询。
简介
Nest 与数据库无关,允许您轻松地与任何 SQL 或 NoSQL 数据库集成。根据您的偏好,您有许多可用的选项。一般来说,将 Nest 连接到数据库只需为数据库加载一个适当的 Node.js 驱动程序,就像使用 Express 或 Fastify 一样。
您还可以直接使用任何通用的 Node.js 数据库集成库或 ORM ,例如 Sequelize (recipe)、knexjs (tutorial)`和 TypeORM ,以在更高的抽象级别上进行操作。
为了方便起见,Nest 还提供了与现成的 TypeORM 与 @nestjs/typeorm 的紧密集成,我们将在本章中对此进行介绍,而与 @nestjs/mongoose 的紧密集成将在这一章中介绍。这些集成提供了附加的特定于 nestjs 的特性,比如模型/存储库注入、可测试性和异步配置,从而使访问您选择的数据库更加容易。
对于一个Web API项目,数据库是必不可少的,Nest与数据库无关,允许您轻松地与任何SQL或NoSQL数据库集成。根据您的偏好,您有许多可用的选项。本篇我们讲解集成MySQL数据库,Nest提供了@nestjs/typeorm包,为了开始使用它,我们首先安装所需的依赖项。
起步
使用 Nest CLI 建立新项目非常简单。 在安装好 npm 后,您可以使用下面命令在您的 OS 终端中创建 Nest 项目:
$ npm i -g @nestjs/cli
$ nest new project-name
要创建启用 TypeScript strict模式的新项目,请将 --strict 标志传递给 nest new 命令
将会创建 project-name 目录, 安装 node_modules 和一些其他样板文件,并将创建一个 src 目录,目录中包含几个核心文件。
src
├── app.controller.spec.ts
├── app.controller.ts
├── app.module.ts
├── app.service.ts
└── main.ts
以下是这些核心文件的简要概述:
app.controller.ts | 带有单个路由的基本控制器示例。 |
app.controller.spec.ts | 对于基本控制器的单元测试样例 |
app.module.ts | 应用程序的根模块。 |
app.service.ts | 带有单个方法的基本服务 |
main.ts | 应用程序入口文件。它使用 NestFactory 用来创建 Nest 应用实例。 |
main.ts | 包含一个异步函数,它负责引导我们的应用程序: |
连接mysql
为了与 SQL和 NoSQL 数据库集成,Nest 提供了 @nestjs/typeorm 包。Nest 使用TypeORM是因为它是 TypeScript 中最成熟的对象关系映射器( ORM )。因为它是用 TypeScript 编写的,所以可以很好地与 Nest 框架集成。
为了开始使用它,我们首先安装所需的依赖项。在本章中,我们将演示如何使用流行的关系型数据库 Mysql , TypeORM 提供了对许多关系数据库的支持,比如 PostgreSQL 、Oracle、Microsoft SQL Server、SQLite,甚至像 MongoDB 这样的 NoSQL 数据库。我们在本章中介绍的步骤对于 TypeORM 支持的任何数据库都是相同的。您只需为所选数据库安装相关的客户端 API 库。
npm install --save @nestjs/typeorm typeorm mysql2
安装过程完成后,我们可以将 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: 'root',
database: 'test',
entities: [],
synchronize: true,
}),
],
})
export class AppModule {}
警告:设置 synchronize: true 不能被用于生产环境,否则您可能会丢失生产环境数据
mysql2 | 用于node.js来操作mysql的库; |
typeorm | 一个orm库,可以理解为把对象和表做了一个映射关系,来方便操作; |
@nestjs/typeorm | nestjs基于typeorm的封装。 |
犹豫前面我们已经介绍了dotenv
配置文件,那么我们就可以使用配置文件的方式使用
存储库模式
TypeORM
支持存储库设计模式,因此每个实体都有自己的存储库。可以从数据库连接获得这些存储库。
创建user.entity.ts
import { Entity, Column, PrimaryGeneratedColumn } from 'typeorm';
@Entity()
export class User {
@PrimaryGeneratedColumn()
id: number;
@Column()
firstName: string;
@Column()
lastName: string;
@Column({ default: true })
isActive: boolean;
}
该 User
实体在 users
目录下。这个目录包含了和 UsersModule
模块有关的所有文件。你可以决定在哪里保存模型文件,但我们推荐在他们的域中就近创建,即在相应的模块目录中。
要开始使用user
实体,我们需要在模块的forRoot()
方法的选项中(除非你使用一个静态的全局路径)将它插入entities
数组中来让 TypeORM知道它的存在。
为了继续这个示例,我们需要至少一个实体。我们来定义User 实体。
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { User } from './users/user.entity';
@Module({
imports: [
TypeOrmModule.forRoot({
type: 'mysql',
host: 'localhost',
port: 3306,
username: 'root',
password: 'root',
database: 'test',
entities: [User],
synchronize: true,
}),
],
})
export class AppModule {}
现在让我们看一下 UsersModule:
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 {}
此模块使用 forFeature()
方法定义在当前范围中注册哪些存储库。这样,我们就可以使用 @InjectRepository()
装饰器将 UsersRepository
注入到 UsersService
中: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);
}
}
不要忘记将 UsersModule 导入根 AppModule。
如果是简单的项目可以这样 粗暴一点,直接写的 app.module.ts
里面,那要是企业级的开发肯定不允许这样做的,首先 该模块会非常的臃肿,而且不符合开发规范,那么我们需要使用配置文件导入到app.module.ts 里面,这样 如果修改数据库相关的配置,只需要修改配置文件即可。
使用配置文件之前我们还是需要了解以下几个库:
dotenv 模块
首先先介绍一下在行业里面应用非常广泛的dotenv,,由于项目不同需求,需要配置不同环境变量,按需加载不同的环境变量文件,使用dotenv,可以完美解决这一问题。
使用
npm install dotenv --save
Usage Create a .env file in the root of your project:
S3_BUCKET="YOURS3BUCKET"
SECRET_KEY="YOURSECRETKEYGOESHERE"
As early as possible in your application, import and configure dotenv:
require('dotenv').config()
console.log(process.env) // remove this after you've confirmed it is working
如需了解更多则可以去查看https://www.npmjs.com/package/dotenv
配置项目
1. 配置文件
创建 .env
文件 主要用于配置变量 内置dotenv
库
- 创建dotenv 文件
在项目第一级文件夹创建 一个.env
文件
DB_TYPE=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_SYNC=false
开发环境env
DB_DATABASE=testdb
DB_HOST=127.0.0.1
DB_PORT=3306
DB_USERNAME=root
DB_PASSWORD=123456
# 用在开发过程中,作用:同步实体->数据库
DB_SYNC=true
生产环境中的env
DB_DATABASE=proddb
DB_HOST=xxx.xxx
DB_PORT=3306
DB_USERNAME=root
DB_PASSWORD=long-password
DB_SYNC=true
这样做的好处就是 以后方便使用和切换各个环境
2. 安装 @nestjs/config
@nestjs/config依赖于dotenv,可以通过key=value形式配置环境变量,项目会默认加载根目录下的.env文件,我们只需在app.module.ts中引入ConfigModule,再使用ConfigModule.forRoot()方法即可。
npm i --save @nestjs/config
app.modules.ts
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { ConfigModule } from '@nestjs/config';
import * as dotenv from 'dotenv';
import * as Joi from 'joi';
const envFilePath = `.env.${process.env.NODE_ENV || `development`}`;
@Module({
// ConfigModule.forRoot() 可以使用env 文件中的常量
imports: [
ConfigModule.forRoot({
// 全局可以使用
isGlobal: true,
envFilePath,
load: [() => dotenv.config({ path: '.env' })],
}),
TypeOrmModule.forRootAsync({
useFactory: () => ({
type: 'mysql',
host: process.env.DB_HOST,
port: process.env.DB_PORT,
username: process.env.DB_USERNAME,
password: process.env.DB_PASSWORD,
database: process.env.DB_DATABASE,
timezone: 'UTC',
charset: 'utf8mb4',
entities: ['./**/*.entity.js'],
synchronize: true,
logging: true,
})})],
],
controllers: [AppController],
providers: [AppService],
})
export class AppModule {}
上述代码将从默认位置(项目根目录)载入并解析一个.env文件,从.env文件和process.env合并环境变量键值对,并将结果存储到一个可以通过ConfigService访问的私有结构。forRoot()方法注册了ConfigService提供者,后者提供了一个get()方法来读取这些解析/合并的配置变量。
3. 配置校验Joi
Joi是一个广泛使用的Node.js数据验证库,提供了一个简单、直观、可读的API来描述数据。它主要用于验证从API端点发送的数据,并允许你创建你想接受的数据类型的蓝图。
下面是一个用Joi描述模式的简单例子:
const schema = Joi.object().keys({
name: Joi.string().alphanum().min(3).max(30).required(),
birthyear: Joi.number().integer().min(1970).max(2013),
});
配置验证,主要是指在应用程序启动时,如果没有提供所需的环境变量或不符合某些验证规则,就会抛出一个异常。@nestjs/config包实现了两种不同的方式来实现这一点。
- Joi内置验证器。通过]gi,你可以定义一个对象模式,并根据它验证JavaScript对象
- 一个自定义的validate()函数,它将环境变量作为输入
特别说明:
最新版本的joi 需要你运行Node v12或更高版本。旧版本的node请安装v16.1.8。这主要是因为在v17.0.2发布后,在构建的时候会出现错误。更多信息请参考其17.0.0发布说明.
安装依赖
npm install --save joi
使用
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { ConfigModule } from '@nestjs/config';
import * as dotenv from 'dotenv';
import * as Joi from 'joi';
const envFilePath = `.env.${process.env.NODE_ENV || `development`}`;
@Module({
// ConfigModule.forRoot() 可以使用env 文件中的常量
imports: [
ConfigModule.forRoot({
isGlobal: true,
envFilePath,
load: [() => dotenv.config({ path: '.env' })],
validationSchema: Joi.object({
NODE_ENV: Joi.string()
.valid('development', 'production')
.default('development'),
DB_PORT: Joi.number().default(3306),
DB_HOST: Joi.string().ip(),
DB_TYPE: Joi.string().valid('mysql', 'postgres'),
DB_DATABASE: Joi.string().required(),
DB_USERNAME: Joi.string().required(),
DB_PASSWORD: Joi.string().required(),
DB_SYNC: Joi.boolean().default(false),
}),
}),
],
controllers: [AppController],
providers: [AppService],
})
export class AppModule {}
4. 加载自定义配置文件
上面我们把数据库的信息都写在了环境变量中,再通过process.env.key形式去获取,但可能存在下面一种需求:config文件夹下存在config.enum.ts
配置文件,需根据NODE_ENV加载不同配置文件;此外需要一种扩展性更好的配置文件格式支持,比如.ts,.js文件,这时可以将数据库配置项等作为一个对象,而不仅仅是key=value的格式,同时也能附加注释、说明等内容。下面我们在config文件夹中增加配置文件,app.module.ts文件再作修改,数据库信息等移至配置文件中。
export enum ConfigEnum {
DB_TYPE = 'DB_TYPE',
DB_HOST = 'DB_HOST',
DB_PORT = 'DB_PORT',
DB_DATABASE = 'DB_DATABASE',
DB_USERNAME = 'DB_USERNAME',
DB_PASSWORD = 'DB_PASSWORD',
DB_SYNC = 'DB_SYNC',
}
import { Module } from '@nestjs/common';
import { UserModule } from './user/user.module';
import { ConfigModule, ConfigService } from '@nestjs/config';
import * as dotenv from 'dotenv';
import * as Joi from 'joi';
import { TypeOrmModule, TypeOrmModuleOptions } from '@nestjs/typeorm';
import { ConfigEnum } from './enum/config.enum';
const envFilePath = `.env.${process.env.NODE_ENV || `development`}`;
@Module({
imports: [
ConfigModule.forRoot({
isGlobal: true,
envFilePath,
load: [() => dotenv.config({ path: '.env' })],
validationSchema: Joi.object({
NODE_ENV: Joi.string()
.valid('development', 'production')
.default('development'),
DB_PORT: Joi.number().default(3306),
DB_HOST: Joi.string().ip(),
DB_TYPE: Joi.string().valid('mysql', 'postgres'),
DB_DATABASE: Joi.string().required(),
DB_USERNAME: Joi.string().required(),
DB_PASSWORD: Joi.string().required(),
DB_SYNC: Joi.boolean().default(false),
}),
}),
TypeOrmModule.forRootAsync({
imports: [ConfigModule],
inject: [ConfigService],
useFactory: (configService: ConfigService) =>
({
type: configService.get(ConfigEnum.DB_TYPE),
host: configService.get(ConfigEnum.DB_HOST),
port: configService.get(ConfigEnum.DB_PORT),
username: configService.get(ConfigEnum.DB_USERNAME),
password: configService.get(ConfigEnum.DB_PASSWORD),
database: configService.get(ConfigEnum.DB_DATABASE),
entities: [],
// 同步本地的schema与数据库 -> 初始化的时候去使用
synchronize: configService.get(ConfigEnum.DB_SYNC),
logging: process.env.NODE_ENV === 'development',
// logging: ['error'],
} as TypeOrmModuleOptions),
}),
// TypeOrmModule.forRoot({
// type: 'mysql',
// host: 'localhost',
// port: 3306,
// username: 'root',
// password: 'example',
// database: 'testdb',
// entities: [],
// // 同步本地的schema与数据库 -> 初始化的时候去使用
// synchronize: true,
// logging: ['error'],
// }),
UserModule,
],
controllers: [],
providers: [],
})
export class AppModule {}
运行项目,用数据库管理工具连接我们的数据库,可以发现,我们就完成了数据库的连接。
接下来将讲解 怎么创建实体连接完数据库之后创建实体