tips

项目使用 express 和 ts

概念

我们不可能把应用里的东西都放在同一个文件里面,因为这样会让应用变得混乱不堪,所以需要一种合适的架构,将应用分割成不同的部分。每个部分里可以有自己的组织结构。在很多重型的框架里面都会提供这样的架构,比如这种框架里可能会有模块、服务、拦截器、守卫、中间件这些东西,在应用中可以创建这些东西然后把它们组织在一起。规划应用的架构可以根据功能将应用分割成不同的部分,比如说 app 表示应用,auth 是身份验证,user 是用户,post 是内容,comment 是评论等等。每个部分都可以单独的放在一个目录里面。每个部分又可以细分一些组件,比如 router 表示路由,controller 表示控制器,middleware 是中间件,service 是服务。比如说 user,它里面的东西是和用户相关的东西,可以把它单独放在一个 user 的目录里面,在这个目录里面又可能会包含一些文件,比如说 user-router,user-controller,user-middleware,user-service,在 router 文件可以定义跟这个部分相关的一些路由,也就是应用的接口,在 controller 文件中可以定义接口需要的处理器,这样在 router 文件里面就可以导入使用在 controller 中定义的这些处理器。模块里面需要的中间件可以单独放在 middleware 这个文件里面,在 service 文件里可以定义一些服务方法,其实就是一些函数,在别的地方可以导入使用这些函数,比如在控制器的方法里面可以重复地用到这些东西。在控制器的方法里面可能会重复用到的一些东西我们可以单独地把它定义成一个服务,这样就可以重复地去使用了。


把应用的配置放在环境变量里

将环境变量的设置放在一个文本文件里面,具体方法为:

  1. 安装 dotenv:npm install dotenv –save-dev
  2. 项目根目录创建.env 文件 在这个文件里定义环境变量 比如 APP_PORT = 3000
  3. 在 gitignore 文件里忽略.env 开头的文件: .env*
  4. 创建一个配置文件,比如 app.config,并配置
1
2
3
4
import dotenv from 'dotenv'  //引入刚安装的包
dotenv.config() // 默认会载入根目录下的.env文件

export const {APP_PORT} = process.env //取到app_port的值并导出
  1. 使用的时候,举个例子
1
2
3
4
5
import {APP_PORT} from './app/app.config'   //引入

app.listen(APP_PORT,()=>{
console.log('服务已启动')
})

控制器

接口需要的处理器我们可以放在一种叫控制器的东西里面,控制器只是个名字,没什么特别的,应用里的每个部分都可以创建一个控制器,比如内容资源可以创建一个控制器,用户资源也可以有自己的控制器,在控制器里可以定义一些接口需要的处理器,也就是一些函数。控制器可以是这个样子:

1
2
3
4
5
6
7
8
9
10
11
12
import { Request, Response, NextFunction } from 'express'; //引入接口、处理器、参数需要的类型,ts用

/**
* 内容列表
*/
export const index = (
request: Request,
response: Response,
next: NextFunction,
) => {
response.send('内容列表接口');
};

路由

应用的接口、或者叫路由,也可以单独的放在一个路由文件里面,应用的每个部分都可以有单独的一个路由文件,在这个文件里面可以定义应用的这部分需要的接口。路由是这样的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import express from 'express';  //导入express
import * as postController from './post.controller'; //导入刚刚创建的控制器里所有的东西

const router = express.Router(); //使用express里的Router方法

/**
* 内容列表
*/
router.get('/posts', postController.index); //使用刚才控制器里定义的方法

/**
* 导出路由
*/
export default router; //设置默认导出

用的时候这样写:

1
2
3
4
5
6
import postRouter from '../post/post.router'

/**
* 路由
*/
app.use(postRouter)

服务

在应用里可以加一层服务,用它来缓解控制器的压力,就是可以把一些事情分配给服务去做,创建一个服务文件比如 post.service,内容可以是这样的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/**
* 获取内容列表
*/
export const getPosts = () => {
const data = [
{
content: '明月出天山,苍茫云海间1',
},
{
content: '明月出天山,苍茫云海间2',
},
{
content: '明月出天山,苍茫云海间3',
},
];
return data;
};

用的时候:

1
2
3
4
5
6
7
8
9
10
11
12
13
import { getPosts } from './post.service';

/**
* 内容列表
*/
export const index = (
request: Request,
response: Response,
next: NextFunction,
) => {
const posts = getPosts();
response.send(posts);
};

中间件

middleware ,写法可以是这样的:

1
2
3
4
5
6
7
8
9
10
11
12
13
import { Request, Response, NextFunction, request } from 'express';

/**
* 输出请求地址
*/
export const requestUrl = (
request: Request,
response: Response,
next: NextFunction,
) => {
console.log(request.url);
next(); //执行以下next,这样这个请求才会继续被其他的中间件和接口处理器去处理,如果不执行的话会一直卡在这里
};

使用在路由中:

1
2
3
4
5
6
7
import { requestUrl } from '../app/app.middleware';
const router = express.Router();

/**
* 内容列表
*/
router.get('/posts', requestUrl, postController.index);

默认的异常处理器

异常处理器就是个函数,跟中间件和接口处理器都差不多,只不过多了个 error 参数。在中间件中设置默认异常处理器:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/**
* 默认异常处理器
*/
export const defaultErrorHandler = (
error: any,
request: Request,
response: Response,
next: NextFunction,
) => {
let statusCode: number, message: string;

/**
* 处理异常
*/
switch (error.message) {
default:
statusCode = 500;
message = '服务暂时出了点问题~';
break;
}
response.status(statusCode).send({ message });
};

在页面中配置:

1
2
import { defaultErrorHandler } from './app.middleware';
app.use(defaultErrorHandler);

在控制器的中判断一下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/**
* 内容列表
*/
export const index = (
request: Request,
response: Response,
next: NextFunction,
) => {
if (request.headers.authorization !== 'SECRET') {
return next(new Error()); //如果不加return 会继续执行这个接口处理器的其他代码
}
const posts = getPosts();
response.send(posts);
};

这时候测试一下,会弹出服务暂时出了点问题的提示