前言
- 前面分别学习了nest与typeorm的基本使用,下面需要把2者结合起来。
- 本篇任务:
1、创建users、posts、role表,每个表字段不少于4个
2、users和posts是一对多的关系(不要求一定创建外键)
3、users和role是多对多的关系(不要求一定创建外键)
4、users、posts、role的增删改操作
5、查询用户列表,要同时查询出关联的posts和role的数据
6、给用户分配角色的时候时候要加上事务
7、上面的全部提供restfull api接口的方式 - 官网资料:https://docs.nestjs.com/techniques/database
新建项目
- 我们使用官网脚手架进行安装。
nest new yourproject
- 同时需要安装typeorm与mysql,数据库搭建不在本篇内容。
npm install --save @nestjs/typeorm typeorm mysql
链接数据库
- 对于数据库敏感信息,需要放置env里,nest提供了个包内部使用dotenv来解决:
$ npm i --save @nestjs/config
- 关于配置env,中文文档和英文文档说法不太一样,中文文档那个应该过时了。
- 如果没啥特别的配置,使用时在appmodule中import:
import { Module } from '@nestjs/common';
import { ConfigModule } from '@nestjs/config';
@Module({
imports: [ConfigModule.forRoot()],
})
export class AppModule {}
- 使用自定义配置,需要建立配置文件导出 config/database.config.ts
export default () => ({
type: process.env.DB_TYPE,
host: process.env.DB_HOST,
port: Number(process.env.DB_PORT),
database: process.env.DB_DATABASE,
username: process.env.DB_USERNAME,
password: process.env.DB_PASSWORD,
logging: true,
});
- 然后appmodule下导入,同时配置typeorm:
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import configuration from '../config/database.config';
import { TypeOrmModule } from '@nestjs/typeorm';
import { ConfigModule } from '@nestjs/config';
@Module({
imports: [
ConfigModule.forRoot({
load: [configuration],
}),
TypeOrmModule.forRoot(),
],
controllers: [AppController],
providers: [AppService],
})
export class AppModule {}
- 配置typeorm的ormconfig文件,用来连接数据库:
module.exports = [
{
name: 'default',
type: process.env.DB_TYPE,
host: process.env.DB_HOST,
port: Number(process.env.DB_PORT),
database: process.env.DB_DATABASE,
username: process.env.DB_USERNAME,
password: process.env.DB_PASSWORD,
logging: false,
synchronize: true,
entities: ['dist/src/**/*.entity.{ts,js}'],
migrations: ['src/migration/*.ts'],
subscribers: ['src/subscriber/**/*.ts'],
cli: {
entitiesDir: 'src/',
migrationsDir: 'src/migration',
subscribersDir: 'src/subscriber',
},
},
];
- 此时start,发现已经成功链接数据库了。
- nest上要的env实际不是链接数据库,而是去提供了一个config的service,typeorm才是真正利用env链接得数据库。
- 可以新建个模块来验证下:
- 使用命令新建个模块三件套:
nest g mo user
nest g co user
nest g s user
- module下导入它
@Module({
imports: [ConfigModule],
controllers: [UserController],
providers: [UserService],
})
export class UserModule {}
- 在控制器中,可以试着打印注入的config服务,如果能打印出来则成功:
@Controller('user')
export class UserController {
constructor(private configService: ConfigService) {
// get an environment variable
const dbUser = this.configService.get<string>('DB_TYPE');
// get a custom configuration value
const dbHost = this.configService.get<string>('DB_PORT');
console.log(dbUser, dbHost);
}
}
编写实体
- 首先创建user的实体,在其文件夹下建立user.entity.ts。
- 因为ormconfig里配置了实体后缀,所以实体必须以此结尾,代码直接拷贝上次的
import {
Entity,
PrimaryGeneratedColumn,
Column,
CreateDateColumn,
UpdateDateColumn,
DeepPartial
} from 'typeorm';
@Entity({ name: 'user' })
export class UserEntity {
@PrimaryGeneratedColumn({
type: 'int',
name: 'id',
comment: '主键id',
})
id: number;
@Column({
type: 'varchar',
nullable: false,
length: 50,
unique: true,
name: 'username',
comment: '用户名',
})
username: string;
@Column({
type: 'varchar',
nullable: false,
length: 100,
comment: '密码',
})
password: string;
@Column('tinyint', {
nullable: false,
default: () => 0,
name: 'is_del',
comment: '是否删除,1表示删除,0表示正常',
})
isDel: number;
@CreateDateColumn({
type: 'timestamp',
nullable: false,
name: 'created_at',
comment: '创建时间',
})
createdAt: Date;
@UpdateDateColumn({
type: 'timestamp',
nullable: false,
name: 'updated_at',
comment: '更新时间',
})
updateAt: Date;
}
export type UserEntityDataType = UserEntityDataType = DeepPartial<UserEntity>;
- module中需要导入实体:
@Module({
imports: [ConfigModule, TypeOrmModule.forFeature([UserEntity ])],
controllers: [UserController],
providers: [UserService],
})
- 先在服务中写个新增和查询:
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { UserEntity ,UserEntityDataType} from './user.entity';
@Injectable()
export class UserService {
constructor(
@InjectRepository(UserEntity)
private readonly userRepository: Repository<UserEntity>,
) {}
async createUser(data:UserEntityDataType): Promise<UserEntity> {
return await this.userRepository.save(data);
}
async userList(): Promise<UserEntity[]> {
return await this.userRepository.find();
}
}
- 在控制器中写入路由和服务:
import { Controller, Post, Body, Get } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { UserService } from './user.service';
import { UserEntity,UserEntityDataType } from './user.entity';
@Controller('user')
export class UserController {
constructor(
private configService: ConfigService,
private readonly userService: UserService,
) {
// get an environment variable
const dbUser = this.configService.get<string>('DB_TYPE');
// get a custom configuration value
const dbHost = this.configService.get<string>('DB_PORT');
console.log(dbUser, dbHost);
}
@Post()
async createUser(
@Body() data:UserEntityDataType,
): Promise<UserEntity> {
return await this.userService.createUser(data);
}
@Get()
async userList(): Promise<UserEntity[]> {
return await this.userService.userList();
}
}
- 启动服务,拿postman之类玩意测试下。
- 使用get访问http://localhost:3000/user 应该拿到空数组。
- 然后使用Post新增个user:
{
"username":"yehuozhili",
"password":"12345"
}
- 得到回复:
{
"username": "yehuozhili",
"password": "12345",
"id": 1,
"isDel": 0,
"createdAt": "2020-09-08T22:24:09.569Z",
"updateAt": "2020-09-08T22:24:09.569Z"
}
- 查看数据库有写入就ok。
- 下面照葫芦画瓢,把posts、role表创建好,users和posts是一对多,users和role是多对多的关系
- 2个模块使用命令创建三件套,新建其实体。
- posts实体抄上次的,role抄上次tag的。
@ManyToOne(
() => UserEntity,
user => user.posts,
)
user: UserEntity;
@ManyToMany(
() => UserEntity,
user => user.roles,
)
@JoinTable({ name: 'role_user' })
users: UserEntity[];
@OneToMany(
() => Posts,
post => post.user,
)
posts: Posts[];
@ManyToMany(
() => Roles,
role => role.users,
)
roles: Roles[];
- 实体制作完成,下面制作服务与控制器,别忘了引入实体到Module上。
- 服务与控制器和user的写法基本一致,值得注意的就是外键保存的话需要注入额外的服务拿到Repository,我终于知道为啥service需要分离了,真是不写不知道。
- 还需要写修改和删除,我就简单写写,无非就是传参问题。
@Put()
async changePassw(@Body() data: UserEntityDataType) {
if (data.id !== undefined && data.password !== undefined) {
return await this.userService.changePassword(data.id, data.password);
} else {
return 'you need to pass id and password';
}
}
@Delete()
async delUser(@Body() data: UserEntityDataType) {
return this.userService.delUser(data.id);
}
- 服务就这样:
async changePassword(id: number, newPassword: string): Promise<UserEntity> {
const user = await this.userRepository.findOne(id);
user.password = newPassword;
return await this.userRepository.save(user);
}
async delUser(id: number): Promise<UserEntity> {
const user = await this.userRepository.findOne(id);
return await this.userRepository.remove(user);
}
}
- 这样就完成了
- 开启服务试试效果。这里我已经测试过ok。
事务
- 这里我们对创建role使用事物,创建一个role同时,分配给指定user 。
- 关于多对多以及事务,这里有几个坑。
- 首先多对多是有个中间表,也就是user或者role其中一个没加上对方,都会完全添加不上。
- 第二个坑就是查询user时,user的表中role字段是不显示的,只有加入relation才显示,自己指定是无效的。
- 第三个坑就是事务操作必须manager从头干到尾,不能使用repo进行保存,否则repo一旦save,必然存进数据库,而manager如果发现之中有错误,就算是save完再error,也能回滚。
- 这里使用role的服务进行创建的transition操作:
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Roles } from './roles.entity';
import { Repository, EntityManager } from 'typeorm';
import { UserEntity } from 'src/user/user.entity';
import { RoleCreateDataType } from './roles.controller';
import { isArray } from 'util';
@Injectable()
export class RolesService {
constructor(
@InjectRepository(Roles)
private readonly roleRepository: Repository<Roles>,
) {}
async create(
data: RoleCreateDataType,
manager: EntityManager,
): Promise<string> {
const roles = new Roles();
roles.name = data.role.name;
const user = await manager
.getRepository(UserEntity)
.findOne(data.user.id, { relations: ['roles'] });
console.log(user.roles);
//找到的user里加入role
if (isArray(user.roles)) {
user.roles.push(roles);
} else {
user.roles = [roles];
}
roles.users = [user];
console.log(user);
await manager.save(user);
await manager.save(roles);
return 'ok';
}
async getList(): Promise<Roles[]> {
return await this.roleRepository.find();
}
}
- 控制器使用transaction:
export type RoleCreateDataType = {
user: UserEntityDataType;
role: RolesTypes;
};
@Controller('roles')
export class RolesController {
constructor(private readonly roleService: RolesService) {}
@Post()
@Transaction()
async createUser(
@Body()
data: RoleCreateDataType,
@TransactionManager() manager: EntityManager,
): Promise<string> {
return await this.roleService.create(data, manager);
}
@Get()
async userList(): Promise<Roles[]> {
return await this.roleService.getList();
}
}