在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)设计模式,因此每个实体都有自己的repository,repository可以处理其实体的所有操作。当需要经常处理实体时,Repository 比 EntityManagers 更方便,可以通过定义一个新类来创建一个实体,并用@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()
函数,需要在UserModule
的forFeature()
方法的选项中将它插入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
,我们就可以在XxxModule
的providers
中使用 @InjectRepository(User)
了。
总结
本文主要记录了创建Nest
项目从安装数据库到利用TypeORM
创建表的过程,Nest
的核心概念在此并没有介绍,详情可查看官网:NestJS - A progressive Node.js framework。