Nest项目(三)-连接 MySQL 数据库并渲染页面
前言
到目前位置服务已经满足了如下功能:
- 启动一个服务,端口为 3000
- 可以处理普通 get 请求,并返回 接口数据
- 可以访问静态 js | css | html | img 资源
- 可以通过接口访问经过 ejs 编译后的 html
在第一篇中已经说明,这个示例项目首先要做到服务端渲染一个网站的效果。而到目前为止已经实现页面的渲染,但是还是不够完美,有些少量的数据是可以放在 接口返回体里面的,但是更多的数据是存在其他地方的:json、excel、数据库。
既然看到了 json、excel 都是通过解析文件的方式实现的,就不进行着重记录了。
本篇记录一下如何连接 MySQL 数据库,并获取数据,然后给 ejs 进行渲染。
配置流程
一、安装依赖
$ pnpm install --save @nestjs/typeorm typeorm mysql2
- mysql2: 用于
node.js
来操作mysql
的库; - typeorm:一个
orm
库,可以理解为把对象和表做了一个映射关系,来方便操作; - @nestjs/typeorm:
nestjs
基于typeorm
的封装。
通过上面的安装,我们就在nestjs
集成了mysql
库并使用typeorm
来操作。
当然前提条件是有个数据库供我们访问,如果没有的话可以安装个 phpstudy,虽然是叫 phpstudy,但是我们这里只用到它的 MySQL 相关的功能,对于新手来说比较友好。如果有现成的远程仓库也可以连接远程仓库。
二、项目配置
1. 直接使用 mysql2 链接数据库
这个方案其实是因为我没找到如何在不声明 entity 的场景下去直接查询数据库,主要是用在比较简单且需要迅速实现一个简单的逻辑的场景下。
还有一个场景就是,在现有的数据库,不太方便去声明 entity 时候,要操作数据库。因为之前使用过 mysql2 就把这个方法放在了这里。建议还是使用官方推荐的方法。
具体实现方式:
因为 mysql2 默认的方式是回调方式调用,想在现在的接口中返回查询结果就要封装成一个 promise 函数,同时也能提高代码的可复用性。
然后在接口内部调用,为了演示方便我放在了一个文件里面,正常情况下,应该是单独提到个公共的地方。
首先要先增加一个接口 getUsers,这个在前面已经增加 ejs 的时候已经展示了。
这里就只放 具体 service 部分的逻辑。
// database.ts
export const CONF_MYAQL2: ConnectionOptions = {
host: 'localhost',
user: 'root',
password: 'root',
database: 'nest-test',
timezone: 'Z',
charset: 'utf8',
};
// app.service.ts
import { Injectable } from '@nestjs/common';
import { CONF_MYAQL2 } from './config/database'; // mysql2 连接信息
import mysql from 'mysql2';
// 封装 查询 Promise 方法
const queryPromise = (query) =>
new Promise<any>((resolve, reject) => {
const connection = mysql.createConnection(CONF_MYAQL2);
connection.connect();
connection.query(query, (err, results) => {
if (err) {
reject(err);
throw err;
}
return resolve(results);
});
connection.end();
});
@Injectable()
export class AppService {
getHello(): string {
return 'Hello World!';
}
// 新增 getIsers 方法,被 /users 调用
async getUsers(): Promise<any> {
// 执行 sql 语句
const result = await queryPromise('select * from user1;');
// 返回执行结果
return result;
}
}
数据如下:
然后浏览器访问 http://localhost:3000/users ,即可看到查询结果。
2. 使用官方推荐的方式连接 MySQL 数据库
连接数据库很简单,做如下配置就好:
// app.module.ts
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { TypeOrmModule } from '@nestjs/typeorm';
@Module({
imports: [
// 使用 typeorm 链接数据库
TypeOrmModule.forRoot({
type: 'mysql',
host: 'localhost',
port: 3306,
username: 'root',
password: 'root',
timezone: 'Z', // MySQL 服务器上配置的时区。这用于将服务器日期/时间值强制转换为 JavaScript Date 对象,反之亦然。该值可以是`local`,`Z`或`+HHMM`或`-HHMM`形式的偏移。 (默认:`local`)
database: 'nest',
entities: ['dist/modules/**/*.entity{.ts,.js}'],
synchronize: true,
dateStrings: true,
}),
],
controllers: [AppController],
providers: [AppService],
})
export class AppModule {}
2.1 创建User模块相关文件
nest 内置了脚手架,可以通过nest相关命令创建一个新的模块。为了使项目更整洁,在 src 下创建 modules 用来存放所有的模块。
$ nest g mo modules/user && nest g co modules/user && nest g service modules/user
---
CREATE src/modules/user/user.module.ts (81 bytes)
UPDATE src/app.module.ts (499 bytes)
CREATE src/modules/user/user.controller.spec.ts (478 bytes)
CREATE src/modules/user/user.controller.ts (97 bytes)
UPDATE src/modules/user/user.module.ts (166 bytes)
CREATE src/modules/user/user.service.spec.ts (446 bytes)
CREATE src/modules/user/user.service.ts (88 bytes)
UPDATE src/modules/user/user.module.ts (240 bytes)
如上所示,就在项目中创建了一个新的模块 user,并且和 app 一样,它也是拥有三个核心文件 module/controller/service,并且 nest 脚手架已经同步更新了 user.module.ts ,将 controller 和 service 加载了进去。
2.2 声明并使用 User Entity
在user模块中创建 user.entity.ts:
import {
Entity,
PrimaryGeneratedColumn,
Column,
VersionColumn,
CreateDateColumn,
UpdateDateColumn,
} from 'typeorm';
export enum Sex {
'女' = 0,
'男' = 1,
}
export abstract class Common {
// 主键id
@PrimaryGeneratedColumn()
id: number;
// 创建时间
@CreateDateColumn({ type: 'timestamp' })
createTime: Date;
// 更新时间
@UpdateDateColumn({ type: 'timestamp' })
updateTime: Date;
// 软删除
@Column({
default: false,
select: false,
})
isDelete: boolean;
// 更新次数
@VersionColumn({
select: false,
default: 1,
})
version: number;
}
@Entity()
export class User extends Common {
// 姓名
@Column({ length: 16 })
username: string;
// 性别
@Column()
sex: Sex;
}
如上所示,为了便于日后复用,我特意将几个通用的字段放在了 Common 中,而 User 私有的字段则继承自 Common。
而添加这个 entity 之后,查看数据库则会发现,已经创建好了一张 名为 user 的表,且字段和声明的一致。
注意,使用 typeorm 连接数据库根据 entity 重新初始化数据库,如果连接已经有数据的数据库,千万要先备份数据再操作。
2.3 操作数据库 CURD
操作数据库需要先在 module 中安装数据模型。修改如下:
// user.module.ts
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { UserController } from './user.controller';
import { UserService } from './user.service';
import { User } from './user.entity';
@Module({
imports: [TypeOrmModule.forFeature([User])] /* 安装数据模型 */,
controllers: [UserController],
providers: [UserService],
})
export class UserModule {}
然后,在 user.controller.ts 中增加两个接口,一个 get 接口返回页面,另一个 post 接口用来新增 数据:
// user.controller.ts
import { Controller, Get, Post } from '@nestjs/common';
import { UserService } from './user.service';
@Controller('user')
export class UserController {
constructor(private userService: UserService) {}
@Get('listPage')
getListPage() {
return this.userService.getListPage();
}
@Post('create')
create() {
return this.userService.create();
}
}
再在 user.service.ts 中添加操作数据库的逻辑:
// user.service.ts
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { User } from './user.entity';
@Injectable()
export class UserService {
constructor(
@InjectRepository(User)
private readonly userRepository: Repository<User>,
) {}
async getListPage() {
const userlist = this.userRepository.createQueryBuilder('user').getMany();
const list = await userlist;
return list;
}
async create() {
const user = { username: '张三', sex: 1 };
const result = await this.userRepository.save(user);
return result;
}
}
至此就可以访问这两个接口操作数据库了。现在的get接口只是查询数据并没有返回到页面上。
3. 渲染返回页面
然后我们渲染查询的结果放在page页面上。
创建 page.ejs 页面。
// page.ejs
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>用户列表</title>
<style>
table{
border-collapse: collapse;
}
th,td{
border: 1px solid #ccc;
padding: 5px 15px;
}
</style>
</head>
<body>
<h1>用户列表</h1>
<table>
<tr>
<% Object.entries(list[0]).forEach(function(item){%>
<th><%- item[0] %></th>
<% }) %>
</tr>
<% list.forEach(function(item){%>
<tr>
<% Object.entries(item).forEach(function(b){%>
<td><%- b[1] %></td>
<% }) %>
</tr>
<% }) %>
</table>
</body>
</html>
然后通过 Render 方法返回渲染出来的页面:
// user.controller.ts
import { Controller, Get, Post, Render } from '@nestjs/common';
import { UserService } from './user.service';
@Controller('user')
export class UserController {
constructor(private userService: UserService) {}
@Get('listPage')
@Render('page') /* 渲染page页面 */
getListPage() {
return this.userService.getListPage();
}
@Post('create')
create() {
return this.userService.create();
}
}
浏览器访问:http://localhost:3000/user/listPage 看到如下页面:
rest 介绍
介绍一个vscode插件 REST,可以在编辑器中发起接口请求,用来测试 接口 很方便。
安装
如图所示安装即可。
编写接口请求
创建 user.rest 文件并编写如下内容:
@host = http://localhost
@port = 3000
@contentType = application/json
###
# 获取列表
GET {{host}}:{{port}}/users HTTP/1.1
content-type: {{contentType}}
###
# 获取列表页面
GET {{host}}:{{port}}/user/listPage HTTP/1.1
content-type: {{contentType}}
###
# 添加一条数据
POST {{host}}:{{port}}/user/create HTTP/1.1
content-type: {{contentType}}
发起请求
发起请求的方法方法更容易了:
点击 Send Request 即可,会在右面看到请求结果。
下一步计划
前后端分离,通过 ajax 的方式来请求数据,并进行页面渲染和交互,并统一接口返回体。
参考文档
转载自:https://juejin.cn/post/7171992098332213284