那为什么我们还要去写 Node 做事? 紧张是方便快捷,对付小项目可以迅速完成培植,开拓本钱小。 其次,紧张通过写 Nest 完成下面收成:
学习装饰器语法,感想熏染其简洁幽美;自己学习一门新的开拓框架,感想熏染不同框架的优缺陷,为往后开拓选型打根本;感想熏染做事端排查问题的繁芜性,找找前端设计的灵感。本篇文章紧张是利用 NestJs + Sequelize + MySQL 完成根本运行, 带大家理解 Node 做事真个根本搭建,也可以顺便看看 Java SpringBoot 项目的根本构造,它俩真的非常相似,不信你去问做事端开拓同学。
养成好习气,看文章先一键三连~【点赞,关注,转发】,评论可以看完再吐槽~连续完善填坑~

在选择做事真个时候,我之前利用过 Egg.js ,以是这次就不选它了。其次,Egg 也是继续了 Koa 的开拓根本,加上 Express 也是基于 Koa 上创新的,两者该当差不多,就不选择 Koa 和 Express 。
以是,我想考试测验下 Nest.js 看语法跟 Java 是一样的,加上之前也自己开拓过 Java + SpringBoot 的项目,当然更古老的 SSH 2.0 也从无到有搭建过,即:Spring2.0 + Struts2+ Hibernate3.2,想想该当会很随意马虎上手,顺便怀旧下写写。
参考文档:
https://www.geeksforgeeks.org/best-nodejs-frameworks-for-app-development/https://anywhere.epam.com/business/best-node-js-frameworks说下我的想法,首先我们刚入门,估计会有一堆不清楚的坑,我们先大略点,后续我们再连续加深。既然要搞做事端,要搞就多搞点,我们都去尝鲜玩玩。我们打算利用 Nest 作为前端框架,Graphql 作为中间处理层。底层数据库我们用传统的 MySQL,比较稳定可靠,而且相比拟较熟习,这个就不玩新的了,毕竟数据库是统统的基石 。
说下我们详细实现步骤:
1.【必须】没有任何数据库,完成接口要求运行,能够跑起来;
2.【必须】创建根本数据库 MySQL ,接入 @nestjs/sequelize 库 完成 增编削查 功能即:CRUD
3.【可选】打算采纳 Graphql 处理 API 查询,做到精确数据查询,这个已经火了很多了,但是真正利用的很少,我们打算先感想熏染下,后续可以直接用到业务。
4.【可选】接入 Swagger 自动天生 API 文档,快捷进行前端与后端做事联调测试。
◦Swagger是一个开源工具,用于设计、构建、记录和利用RESTful web做事。
5.【可选】接口要求,数据库优化处理
◦要求分流,数据库写入加锁,处理并发流程
◦增加 middleware 中间件统一处理要求及相应,进行鉴权处理,要求拦截等操作
◦数据库分割备份,数据库融灾处理,分为:主、备、灾
◦数据库读写分离,数据双写,建立数据库缓存机制,利用 redis 处理
也欢迎大家补充更多的优化点,我们一起磋商~有兴趣可以帮忙补充代码哈~
确定了大概方向,我们就开始整。先不追求一步到位,否则越多越乱,锦上添花的东西,我们可往后续增加,根本功能我们要优先保障完成。Nest.js 官网:https://docs.nestjs.com/ ,话不多说,我们直接开整。
# 进入文件夹目录cd full-stack-demo/packages# 安装脚手架npm i -g @nestjs/cli# 创建根本项目nest new node-server-demo # 进入项目 cd new node-server-demo # 运行项目测试npm run start:dev
我们移除一些不须要的东西,先大略再繁芜,别把自己搞晕了。接下来写一个大略示例感想熏染下这个框架,之后完全的代码,我会公布在后面。废话不多说,开整!
调度后目录构造:
•common - 公用方法类
•config - 配置类文件
•controller - 掌握器,用于处理前端发起的各种要求
•service - 做事类,用于处理与数据库交互逻辑
•dto - DTO(Data Transfer Object)可以用于验证输入数据、限定传输的字段或格式。
•entities - 实体类,用于描述工具干系的属性信息
•module - 模块,用于注册所有的做事类、掌握器类,类似 Spring 里面的 bean
◦这里不能完备等同哈,两个实现机制上就不同,只是帮助大家理解。
•main.ts - nest 启动入口
•types - typescript 干系声明类型
只是写 demo, 搞快点就没有怎么写注释了,我觉得是一看就懂了,跟 Java SpringBoot 的写法非常同等,部分代码展示:
•掌握器 controller
// packages/node-server-demo/src/controller/user/index.tsimport { Controller, Get, Query } from '@nestjs/common';import UserServices from '@/service/user';import { GetUserDto, GetUserInfoDto } from '@/dto/user';@Controller('user')export class UserController { constructor(private readonly userService: UserServices) {} // Get 要求 user/name?name=bricechou @Get('name') async findByName(@Query() getUserDto: GetUserDto) { return this.userService.read.findByName(getUserDto.name); } // Get 要求 user/info?id=123 @Get('info') async findById(@Query() getUserInfoDto: GetUserInfoDto) { const user = await this.userService.read.findById(getUserInfoDto.id); return { gender: user.gender, job: user.job }; }}
// packages/node-server-demo/src/controller/log/add.tsimport { Controller, Post, Body } from '@nestjs/common';import { AddLogDto } from '@/dto/log';import LogServices from '@/service/log';@Controller('log')export class CreateLogController { constructor(private readonly logServices: LogServices) {} // post('/log/add') @Post('add') create(@Body() createLogDto: AddLogDto) { return this.logServices.create.create(createLogDto); }}
•数据转换 Data Transfer Object
// packages/node-server-demo/src/dto/user.tsexport class CreateUserDto { name: string; age: number; gender: string; job: string;}// 可以分开写,也可以合并export class GetUserDto { id?: number; name: string;}// 可以分开写,也可以合并export class GetUserInfoDto { id: number;}
•service 数据库交互处理类
// packages/node-server-demo/src/service/user/read.tsimport { Injectable } from '@nestjs/common';import { User } from '@/entities/User';@Injectable()export class ReadUserService { constructor() {} async findByName(name: string): Promise<User> { // 可以处理判空,从数据库读取/写入数据,可能会被多个 controller 进行调用 console.info('ReadUserService findByName > ', name); return Promise.resolve({ id: 1, name, job: '程序员', gender: 1, age: 18 }); } async findById(id: number): Promise<User> { console.info('ReadUserService findById > ', id); return Promise.resolve({ id: 1, name: 'BriceChou', job: '程序员', gender: 1, age: 18, }); }}
•module 模块注册,做事类/掌握类
// packages/node-server-demo/src/module/user.tsimport { Module } from '@nestjs/common';import UserService, { ReadUserService } from '@/service/user';import { UserController } from '@/controller/user';@Module({ providers: [UserService, ReadUserService], controllers: [UserController],})export class UserModule {}
// packages/node-server-demo/src/module/index.ts 根模块注入import { Module } from '@nestjs/common';import { UserModule } from './user';import { LogModule } from './log';@Module({ imports: [ UserModule, LogModule, ],})export class AppModule {}
•main.js 启动注册的所有类
// packages/node-server-demo/src/main.tsimport { AppModule } from '@/module';import { NestFactory } from '@nestjs/core';import { NestExpressApplication } from '@nestjs/platform-express';async function bootstrap() { const app = await NestFactory.create<NestExpressApplication>(AppModule); // 监听端口 3000 await app.listen(3000);}bootstrap();
这样一个单机的做事器就启动起来了,我们可以利用 Postwoman [https://hoppscotch.io/] 进行要求,瞅瞅看返回效果。
掌握台也收到日志了,后面可以把这些日志要求保留成 .log 文件,这样要求日志也有了,完美!
下一步,我们开始连接数据库,这样就不用单机玩泥巴了~
MySQL 安装实在很大略,我电脑是 Mac 的,以是下面的截图都因此 mac 为例,先下载对应的数据库。
下载地址:https://dev.mysql.com/downloads/mysql/ 至于其他系统的,可以网上找教程,这个该当烂大街了,我就不重复搬运教程了。
•把稳:安装的数据库,一定要设置密码,连接数据库必须要有密码,否则会导致连接数据库失落败。
•MySQL 我们只安装数据库就行,熟习指令的童鞋,就直接命令行操作就行。
•不熟习的话,那就下载图形化管理工具。
◦Mysql 官方掌握台 https://dev.mysql.com/downloads/workbench/
◦Windows 也可以利用 https://www.heidisql.com/download.php?download=installer
PS:安装 workbench 时创造哀求 MacOS 13以上,我的电脑是 MacOS 12。
白白下载,以是只能 https://downloads.mysql.com/archives/workbench/ 从归档里面找低版本 8.0.31。对付数据库做事也有版本哀求,大家按照自己电脑版本,选择支持的版本即可。 https://downloads.mysql.com/archives/community/。我这边选择的是默认最新版本:8.0.34,下载好直接安装,一起 Next 到底,记住自己输入的 Root 密码!
!
!
确认好当前数据库是否已经运行起来了,启动 Workbench 查看状态。
1.创建数据库
数据库存在字符集选择,不同的字符集和校验规则,会对存储数据产生影响,以是大家可以自行查询,按照自己存储数据原则选择,我这里默认选最广泛的。确认好,就选择右下角的运用按钮。
2.创建表和属性
选项解答:
•PRIMARY KEY 是表中的一个或多个列的组合,它用于唯一标识表中的每一行。
•Not NULL 和 Unique 就不阐明,便是直译的那个意思。
•GENERATED 天生列是表中的一种分外类型的列,它的值不是从插入语句中获取的,而是根据其他列的值通过一个表达式或函数天生的。
CREATE TABLE people ( first_name VARCHAR(100), last_name VARCHAR(100), full_name VARCHAR(200) AS (CONCAT(first_name, ' ', last_name)));
•UNSIGNED 这个数值类型就只能存储正数(包括零),不会存储负数。
•ZEROFILL 将数值类型的字段的前面添补零,他会自动使字段变为 UNSIGNED,直到该字段达到声明的长度,如:00007
•BINARY 用于存储二进制字符串,如声明一个字段为 BINARY(5),那么存储在这个字段中的字符串都将被处理为长度为 5 的二进制字符串。
◦如考试测验存储一个长度为 3 的字符串,那么它将在右侧用两个空字节添补。
◦如果你考试测验存储一个长度为 6 的字符串,那么它将被截断为长度为 5
◦紧张用场是存储那些须要按字节进行比较的数据,例如加密哈希值
•此外也可顺手传创建一个索引,方便快速查找。
CREATE TABLE `rrweb`.`test_sys_req_log` ( `id` INT UNSIGNED NOT NULL AUTO_INCREMENT, `content` TEXT NOT NULL, `l_level` INT UNSIGNED NOT NULL, `l_category` VARCHAR(255) NOT NULL, `l_created_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, `l_updated_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`id`), UNIQUE INDEX `id_UNIQUE` (`id` ASC) VISIBLE, INDEX `table_index` (`l_level` ASC, `l_category` ASC, `l_time` ASC) VISIBLE);
3.连接数据库
由于目前 node-oracledb 官方尚未供应针对 Apple Silicon 架构的预编译二进制文件。导致我们无法在 Mac M1 芯片上利用 TypeORM 链接数据库操作,它目前只支持 Mac x86 芯片。哎~折腾老半天,查阅各种文档,居然有这个坑,没紧要我们换个办法打开。
我们不得不放弃,从而选用 https://docs.nestjs.com/techniques/database#sequelize-integration 哐哐哐~一顿操作猛如虎,盘它!
•安装 Sequelize
# 安装连接库npm install --save @nestjs/sequelize sequelize sequelize-typescript mysql2# 安装 typenpm install --save-dev @types/sequelize
•配置数据库根本信息
// packages/node-server-demo/src/module/index.tsimport { Module } from '@nestjs/common';import { UserModule } from './user';import { LogModule } from './log';import { Log } from '@/entities/Log';import { SequelizeModule } from '@nestjs/sequelize';@Module({ imports: [ SequelizeModule.forRoot({ dialect: 'mysql', // 按数据库实际配置 host: '127.0.0.1', // 按数据库实际配置 port: 3306, // 按数据库实际配置 username: 'root', // 按数据库实际配置 password: 'hello', // 按数据库实际配置 database: 'world', synchronize: true, models: [Log], autoLoadModels: true, }), LogModule, UserModule, ],})export class AppModule {}
•实体与数据库逐一映射处理
import { getNow } from '@/common/date';import { Model, Table, Column, PrimaryKey, DataType,} from 'sequelize-typescript';@Table({ tableName: 'test_sys_req_log' })export class Log extends Model<Log> { @PrimaryKey @Column({ type: DataType.INTEGER, autoIncrement: true, field: 'id', }) id: number; @Column({ field: 'content', type: DataType.TEXT }) content: string; @Column({ field: 'l_level', type: DataType.INTEGER }) level: number; // 3严重,2危险,1轻微 @Column({ field: 'l_category' }) category: string; // 模块分类/来源分类 @Column({ field: 'l_created_at', type: DataType.NOW, defaultValue: getNow(), }) createdAt: number; @Column({ field: 'l_updated_at', type: DataType.NOW, defaultValue: getNow(), }) updatedAt: number;}
•module 注册实体
// packages/node-server-demo/src/module/log.tsimport { Module } from '@nestjs/common';import { SequelizeModule } from '@nestjs/sequelize';import { Log } from '@/entities/Log';import LogServices, { CreateLogService, UpdateLogService, DeleteLogService, ReadLogService,} from '@/service/log';import { CreateLogController, RemoveLogController, UpdateLogController,} from '@/controller/log';@Module({ imports: [SequelizeModule.forFeature([Log])], providers: [ LogServices, CreateLogService, UpdateLogService, DeleteLogService, ReadLogService, ], controllers: [CreateLogController, RemoveLogController, UpdateLogController],})export class LogModule {}
•service 操作数据库处理数据
import { Log } from '@/entities/Log';import { Injectable } from '@nestjs/common';import { AddLogDto } from '@/dto/log';import { InjectModel } from '@nestjs/sequelize';import { ResponseStatus } from '@/types/BaseResponse';import { getErrRes, getSucVoidRes } from '@/common/response';@Injectable()export class CreateLogService { constructor( @InjectModel(Log) private logModel: typeof Log, ) {} async create(createLogDto: AddLogDto): Promise<ResponseStatus<null>> { console.info('CreateLogService create > ', createLogDto); const { level = 1, content = '', category = 'INFO' } = createLogDto || {}; const str = content.trim(); if (!str) { return getErrRes(500, '日志内容为空'); } const item = { level, category, // Tips: 为防止外部数据进行数据注入,我们可以对内容进行 encode 处理。 // content: encodeURIComponent(str), content: str, }; await this.logModel.create(item); return getSucVoidRes(); }}
一起操作猛如虎,转头一看嘿嘿嘿~终于,我们收到了来自外界的第一条数据!
hello world!
连接及创建数据成功!
此时已经完成根本功能啦~
剩下的内容,实在大家可以自行脑补了,便是调用数据库的操作逻辑。先说说什么是 CRUD
•C create 创建
•R read 读取
•U update 更新
•D delete 删除
下面给个大略示例,大家看看,剩下就去找文档,实现业务逻辑即可:
import { Injectable } from '@nestjs/common';import { InjectModel } from '@nestjs/sequelize';import { User } from './user.model';@Injectable()export class UserService { constructor( @InjectModel(User) private userModel: typeof User, ) {} // 创建新数据 async create(user: User) { const newUser = await this.userModel.create(user); return newUser; } // 查找所有数据 async findAll() { return this.userModel.findAll(); } // 按哀求查找单个 async findOne(id: string) { return this.userModel.findOne({ where: { id } }); } // 按哀求更新 async update(id: string, user: User) { await this.userModel.update(user, { where: { id } }); return this.userModel.findOne({ where: { id } }); } // 按哀求删除 async delete(id: string) { const user = await this.userModel.findOne({ where: { id } }); await user.destroy(); }}
Tips: 进行删除的时候,我们可以进行假删除,两个数据库,一个是备份数据库,一个是主数据库。主数据库可以直接删除或者增加标识表示删除。备份数据库,可以不用删除只写入和更新操作,这样可以进行数据还原操作。
此外,为了防止 SQL 数据库注入,大家须要对数据来源进行统一校验处理或者直接进行 encode 处理,对付主要数据可以直接进行 MD5 加密处理,防止数据库被直接下载透露。关于 SQL 数据库的安全处理,网上教程有很多,大家找一找就可以啦~
支配就比较大略了,我们就不须要逐一赘述了,数据库可以用集团供应的云数据库,而 Nest 便是普通的 node 支配。
作者:京东零售 周通亮
来源:京东云开拓者社区 转载请注明来源