记录使用 Ejs + Nest + Mysql 在服务端渲染模板
这篇文章将记录下使用Ejs + Nest + Mysql 在服务端渲染模板。
Ejs 是利用普通的 JavaScript 代码生成 HTML 页面的框架,它简单易懂,学习成本比较低,还可以引用公共的Layout。可以去中文官网查看,网址为:ejs.bootcss.com/ 。
Nest,众所周知,是 Node.js 服务器端应用程序的开发框架,是一款可以创建企业级应用的框架,相对而言,比较轻量级的可以用Express、Egg、Koa等,当然据网友们说,可以和Nest相提并论的有Midway,本人没用过Midway,所以不好下结论,各位可以去尝试下。
Mysql,这个接触过后端的大家都知道,是数据库,就不多做介绍了。
首先,先安装好有关的环境和依赖,提示:本人node版本为:18.16.0。
1、在cmd里,输入下方命令,全局安装Nest 脚手架。
新建:npm install -g @nestjs/cli
更新:npm update -g @nestjs/cli
2、在cmd里,输入下方命令,新建Nest项目,本人案例项目名为demo,注意新建项目的文件路径。(大家最好看看要不要切换npm源,本人这用原npm源有点慢,新建了快10分钟,当然也可能是网络问题)
nest new demo
3、在demo项目根目录里,打开cmd,输入下方命令安装Ejs。
npm install ejs
4、安装platform-express,照例,在命令行输入下方命令。
npm install @nestjs/platform-express
5、安装typeorm操作数据库,typeorm需要详细看官网文档去学习配置,这里不做详细介绍,照例,在命令行输入下方命令。
npm install @nestjs/typeorm
6、安装Mysql,输入下方命令安装
npm install mysql2
现在,所需的依赖都安装好了,万事具备只差数据库了,本地创建数据库我用的是phpStudy,当然大家也可以用其它工具,数据库管理用的是Navicat Premium。
新建完即可用phpStudy的phpMyAdmin,连接数据库并管理相关数据库。
当然,也可以用Navicat Premium去连接并管理
在demo项目根目录里新建一个模块,Nest有个方便的地方,就是有相关命令来新建模块,输入命令:nest generate resource news,它会把news的CRUD + REST api 的代码生成出来,非常的方便。
REST API的代码已经生成,那么先把它晾在一边,先把Ejs用起来。
在项目根目录下创建public和views文件夹,在src入口的main.ts,配置模板引擎,这样就配置的差不多了,代码如下:
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
//引入
import { NestExpressApplication } from '@nestjs/platform-express'
async function bootstrap() {
const app = await NestFactory.create<NestExpressApplication>(AppModule);
// 配置静态资源目录
// app.useStaticAssets('public', {
// prefix: '/static/'
// })
// 模板引擎配置
app.useStaticAssets('public'); //静态文件
app.setBaseViewsDir('views'); //模板引擎目录
app.setViewEngine('ejs') //模板渲染引擎
app.engine('html', require('ejs').renderFile); //改为html文件,不想改为html文件可以不用加这句,后缀则为 .ejs
await app.listen(3000);
}
bootstrap();
目录如下:public/static/images/目录里我放了一张2222的静态图片,我这里是在views文件夹里提前新建好了公共模块header.html,和default里的index.html和news.html。
现在我们启动项目看看,在vsCode的项目终端输入命令npm run start:dev,在文件目录里输入命令nest start:dev都可以,启动成功后,在浏览器输入localhost:3000即可访问。
好,来试试用index.html如何输出静态文字,打开src目录下的app.controller文件,在引入Render渲染,在@Render里,引入静态模板,接收的数据类型改为Object,因为等会return出来的是Object类型,代码如下:
import { Controller, Get, Render, Param } from '@nestjs/common'; // 引入Render
import { AppService } from './app.service';
@Controller()
export class AppController {
constructor(private readonly appService: AppService) {} // 引入 AppService
@Get()
@Render('default/index.html') // 使用Render方法引入静态模板
getHello(): Object { // 收的数据类型改为Object
return this.appService.getHello(); // 调用app.service的getHello方法
}
// 自定义 getVersion 方法:
@Get('/version')
getVersion(): Object {
return this.appService.getVersion();
}
}
这个时候再打开app.service,这个文件能看到在controller层使用的getHello方法,改为代码如下,因为return的是Object类型,所以方法定义要改为是Object,我这里写代码的顺序是反了的,应该是先写getHello方法里代码,再写controller层的代码,不过这不碍事,只是小习惯不好
import { Injectable } from '@nestjs/common';
@Injectable()
export class AppService {
getHello(): Object {
let returnBody = { name: "张三", img: "/static/images/2222.jpg", nav: [{id: '1', title:'导航1'}, {id: '2', title:'导航2'}]};
return returnBody;
}
// 自定义:获取版本
getVersion(): Object {
return {
code: 200,
msg: "",
data: {
version:"0.0.1"
}
}
}
}
接下来就是在index.html写,从上面的代码里,输出了对象,对象里包含name、img、nav三个参,所以在index.html写的代码如下,引入了公共模块header.html,给header.html传入参数nav。
index.html
<!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><%=name%></title>
</head>
<body>
<%- include('../public/header.html', { nav: nav }); -%>
<h3>首页模板引擎</h3>
<p>hello, <%=name%>, </p>
<img src="<%=img%>" alt="">
</body>
</html>
header.html
<div>
我是头部公共模块 <%=name%> 下面是从index.html传过来的参数nav数组
<% for(var i=0; i<nav.length; i++) {%>
<div key="<%= nav[i].id %>">
<%= nav[i].title %> <br>
</div>
<% } %>
</div>
刷新页面即可看到刚刚代码输出的静态内容
这个时候来渲染从数据库获取的数据,用刚刚CRUD生成的news模块来输出,打开src目录下的app.module文件,引入TypeOrm,链接数据库,代码如下:
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { TypeOrmModule } from '@nestjs/typeorm';
import { UserModule } from './user/user.module';
import { NewsModule } from './news/news.module';
import { User } from './user/entities/user.entity';
import { News } from './news/entities/news.entity';
@Module({
imports: [UserModule, NewsModule,
TypeOrmModule.forRoot({
type: "mysql",
host: "localhost", // 数据库地址
port: 3306,
username: "用户名",
password: "数据库密码",
database: "数据库名称",
synchronize: true,
logging: true,
entities: [User, News], // 需要使用数据库的模块实体
poolSize: 10, // 最大链接数
connectorPackage: 'mysql2',
extra: {
authPlugin: 'sha256_password',
}
}),],
controllers: [AppController],
providers: [AppService],
})
export class AppModule {}
再打开刚刚CRUD生成的news模块目录,打开里面的news.service,里面有个findAll的方法,写入代码如下:(需要注意的是要引入news实体和typeorm,并使用@InjectEntityManager,把EntityManager注入)
import { Injectable } from '@nestjs/common';
import { InjectEntityManager } from '@nestjs/typeorm'; // typeorm实体管理
import { EntityManager } from 'typeorm'; // typeorm实体管理方法
import { CreateNewsDto } from './dto/create-news.dto';
import { UpdateNewsDto } from './dto/update-news.dto';
import { News } from './entities/news.entity'; // news实体
@Injectable()
export class NewsService {
@InjectEntityManager()
private manager: EntityManager; // 注入EntityManager
create(createNewsDto: CreateNewsDto) {
this.manager.save(News, createNewsDto);
}
async findAll() {
const [users, count] = await this.manager.findAndCount(News); // 调用EntityManager的方法
let data = {
rows: users, // rows数组
total: count // 总共几条
}
return data;
}
findOne(id: number) {
return `This action returns a #${id} news`;
}
update(id: number, updateNewsDto: UpdateNewsDto) {
return `This action updates a #${id} news`;
}
remove(id: number) {
return `This action removes a #${id} news`;
}
}
typeorm具体的 EntityManager的方法大概有这些,我用的是findAndCount这个方法,可去看typeorm相关文档
- save:新增或者修改 Entity,如果传入了 id 会先 select 再决定修改还新增
- update:直接修改 Entity,不会先 select
- insert:直接插入 Entity
- delete:删除 Entity,通过 id
- remove:删除 Entity,通过对象
- find:查找多条记录,可以指定 where、order by 等条件
- findBy:查找多条记录,第二个参数直接指定 where 条件,更简便一点
- findAndCount:查找多条记录,并返回总数量
- findAndCountBy:根据条件查找多条记录,并返回总数量
- findOne:查找单条记录,可以指定 where、order by 等条件
- findOneBy:查找单条记录,第二个参数直接指定 where 条件,更简便一点
- findOneOrFail:查找失败会抛 EntityNotFoundError 的异常
- query:直接执行 sql 语句
- createQueryBuilder:创建复杂 sql 语句,比如 join 多个 Entity 的查询
- transaction:包裹一层事务的 sql
- getRepository:拿到对单个 Entity 操作的类,方法同 EntityManager
再打开news.controller文件,写入代码如下:
import { Controller, Get, Post, Body, Patch, Param, Delete, Render } from '@nestjs/common';
import { NewsService } from './news.service';
import { CreateNewsDto } from './dto/create-news.dto';
import { UpdateNewsDto } from './dto/update-news.dto';
@Controller('news')
export class NewsController {
constructor(private readonly newsService: NewsService) {} // 引入 NewsService
@Post()
async create(@Body() createNewsDto: CreateNewsDto) {
await this.newsService.create(createNewsDto);
let returnBody = {
code: 200,
message: '创建成功',
data: true
}
return returnBody
}
@Get() // 页面链接带参数请在这里定义,形成伪静态
@Render('default/news.html') // 注意引入模板引擎!
findAll() {
return this.newsService.findAll(); // 调用service层的方法
}
@Get(':id')
findOne(@Param('id') id: string) {
return this.newsService.findOne(+id);
}
@Patch(':id')
update(@Param('id') id: string, @Body() updateNewsDto: UpdateNewsDto) {
return this.newsService.update(+id, updateNewsDto);
}
@Delete(':id')
remove(@Param('id') id: string) {
return this.newsService.remove(+id);
}
}
在刚才新建好的views/default/news.html写入代码,代码如下:
<!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>
</head>
<body>
总共有新闻<%=total%>条
<br>
----------------------------------------
<br>
<% rows.forEach(function(item) {%>
<div class="cece">
<%= item.title %> <br>
<%= item.descption %>
</div>
<% }); %>
</body>
</html>
然后打开localhost:3000/news,打开的页面如下图:
再打开数据库,看看数据对不对,
渲染的视图数据和数据库的数据是一模一样的,那么就是正常了。
本文记录到这里,大家一起共勉学习进步。
转载自:https://juejin.cn/post/7280740613891424275