NestJS(一):环境搭建

419 阅读4分钟

Nest之前尝试使用全栈框架Next(v13.4 App Router)Route Handler写API,体验下来感觉不是很好,个人感觉Next主要专注于为前端页面渲染提供解决方案,如果想单纯开发API还是选择专业后台框架较好。

安装@nestjs/cli

$ npm i -g @nestjs/cli  

安装MySQL

这里使用docker安装MySQL,先拉取MySQL镜像:

$ docker pull mysql:latest

获取当前docker下载的所有镜像:

$ docker images
REPOSITORY                  TAG       IMAGE ID       CREATED       SIZE
mysql                       latest    05db07cd74c0   4 days ago    565MB

创建并运行MySQL容器:

$ docker run -itd -p 3306:3306 -e MYSQL_ROOT_PASSWORD=123456 --name mysql-latest mysql

参数说明:

  • -i:以交互模式运行,通常配合-t;
  • -t:为容器重新分配一个伪输入终端,通常配合-i;
  • -d:后台运行容器;
  • -p:端口映射,格式为主机端口:容器端口;
  • -e:设置环境变量,这里设置的是root密码;
  • --name:设置容器别名。

查看运行中的容器:

$ docker ps
CONTAINER ID   IMAGE     COMMAND                   CREATED         STATUS         PORTS                               NAMES
c3945261fd17   mysql     "docker-entrypoint.s…"   6 seconds ago   Up 5 seconds   0.0.0.0:3306->3306/tcp, 33060/tcp   mysql-latest

测试MySQL登录:

$ docker exec -it c3945 bash
$ mysql -uroot -p

创建新的Nest项目

$ nest new nest-server

CRUD生成器

Nest CLI提供了一个生成器,可以自动生成所有的模板文件以减少繁琐步骤,同时让开发者感觉更易用。在项目根目录下执行:

$ nest g res user
? What transport layer do you use? REST API
? Would you like to generate CRUD entry points? Yes

nest g resource|res命令不仅仅生成所有Nest构件模块(模块,服务,控制器类)也生成实体类,DTO类和测试(.spec)文件。执行完上述命令就会新建src/user目录并生成REST API风格的CRUD模板文件,想查看更多生成器功能可以执行nest generate|g -h命令。

连接数据库

将 Nest 连接到数据库只需为数据库加载一个适当的 NodeJS 驱动程序,还可以直接使用任何通用的 NodeJS 数据库 ORM ,以在更高的抽象级别上进行操作。为了与 SQL和 NoSQL 数据库集成,Nest 提供了 @nestjs/typeorm 包。Nest 使用TypeORM是因为它是 TypeScript 中最成熟的对象关系映射器( ORM )。并且使用 TypeScript 编写,可以很好地与 Nest 框架集成。

连接数据库之前,需要创建一个数据库:

mysql> CREATE DATABASE admin_schema;

安装必要依赖包:

$ yarn add @nestjs/typeorm typeorm mysql2 -S

安装过程完成后,将 TypeOrmModule 导入AppModule

import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { UserModule } from './user/user.module';
import { TypeOrmModule } from '@nestjs/typeorm';

