文章目录
响应一致化策略
- 你可以点击这里查看本文的 Github README 项目链接也是这个哦
创建路由处理
// current-user.ts
import express from "express";
// 使用 express.Router
const router = express.Router();
router.get("/api/users/currentuser", () => {});
export { router as currentUserRouter };
// index.ts
import express from "express";
import { json } from "body-parser";
import { currentUserRouter } from "./routes/current-user";
const app = express();
app.use(json());
app.use(currentUserRouter);
app.listen(3000, () => {
console.log("Listening on port 3000!!!!!!!!");
});
引入更多的路由
- 这里注意的是,express.Router 负责包装路由
- express 负责 通过 use 整合包装后的路由
// index.ts
import express from "express";
import { json } from "body-parser";
import { currentUserRouter } from "./routes/current-user";
import { signinRouter } from "./routes/signin";
import { signoutRouter } from "./routes/signout";
import { signupRouter } from "./routes/signup";
const app = express();
app.use(json());
app.use(currentUserRouter);
app.use(signinRouter);
app.use(signoutRouter);
app.use(signupRouter);
app.listen(3000, () => {
console.log("Listening on port 3000!");
});
增加验证
- 使用express-validator
- 一句话概括:express-validator 主要提供 express 的验证(到目前只用到过 body)
- 具体用法如下
- 在 router.post / [xxx method] 中,route 和 callback 前,用数组 或者
「,」的分割
来传入 body 的 isXXX 验证,最后.withMessage 返回 msg
- 在 router.post / [xxx method] 中,route 和 callback 前,用数组 或者
// signup.ts
import express, { Request, Response } from "express";
import { body } from "express-validator";
const router = express.Router();
router.post(
"/api/users/signup",
[
body("email").isEmail().withMessage("Email must be valid"),
body("password")
.trim()
.isLength({ min: 4, max: 20 })
.withMessage("Password must be between 4 and 20 characters"),
],
(req: Request, res: Response) => {
const { email, password } = req.body;
if (!email || typeof email !== "string") {
res.status(400).send("Provide a valid email");
}
// new User({ email, password })
}
);
export { router as signupRouter };
认证错误的处理
- 用 express-validator 的 validationResult 接收 Request 的错误信息
- 通过 .array() 就会返回如下格式的错误信息
{
"msg": "The error message",
"param": "param.name.with.index[0]",
"value": "param value",
// Location of the param that generated this error.
// It's either body, query, params, cookies or headers.
"location": "body",
// nestedErrors only exist when using the oneOf function
"nestedErrors": [{ ... }]
}
import express, { Request, Response } from "express";
import { body, validationResult } from "express-validator";
const router = express.Router();
router.post(
"/api/users/signup",
[
body("email").isEmail().withMessage("Email must be valid"),
body("password")
.trim()
.isLength({ min: 4, max: 20 })
.withMessage("Password must be between 4 and 20 characters"),
],
(req: Request, res: Response) => {
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(400).send(errors.array());
}
const { email, password } = req.body;
console.log("Creating a user...");
res.send({});
}
);
export { router as signupRouter };
统一不同类型微服务的错误信息
其他的错误来源
Error 处理的解决方案
处理 Error 的困难 | 解决方式 |
---|---|
无论出现什么问题,我们都必须从所有服务器获得一致的结构化响应 | 编写一个错误处理中间件来处理错误,给它们一个一致的结构,然后返回给浏览器 |
不仅仅是验证请求处理程序的输入。还有其他奇奇怪怪的错误要处理 | 确保我们使用 Express 的错误处理机制捕获所有可能的错误(调用 ‘next’ 函数!) |
创建一个 Error 处理的 middleware
// error-handler.ts
import { Request, Response, NextFunction } from "express";
export const errorHandler = (
err: Error,
req: Request,
res: Response,
next: NextFunction
) => {
console.log("Something went wrong", err);
res.status(400).send({
message: "Something went wrong",
});
};
向错误处理程序传达更多信息
定义并处理更多 Error 信息
- 我们想要一个像“ Error ”这样的对象,但我们想向它添加更多自定义属性
- 通常这类信息需要进行子类化继承
- Custom errors, extending Error
自定义错误 Custom Errors 的子类化及子类类型
// request-validation-error.ts
import { ValidationError } from "express-validator";
export class RequestValidationError extends Error {
constructor(public errors: ValidationError[]) {
super();
// Only because we are extending a built in class
Object.setPrototypeOf(this, RequestValidationError.prototype);
}
}
// database-connection-error copy.ts
export class DatabaseConnectionError extends Error {
reason = "Error connecting to database";
constructor() {
super();
// Only because we are extending a built in class
Object.setPrototypeOf(this, DatabaseConnectionError.prototype);
}
}
确定错误类型 Error Type
- 用
instanceof
判断继承关系,因为我们之前Object.setPrototypeOf(this, [CurrentTypeError].prototype)
- 设置一个指定的对象的原型 ( 即, 内部[[Prototype]]属性)到另一个对象或 null。
if (!Object.setPrototypeOf) {
// 仅适用于Chrome和FireFox,在IE中不工作:
Object.prototype.setPrototypeOf = function (obj, proto) {
if (obj.__proto__) {
obj.__proto__ = proto;
return obj;
} else {
// 如果你想返回 prototype of Object.create(null):
var Fn = function () {
for (var key in obj) {
Object.defineProperty(this, key, {
value: obj[key],
});
}
};
Fn.prototype = proto;
return new Fn();
}
};
}
import { Request, Response, NextFunction } from "express";
import { RequestValidationError } from "../errors/request-validation-error";
import { DatabaseConnectionError } from "../errors/database-connection-error copy";
export const errorHandler = (
err: Error,
req: Request,
res: Response,
next: NextFunction
) => {
if (err instanceof RequestValidationError) {
console.log("handling this error as a request validation error");
}
if (err instanceof DatabaseConnectionError) {
console.log("handling this error as a database connection error");
}
res.status(400).send({
message: err.message,
});
};
将 Error 转换为 Response
将序列化逻辑植入进 Errors middleware
// database-connection-error.ts
export class DatabaseConnectionError extends Error {
statusCode = 500;
reason = "Error connecting to database";
constructor() {
super();
// Only because we are extending a built in class
Object.setPrototypeOf(this, DatabaseConnectionError.prototype);
}
serializeErrors() {
return [{ message: this.reason }];
}
}
// request-validation-error.ts
import { ValidationError } from "express-validator";
export class RequestValidationError extends Error {
statusCode = 400;
constructor(public errors: ValidationError[]) {
super();
// Only because we are extending a built in class
Object.setPrototypeOf(this, RequestValidationError.prototype);
}
serializeErrors() {
return this.errors.map((error) => {
return { message: error.msg, field: error.param };
});
}
}
// error-handler.ts
import { Request, Response, NextFunction } from "express";
import { RequestValidationError } from "../errors/request-validation-error";
import { DatabaseConnectionError } from "../errors/database-connection-error copy";
export const errorHandler = (
err: Error,
req: Request,
res: Response,
next: NextFunction
) => {
if (err instanceof RequestValidationError) {
return res.status(err.statusCode).send({ errors: err.serializeErrors() });
}
if (err instanceof DatabaseConnectionError) {
return res.status(err.statusCode).send({ errors: err.serializeErrors() });
}
res.status(400).send({
errors: [{ message: "Something went wrong" }],
});
};
验证自定义的 Error
- 于是就有了 CommonError 对于继承的子类的 abstract 约束
import { ValidationError } from "express-validator";
interface CustomError {
statusCode: number;
serializeErrors(): {
message: string;
field?: string;
}[]; // 对象数组的声明
}
export class RequestValidationError extends Error implements CustomError {
statusCode = 400;
constructor(public errors: ValidationError[]) {
super();
// Only because we are extending a built in class
Object.setPrototypeOf(this, RequestValidationError.prototype);
}
serializeErrors() {
return this.errors.map((error) => {
return { message: error.msg, field: error.param };
});
}
}
最终 Error 相关的 Code
custom-error.ts
- 抽象类无法实例化
- 主要对子类进行继承属性的约束(序列归一化的体现)
// custom-error.ts
export abstract class CustomError extends Error {
abstract statusCode: number;
constructor(message: string) {
super(message);
Object.setPrototypeOf(this, CustomError.prototype);
}
abstract serializeErrors(): { message: string; field?: string }[];
}
database-connection-error.ts
- 报 500 的 DatabaseConnectionError
- 无 field 验证错误字段返回
// database-connection-error.ts
import { CustomError } from "./custom-error";
export class DatabaseConnectionError extends CustomError {
statusCode = 500;
reason = "Error connecting to database";
constructor() {
super("Error connecting to db");
// Only because we are extending a built in class
Object.setPrototypeOf(this, DatabaseConnectionError.prototype);
}
serializeErrors() {
return [{ message: this.reason }];
}
}
request-validation-error.ts
- 报 400 的 RequestValidationError
- 有错误字段返回
// request-validation-error.ts
import { ValidationError } from "express-validator";
import { CustomError } from "./custom-error";
export class RequestValidationError extends CustomError {
statusCode = 400;
constructor(public errors: ValidationError[]) {
super("Invalid request parameters");
// Only because we are extending a built in class
Object.setPrototypeOf(this, RequestValidationError.prototype);
}
serializeErrors() {
return this.errors.map((error) => {
return { message: error.msg, field: error.param };
});
}
}
error-handler.ts
// error-handler.ts
import { Request, Response, NextFunction } from "express";
import { CustomError } from "../errors/custom-error";
export const errorHandler = (
err: Error,
req: Request,
res: Response,
next: NextFunction
) => {
if (err instanceof CustomError) {
return res.status(err.statusCode).send({ errors: err.serializeErrors() });
}
res.status(400).send({
errors: [{ message: "Something went wrong" }],
});
};
创建 404 的路由错误
import { CustomError } from "./custom-error";
export class NotFoundError extends CustomError {
statusCode = 404;
constructor() {
super("Route not found");
Object.setPrototypeOf(this, NotFoundError.prototype);
}
serializeErrors() {
return [{ message: "Not Found" }];
}
}
import express from "express";
import { json } from "body-parser";
import { currentUserRouter } from "./routes/current-user";
import { signinRouter } from "./routes/signin";
import { signoutRouter } from "./routes/signout";
import { signupRouter } from "./routes/signup";
import { errorHandler } from "./middleware/error-handler";
import { NotFoundError } from "./errors/not-found-error";
const app = express();
app.use(json());
app.use(currentUserRouter);
app.use(signinRouter);
app.use(signoutRouter);
app.use(signupRouter);
app.all("*", () => {
throw new NotFoundError();
});
app.use(errorHandler);
app.listen(3000, () => {
console.log("Listening on port 3000!");
});
异步抛出错误
- express.all 使用案例
var express = require("express");
var app = express();
app.all("*", function(request, response, next) {
response.writeHead(200, { "Content-Type": "text/html;charset=utf-8" }); //设置响应头属性值
next();
});
app.get("/", function(request, response) {
response.end("欢迎来到首页!");
});
app.get("/about", function(request, response) {
response.end("欢迎来到about页面!");
});
app.get("*", function(request, response) {
response.end("404 - 未找到!");
});
app.listen(80);
app.all("*", async (req, res, next) => {
next(new NotFoundError());
});
-
可以用 express 的 NextFunction
-
也可以
import 'express-async-errors';
ExpressJS Async Errors
app.all("*", async (req, res) => {
throw new NotFoundError();
});