前言
在Nest中,一个http请求首先会通过Controller
控制器层进行参数验证或转换,再调用Service
服务层处理复杂的处理逻辑,与数据库交互进行持久化操作,这节我们对MySQL
单表进行CRUD
操作,实现一个用户管理功能。
MySQL数据库+Typeorm
首先对于前端同学比较少接触的MySQL
而言,需要提前安装适合本机的MySQL
版本。
先启动MySQL
服务,使用命令行执行sql
语句来操作数据库
也可以使用更方便的MySQL
可视化工具navicate
操作表数据,这里使用的是navicate
,也可选择白嫖的Sequel Pro
。
在后端体系中,Nest
或Java
等语言都可以直接操作Sql
语句执行CRUD
,但是频繁写Sql
语句太麻烦了,能不能像前端一样直接操作数据对象,对属性进行读写,自动同步到视图中。
这就要引入ORM
概念了,即对象关系映射
。ORM
通过将数据库中的表、行、列映射为程序中的对象、属性和关联使用面向对象的编程方式来操作数据库,而无需操作SQL
语句。
其实还有一个类似的概念,叫ODM
,即对象文档映射
,它是针对文档型数据库(NoSql)的,比如MongoDB
这一类,但理念是跟ORM
一样的。
再回来,Typeorm
就是实现ODM
理念的框架,通过装饰器的写法来描述数据库字段的映射关系。
声明对应字段之后,我们操作数据对象时,Typeorm
自动会执行内部的SQL
语句来同步修改数据库。
了解完如何进行数据库操作,我们创建一个项目来实现一下。
Nest.js + Typeorm
使用CLI
新建一个项目:nest n nest-mysql -p pnpm
,默认选用pnpm
包管理工具。
接着把它启动起来:pnpm run start:dev
Nest
提供了模块化的结构来管理不同的功能,每个Module
里面有属于自己的Controllers
和Services
,不同的Services
可以通过exports
和imports
进行导出导入实现服务共享。
我们新建一个User
模块,使用nest g resource User
,选择REST
风格API和生成CRUD
入口点。
具体来看看Controller
中代码
typescript
// user.controller.ts
import { Controller, Get, Post, Body, Patch, Param, Delete } from '@nestjs/common';
import { UserService } from './user.service';
import { CreateUserDto } from './dto/create-user.dto';
import { UpdateUserDto } from './dto/update-user.dto';
@Controller('user')
export class UserController {
constructor(private readonly userService: UserService) {}
@Post()
create(@Body() createUserDto: CreateUserDto) {
return this.userService.create(createUserDto);
}
@Get()
findAll() {
return this.userService.findAll();
}
@Get(':id')
findOne(@Param('id') id: string) {
return this.userService.findOne(+id);
}
@Patch(':id')
update(@Param('id') id: string, @Body() updateUserDto: UpdateUserDto) {
return this.userService.update(+id, updateUserDto);
}
@Delete(':id')
remove(@Param('id') id: string) {
return this.userService.remove(+id);
}
}
通过@Controller('user')
装饰器来声明控制器,并且指定路由路径为user
,表示在这个Controller
下面的所有路由程序都属于/user
路径下的子路由。
@Post()
、@Get()
、@Patch()
、@Delete()
分别对应请求方式,它们也可以指定属于自己的路由路径,如@Post('create-user')
,最后拼接为/user/create-user
这个URL,指向create
这个路由方法。
@Param()
、@Body()
、@Query()
这些就是获取请求对象里面的参数或请求体,在Nest
中不需要操作类似express
中的@res
、@req
对象再来拿到里面的属性。
而createUserDto
和UpdateUserDto
是用于声明并验证请求体(Body)传递过来的参数。
Controller
层最终调度Service
层,来看看Service
里面的代码:
typescript
import { Injectable } from '@nestjs/common';
import { CreateUserDto } from './dto/create-user.dto';
import { UpdateUserDto } from './dto/update-user.dto';
@Injectable()
export class UserService {
create(createUserDto: CreateUserDto) {
return 'This action adds a new user';
}
findAll() {
return `This action returns all user`;
}
findOne(id: number) {
return `This action returns a #${id} user`;
}
update(id: number, updateUserDto: UpdateUserDto) {
return `This action updates a #${id} user`;
}
remove(id: number) {
return `This action removes a #${id} user`;
}
}
在这个样板代码中,Service
没有实现具体的业务逻辑,没有操作数据库,只返回了String
类型数据。
而Service
通过@Injectable()
装饰器进行修饰,它与filter
、interceptor
等共同被称为Provider
提供者,在这个例子中,Controller
可以看做消费者,消费的是由Provider
提供的服务或者其他模块。
完善Service
部分的逻辑,需要使用到与Nest
集成的@nestjs/typeorm
包,需单独安装。
它提供了 TypeOrmModule
模块,在App.module.ts
中引入,它有两个静态方法 forRoot
、forFeature
。
forRoot
用于在入口处初始化数据库连接,接收数据库相关配置信息
typescript
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: [
TypeOrmModule.forRoot({
type: 'mysql',
host: 'localhost',
port: 3306,
username: 'root',
password: '你的密码',
database: '数据库名',
synchronize: true
}),
UserModule
],
controllers: [AppController],
providers: [AppService],
})
export class AppModule {}
forFeature 用于创建不同实体类对应的 Repository
,在使用的对应 Module
里引入。 在这里我们是uesr.module.ts
typescript
import { Module } from '@nestjs/common';
import { UserService } from './user.service';
import { UserController } from './user.controller';
import { User } from './entities/user.entity';
import { TypeOrmModule } from '@nestjs/typeorm';
@Module({
imports: [
TypeOrmModule.forFeature([User])
],
controllers: [UserController],
providers: [UserService]
})
export class UserModule {}
接着需要创建User
实体user.entity
了,在此之前需要安装typeorm
、mysql
包
typescript
import { Entity, PrimaryGeneratedColumn, Column } from "typeorm";
@Entity()
export class User{
@PrimaryGeneratedColumn()
id: number;
@Column()
name: string;
@Column()
sex: string;
@Column()
createTime: Date;
@Column()
updateTime: Date;
}
再来完善一下创建用户和更新用户的dto
typescript
// create-user.dto.ts
export class CreateUserDto {
name: string;
sex: string;
createTime: Date;
updateTime: Date;
}
PartialType
表格可以更新其中的部分属性字段
typescript
// update-user.dto.ts
import { PartialType } from '@nestjs/mapped-types';
import { CreateUserDto } from './create-user.dto';
export class UpdateUserDto extends PartialType(CreateUserDto) {
name: string;
sex: string;
createTime: Date;
updateTime: Date;
}
最后在Service
中完善实体对应的操作类 Repository
,这样就完成了对用户管理的增删改查了。
typescript
import { Injectable } from '@nestjs/common';
import { CreateUserDto } from './dto/create-user.dto';
import { UpdateUserDto } from './dto/update-user.dto';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { User } from './entities/user.entity';
@Injectable()
export class UserService {
@InjectRepository(User) private userRepository: Repository<User>
async create(createUserDto: CreateUserDto) {
createUserDto.createTime = createUserDto.updateTime = new Date()
return await this.userRepository.save(createUserDto);
}
async findAll() {
return await this.userRepository.find();
}
async findOne(id: number) {
return this.userRepository.findBy;
}
async update(id: number, updateUserDto: UpdateUserDto) {
updateUserDto.updateTime = new Date()
return await this.userRepository.update(id, updateUserDto);
}
async remove(id: number) {
return await this.userRepository.delete(id);
}
}
我们先把存在的User
表删除,后面请求过来的时候会自动创建表。
后端部分已经完成,我们简单起一个前端项目来完成请求闭环,通过CRA
起一个react
项目
bash
create-react-app nest-mysql-frontend
把项目启动起来
src
目录下起一个setupProxy.js
,把代理配置一下
js
// setupProxy.js
const proxy = require('http-proxy-middleware')//引入http-proxy-middleware,react脚手架已经安装
module.exports = function(app){
app.use(
proxy.createProxyMiddleware('/api',{ //遇见/api前缀的请求,就会触发该代理配置
target:'http://localhost:3000', //请求转发给谁
changeOrigin:true,//控制服务器收到的请求头中Host的值
pathRewrite:{'^/api':''} //重写请求路径,下面有示例解释
})
)
}
src
下新建api
目录,存放请求接口
js
// index.js
import axios from 'axios'
export function createUser(data) {
return axios.post('/api/user', data);
}
export function getUserList() {
return axios.get('/api/user');
}
export function getUserById(id) {
return axios.get('/api/user/' + id);
}
export function updateUserById(id, data) {
return axios.patch('/api/user/' + id, data);
}
export function deleteUserById(id) {
return axios.delete('/api/user/' + id);
}
app.js
中简单写几个按钮触发请求
jsx
import './App.css';
import {
Button
} from 'antd'
import {
createUser,
getUserList,
updateUserById,
deleteUserById
} from './api/index'
function App() {
const handleCreateUser = async() => {
await createUser({
name: 'mouse',
sex: '男'
})
}
const handleGetUserList = async() => {
await getUserList()
}
const handleUpdateUser = async() => {
await updateUserById(1, {
sex: '人妖'
})
}
const handleDeleteUser = async() => {
await deleteUserById(1)
}
return (
<div className="App">
<Button type="default" onClick={handleCreateUser}>创建用户</Button>
<Button type="primary" onClick={handleGetUserList}>获取用户列表</Button>
<Button type='link' onClick={handleUpdateUser}>更新用户</Button>
<Button danger onClick={handleDeleteUser}>删除用户</Button>
</div>
);
}
export default App;
点击创建用户
,可以看到成功返回了数据
刷新一下,数据库中成功创建了User
表并插入了一条数据。
点击获取用户列表
,返回了刚刚插入的数据
点击更新用户
,把我的性别改为人妖
,成功更新一条数据
最后点击删除用户
,成功删除库中数据
以上,完成了Nest
操作MySQL
的前后端应用了。
总结
Nest
提供了nest g resource [name]
快速创建CRUD
+REST
风格的代码,通过DTO
格式接收Body
请求体内容,最终传递给Service
层做持久化操作。
操作Mysql
通常结合Typeorm
的包@nestjs/typeorm
来实现数据库表字段定义和对象映射操作,无需操作SQL
语句。
使用前端反向代理请求后端接口,完成前后端分离并形成闭环,基本上可以上手Nest
全栈开发了。