@Module({
  imports: [
    UserModule,
    TypeOrmModule.forRoot({
      type: 'mysql',
      username: 'root',
      password: '123456',
      host: 'localhost',
      port: 3306,
      database: 'admin_schema',
      // 数据库的结构是否和代码保持同步
      synchronize: true,
      // 两次重试连接的间隔(ms)(默认:3000)
      retryDelay: 500,
      // 重试连接数据库的次数(默认:10)
      retryAttempts: 10,
      // 如果为true,将自动加载实体(默认:false)
      autoLoadEntities: true,
      // 如果为true,在应用程序关闭后连接不会关闭(默认:false)
      keepConnectionAlive: false,
    }),
  ],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule {}

定义User实体

实体是一个映射到数据库表(或使用 MongoDB 时的集合)的类。TypeORM 支持存储库(repository)设计模式,因此每个实体都有自己的repositoryrepository可以处理其实体的所有操作。当需要经常处理实体时,RepositoryEntityManagers 更方便,可以通过定义一个新类来创建一个实体,并用@Entity()装饰器来标记,修改CRUD生成器生成的src/user/entities/user.entity.ts文件:

import {
  Column,
  CreateDateColumn,
  Entity,
  PrimaryColumn,
  UpdateDateColumn,
} from 'typeorm';

enum AccountStatus {
  INACTIVE,
  ACTIVE,
}

@Entity()
export class User {
  @PrimaryColumn()
  id: string;

  @Column({ type: 'varchar', length: 255, unique: true })
  username: string;

  @Column({ type: 'varchar', length: 255 })
  password: string;

  @Column({ nullable: true })
  email: string;

  @Column({
    type: 'enum',
    name: 'account_status',
    enum: AccountStatus,
    default: AccountStatus.ACTIVE,
  })
  accountStatus: number;

  @CreateDateColumn({ type: 'timestamp', name: 'created_time' })
  createdTime: Date;

  @UpdateDateColumn({ type: 'timestamp', name: 'update_time' })
  updateTime: Date;
}

创建User实体类后,下一步是将实例化责任移交给 Nest。为此必须将 User 类传递给 TypeOrm.forFeature() 函数,需要在UserModuleforFeature()方法的选项中将它插入entities数组中来让 TypeORM知道它的存在。

import { Module } from '@nestjs/common';
import { UserService } from './user.service';
import { UserController } from './user.controller';
import { TypeOrmModule } from '@nestjs/typeorm';
import { User } from './entities/user.entity';

@Module({
  imports: [TypeOrmModule.forFeature([User])],
  controllers: [UserController],
  providers: [UserService],
})
export class UserModule {}

这将创建以下数据库表:

mysql> DESC user;
+----------------+---------------+------+-----+----------------------+--------------------------------------------------+
| Field          | Type          | Null | Key | Default              | Extra
|
+----------------+---------------+------+-----+----------------------+--------------------------------------------------+
| id             | varchar(255)  | NO   | PRI | NULL                 |
|
| username       | varchar(255)  | NO   | UNI | NULL                 |
|
| password       | varchar(255)  | NO   |     | NULL                 |
|
| email          | varchar(255)  | YES  |     | NULL                 |
|
| account_status | enum('0','1') | NO   |     | 1                    |
|
| created_time   | timestamp(6)  | NO   |     | CURRENT_TIMESTAMP(6) | DEFAULT_GENERATED
|
| update_time    | timestamp(6)  | NO   |     | CURRENT_TIMESTAMP(6) | DEFAULT_GENERATED on update CURRENT_TIMESTAMP(6) |
+----------------+---------------+------+-----+----------------------+--------------------------------------------------+

值得注意的是,如果在连接数据库时配置了autoLoadEntities: true这一选项,每个通过forFeature()注册的实体都会自动添加到配置对象的entities数组中。另外,此模块使用 forFeature() 方法定义哪些repository在当前作用域(UserModule)中被注册。这样就可以使用 @InjectRepository()装饰器将 UserRepository 注入到当前作用域(UserModule)的 UserService 中:

import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { User } from './entities/user.entity';
import { Repository } from 'typeorm';

@Injectable()
export class UserService {
  constructor(
    @InjectRepository(User) private readonly userRepository: Repository<User>,
  ) {
  }
  
  ...
}

如果要在导入TypeOrmModule.forFeature 的模块之外的其他模块使用该模块的repository,则需要重新导出由其生成的providers。 可以通过导出整个模块来做到这一点:

import { Module } from '@nestjs/common';
import { UserService } from './user.service';
import { UserController } from './user.controller';
import { TypeOrmModule } from '@nestjs/typeorm';
import { User } from './entities/user.entity';

@Module({
  imports: [TypeOrmModule.forFeature([User])],
  exports: [TypeOrmModule],
  controllers: [UserController],
  providers: [UserService],
})
export class UserModule {}

这样,如果在 XxxModule 中导入 UserModule ,我们就可以在XxxModuleproviders中使用 @InjectRepository(User)了。

总结

本文主要记录了创建Nest项目从安装数据库到利用TypeORM创建表的过程,Nest的核心概念在此并没有介绍,详情可查看官网:NestJS - A progressive Node.js framework