前言
前面分别学习了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 ( {
imports: [ ConfigModule] ,
controllers: [ UserController] ,
providers: [ UserService] ,
} )
export class UserModule { }
在控制器中,可以试着打印注入的config服务,如果能打印出来则成功:
@Controller ( 'user' )
export class UserController {
constructor ( private configService: ConfigService) {
const dbUser = this . configService. get < string > ( 'DB_TYPE' ) ;
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 ( {
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,
) {
const dbUser = this . configService. get < string > ( 'DB_TYPE' ) ;
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) ;
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 ( ) ;
}
}
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 ( ) ;
}
}