Express 基本结构
import express from 'express';
const app = express();
上方的范例程式码定义 app 是 Express 的实例,在这个层级我们称为 应用程式层 (Application-level) ,而 Express 的应用是由一系列 中介软体 (Middleware) 所构成,其定义为:可以存取 请求 (req) 、回应 (res) 以及呼叫下一个中介软体的 函式 (Function) ,要呼叫下一个中介软体就要使用next()。
应用程式层定义基本路由
可以透过 app 提供的 方法 (Methods) 来指定要用什么方式载入路由,比如说:使用 GET 方式载入路由。下方为基本的路由结构,其中,大写的部分为指定参数:
app.METHOD(PATH, HANDLER)
- METHOD:为载入路由的方式,如:GET、POST、PUT、PATCH、DELETE 等
- PATH:为资源的路径,即要设定的路由位置
- HANDLER:为载入路由后要执行的操作,即 中介软体函数,可以是 一个或多个
function
路由器层定义基本路由
├── src
| ├── index.ts
| ├── app
| | └── app.routing.ts
| └── environments
| ├── development.env
| └── production.env
├── package.json
└── tsconfig.json
写一下 `app.routing.ts` 的内容:
import express from 'express';
const router = express.Router();
router.get('/test', (req, res, next) => {
res.send('test!');
});
export default router;
//改一下 index.ts 的内容:
import express from 'express';
import path from 'path';
import dotenv from 'dotenv';
import appRoute from './app/app.routing';
const app = express();
dotenv.config({ path: path.resolve(__dirname, `./environments/${process.env.NODE_ENV}.env`) });
app.get('/', (req, res, next) => {
res.send('Hello, World!!');
});
app.use('/', appRoute);
app.listen(process.env.PORT, () => console.log(`http server is running at port ${process.env.PORT}.`));
执行npm run start:dev:
从上方范例可以看出,定义完路由器层的路由配置后,需要在应用程式层透过 use 方法来载入 Router 的实例,进而使用该路由的配置,是十分方便的功能。
等等,所以 use 是干嘛用的?
简单来说 use 是用来载入middleware的方法,与get、post等方法一样,透过指定路径,使它在特定路径下才会触发,是经常使用的功能,与其他方法不同的是它并不会受请求资源的方式所限制。
middleware于 Express 中扮演何种角色
从上面的案例不难看出 Express 的路由机制可以建立成树状结构,由许多 Router 组合起来,最终收敛于应用程式层。
body-parser于 Express 中扮演何种角色
body-parser 是 Express 经常使用的中介软体,用于解析请求的资料 (body),比如说:POST 一笔 JSON 格式的资料到我们的 Express App,这时可以透过 body-parser 快速解析这笔资料,以方便取用。下方的图为 Express 的运作模型,可以看到 body-parser 会先进行资料的解析,才会把解析后的资料传给其他相关的 middleware 使用。
常用的解析方式
解析 JSON
用来解析 JSON 格式的请求资料。
bodyParser.json(options);
options的参数:
inflate: 解压缩 deflate 资料,若为false, deflate 资料会被拒绝,预设为truelimit: 设置请求大小的上限,预设为100kbreviver: 为JSON.parse的第二个参数strict: 仅解析Object与Array,若为false则采用JSON.parse的解析模式,预设为truetype: 设定何种类型的资源要进行解析,预设为application/json
解析 urlencoded
用来解析 urlencoded 格式的请求资料。
bodyParser.urlencoded(options);
options的参数:
extended: 使用 qs 进行解析,若为false,则采用 querystring 进行解析,预设为trueinflate: 解压缩 deflate 资料,若为false, deflate 资料会被拒绝,预设为truelimit: 设置请求大小的上限,预设为100kbparameterLimit: 设置最大参数数量,预设为1000type: 设定何种类型的资源要进行解析,预设为application/x-www-form-urlencoded
使用方法
Express 将 body-parser 包起来了,所以 body-parser 的 API 都可以从 express 中调用,如:
express.json();
import express from 'express';
const router = express.Router();
router.post('/test', express.json(), (req, res, next) => {
res.send(JSON.stringify(req.body));
});
export default router;
一句话: body-parser可以帮我们处理不同类型的资料
Express 抛出错误的方式
在 Express 中抛出错误的方式大致上可以分成两种,会在下方进行示范,并在 app.routing.ts 中进行测试
直接抛出
透过 throw 进行抛出:
router.get('/error', (req, res, next) => {
throw 'error page.';
});
next 抛出
next()带入错误讯息,Express 会视为错误:
router.get('/error', (req, res, next) => {
next('error page.');
});
Promise next(err)
如果今天有一连串的资料要处理,并且用 then chain 的方式处理,大致上会长这样:
router.get('/data/error', (req, res, next) => {
// Fake API
const getProfile = new Promise((resolve, reject) => {
setTimeout(() => resolve({ name: 'HAO', age: 22 }), 100);
});
const getFriends = new Promise((resolve, reject) => {
setTimeout(() => resolve([]), 120);
});
const errorRequest = new Promise((resolve, reject) => {
setTimeout(() => reject('Oops!'), 2000);
});
getProfile
.then(profile => getFriends)
.then(friends => errorRequest)
.then(() => res.send('GoGoGo!'))
.catch(err => next(err));
});
如果比较讲究 (龟毛) 的人就会希望不要看到一堆next(err),那可以怎么做呢?运用 await/async 的机制可以办到,先定义一个函式:
const errorHandler = (
func: (req: Request, res: Response, next: NextFunction) => Promise<void>
) => (req: Request, res: Response, next: NextFunction) =>
func(req, res, next).catch(next);
并把 middleware 带入此函式中:
router.get('/data/error/promise', errorHandler(async (req, res, next) => {
// Fake API
const getProfile = new Promise((resolve, reject) => {
setTimeout(() => resolve({ name: 'HAO', age: 22 }), 100);
});
const getFriends = new Promise((resolve, reject) => {
setTimeout(() => resolve([]), 120);
});
const errorRequest = new Promise((resolve, reject) => {
setTimeout(() => reject('Oops!'), 2000);
});
const profile = await getProfile;
const friends = await getFriends;
const none = await errorRequest;
res.send('GoGoGo!');
}));
两者结果相同,但前者会将错误处理直接放在 middleware 里,而后者是将 middleware 包起来,并执行一个被包装好的 async function,只要发生错误就会自动将错误传至 next() 中。
Express 全域错误处理
Express 本身有内建错误处理器,只要抛出错误就会将错误资讯带入其中,这个就是 全域错误处理 机制,以一个庞大的系统而言,建议自行设计一套全域错误处理机制来格式化错误资讯,再送回客户端。究竟要如何设计呢?只需要在 应用程式层 用中介软体来解决,这个中介软体正是前面所提到的Error-First Callback:
app.use(function(err: any, req: Request, res: Response, next: NextFunction) {
res.status(500).json({ message: err.message || err });
});
现在,打开浏览器并进入刚刚设计的http://localhost:3000/data/error来测试看看是不是变成 JSON 格式了