本篇将介绍如何建立 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,可以完美解决这一问题。

nestJS接口返回500 nestjs tcp_配置文件


使用

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

  1. 创建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 {}

运行项目,用数据库管理工具连接我们的数据库,可以发现,我们就完成了数据库的连接。

接下来将讲解 怎么创建实体连接完数据库之后创建实体