【使用Nest.js开发】nest.js实现静态目录
Nest.js是基于express实现的,express的很大一个卖点就是middleware(中间件),同样,nest.js也少不了中间件。
中间件是一个函数,它在路由处理程序之前被调用。中间件功能可以访问request和response对象,以及next应用程序请求 - 响应周期中的中间件功能
nest中的中间件就等于express中的中间件,这篇博文的目的就是使用nest.js实现一个静态目录。在express里,可以方便的实现静态目录,我们通常把需要显示的html,css,js等静态文件都放入静态目录,方便访问。现在我们着手用nest来实现一个静态目录。同样的原理还可以实现cors等功能。中间件在nest和express中的作用不可忽视。
开始
基于上一篇的例子。下面是我demo的目录树
.
|____dist
|____node_modules
|____src
| |____app.module.ts
| |____main.ts
| |____user
| |____common
| | |____middlewares
| | | |____static.middleware.ts <----
|____index.js
|____package.json
|____tsconfig.json
上面标注箭头的地方,就是我们的static中间件,用于实现静态目录
下面来看一下具体实现
src/common/middlewares/static.middleware.ts
import { Middleware, NestMiddleware, ExpressMiddleware } from "@nestjs/common";
import { Response, Request, NextFunction } from "express";
import * as path from 'path';
import * as fs from 'fs';
//封装readDir
let readDirFunc = (path:string):Promise<string[]> => {
return new Promise<string[]>((resolve, reject) => {
fs.readdir(path,(err:Error, files:string[]) => {
if(err) throw err;
resolve(files);
})
})
}
//将public作为静态目录
@Middleware()
export class StaticMiddleware implements NestMiddleware {
resolve(...args: any[]): ExpressMiddleware | Promise<ExpressMiddleware> | Promise<Promise<ExpressMiddleware>> {
return async (req:Request, res:Response, next:NextFunction) => {
let filenameArr = await readDirFunc(path.resolve('./src/public'));
let targetFile:string | undefined;
for(let filename of filenameArr){
if("/" + filename == req.url){
targetFile = req.url;
break;
}
}
if(!targetFile) next();
else {
res.header('Content-Type',"text/html");
return res.sendFile(path.resolve(`./src/public${targetFile}`))
}
}
}
}
nest中的中间件需要实现NestMiddleware接口,实现resolve方法,resolve方法返回一个ExpressMiddleware对象,实际上就是express的中间件形式了,于是在resolve函数的内部我们可以直接返回一个Express中间件形式的函数。
return (req:Request, res:Response, next:NextFunction) => {
//some logic
}
这里req,res,next的类型实际上是@types/express提供的,方便我们使用express中的对象,使用之前我们可以通过npm安装它
$ npm install --save @types/express
现在再回过头来看看上面的代码
在中间件外面,封装了node提供的readDir方法,实际上就是把readDir方法thunk化,让它返回一个promise,以便于使用async函数(并不是强制的)。
readDir函数可以读取一个文件夹,并返回此文件夹内的所有文件的文件名,因此我们可以通过readDir方法读取到一个装有某个文件夹内的所有文件的文件名的字符串数组。当然,这个需要读取的文件夹就是静态目录。通过遍历此静态目录,查找请求的文件是否在此静态文件夹内,如果存在,则返回,如果不存在,就让它交给相应的控制器处理。
实现逻辑很简单,这里就不再赘述了
let filenameArr = await readDirFunc(path.resolve('./src/public'));
let targetFile:string | undefined;
for(let filename of filenameArr){
if("/" + filename == req.url){
targetFile = req.url;
break;
}
}
if(!targetFile) next();
else {
res.header('Content-Type',"text/html");
return res.sendFile(path.resolve(`./src/public${targetFile}`))
}
如何使我们的中间件生效
我们目前创建了一个static.middleware.ts,实现了一个中间件,但是我们并没有让主模块知道这个中间件的存在。接下来做的事情就是要把中间件挂载到主模块上,如何挂载呢,其实很简单。
app.module.ts
@Module({
imports: [
UserModule
]
})
export class AppModule implements NestModule {
configure(consumer: MiddlewaresConsumer): void | MiddlewaresConsumer {
consumer
.apply(StaticMiddleware)
.forRoutes(
UserController
)
}
}
先让主模块实现NestModule接口,随后实现configure方法,调用MiddlewareConsumer的实例的apply方法把我们的中间件传入,然后链式调用forRoutes方法,传入需要应用的路由。
forRoutes方法可以传入类似{path:string, method:number}的对象,比如
{path:"index",method:RequestMethod.ALL} //捕获访问index的请求,method指定捕获用什么方法访问的请求
也可以传入多个,用逗号隔开。
还能传入如上述代码所示的控制器对象,总之是非常灵活的
我们的静态目录被指定为src目录下的public目录,现在我们创建public目录,并且在里面创建一个index.html,写上
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Tianwen2front</title>
<base href="/">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" type="image/x-icon" href="favicon.ico">
</head>
<body>
<h1>Hello Middleware</h1>
</body>
</html>
现在直接访问localhost:3000/index.html
就能看到 Hello Middleware了!!