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

相关推荐
5hand4 分钟前
Element-ui的使用教程 基于HBuilder X
前端·javascript·vue.js·elementui
GDAL21 分钟前
vue3入门教程:ref能否完全替代reactive?
前端·javascript·vue.js
六卿21 分钟前
react防止页面崩溃
前端·react.js·前端框架
追逐时光者42 分钟前
.NET 在 Visual Studio 中的高效编程技巧集
后端·.net·visual studio
z千鑫1 小时前
【前端】详解前端三大主流框架:React、Vue与Angular的比较与选择
前端·vue.js·react.js
大梦百万秋1 小时前
Spring Boot实战:构建一个简单的RESTful API
spring boot·后端·restful
m0_748256141 小时前
前端 MYTED单篇TED词汇学习功能优化
前端·学习
斌斌_____2 小时前
Spring Boot 配置文件的加载顺序
java·spring boot·后端
路在脚下@2 小时前
Spring如何处理循环依赖
java·后端·spring
小白学前端6662 小时前
React Router 深入指南:从入门到进阶
前端·react.js·react