【Nest全栈之旅】第八章:Nest快速上手操作MySQL

前言

在Nest中,一个http请求首先会通过Controller控制器层进行参数验证或转换,再调用Service服务层处理复杂的处理逻辑,与数据库交互进行持久化操作,这节我们对MySQL单表进行CRUD操作,实现一个用户管理功能。

MySQL数据库+Typeorm

首先对于前端同学比较少接触的MySQL而言,需要提前安装适合本机的MySQL版本。

MySQL地址

先启动MySQL服务,使用命令行执行sql语句来操作数据库

也可以使用更方便的MySQL可视化工具navicate操作表数据,这里使用的是navicate,也可选择白嫖的Sequel Pro

navicate地址

在后端体系中,NestJava等语言都可以直接操作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里面有属于自己的ControllersServices,不同的Services可以通过exportsimports进行导出导入实现服务共享。

我们新建一个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对象再来拿到里面的属性。

createUserDtoUpdateUserDto是用于声明并验证请求体(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()装饰器进行修饰,它与filterinterceptor等共同被称为Provider提供者,在这个例子中,Controller可以看做消费者,消费的是由Provider提供的服务或者其他模块。

完善Service部分的逻辑,需要使用到与Nest集成的@nestjs/typeorm包,需单独安装。

它提供了 TypeOrmModule模块,在App.module.ts中引入,它有两个静态方法 forRootforFeature

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了,在此之前需要安装typeormmysql

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全栈开发了。

相关推荐
东风西巷18 分钟前
PDFgear:免费全能的PDF处理工具
前端·pdf·软件需求
小沈同学呀25 分钟前
创建一个Spring Boot Starter风格的Basic认证SDK
java·spring boot·后端
森之鸟1 小时前
Mac电脑上如何打印出字体图标
前端·javascript·macos
mCell1 小时前
GSAP 入门指南
前端·javascript·动效
gnip2 小时前
组件循环引用依赖问题处理
前端·javascript
方圆想当图灵2 小时前
如何让百万 QPS 下的服务更高效?
分布式·后端
凤山老林3 小时前
SpringBoot 轻量级一站式日志可视化与JVM监控
jvm·spring boot·后端
凡梦千华3 小时前
Django时区感知
后端·python·django
Aotman_3 小时前
el-input textarea 禁止输入中文字符,@input特殊字符实时替换,光标位置保持不变
前端·javascript·vue.js·前端框架·es6
Nan_Shu_6143 小时前
Web前端面试题(1)
前端·面试·职场和发展