ตรวจสอบข้อมูลตั้งแต่ขอบของ API และรวมศูนย์การจัดการข้อผิดพลาดทั้งหมดไว้ใน middleware สี่พารามิเตอร์ตัวเดียว
เปิดบทเรียนนี้ใน Kodokonข้อมูลขาเข้าทุกชิ้นถือเป็นศัตรูจนกว่าจะพิสูจน์ได้เป็นอย่างอื่น ไม่ว่าจะเป็น JSON body, พารามิเตอร์ URL หรือ header การตรวจสอบควรอยู่ที่ ขอบ ของแอปพลิเคชัน คือก่อนถึง controller เพื่อให้ชั้นด้านในทำงานกับข้อมูลที่รับประกันแล้ว จากนั้นจงแยกแยะข้อผิดพลาดสองตระกูล คือ ข้อผิดพลาด เชิงปฏิบัติการ (operational) (ข้อมูลนำเข้าไม่ถูกต้อง, ไม่พบทรัพยากร, ความขัดแย้ง) ซึ่งคาดการณ์ได้และถูกแปลงเป็น 4xx และข้อผิดพลาด เชิงการเขียนโปรแกรม (programming) (บั๊ก) ซึ่งต้องให้ผลเป็น 500 ที่ทึบต่อฝั่งไคลเอนต์ และ stack trace เต็มรูปแบบที่ฝั่งเซิร์ฟเวอร์
export class HttpError extends Error {
constructor(status, message, details = undefined) {
super(message);
this.status = status;
this.details = details;
}
}middleware ตรวจสอบจะดักคำขอไว้ก่อนถึง controller เวอร์ชันเขียนเองด้านล่างแสดงหลักการ ส่วนใน production สคีมาแบบประกาศ (zod ได้กลายเป็นมาตรฐาน) จะให้การอนุมานชนิด (type inference) และข้อความที่มีโครงสร้างเพิ่มเข้ามาด้วย การแลกเปลี่ยนคือ การพึ่งพาหนึ่งตัวและต้นทุนตอนรันไทม์เล็กน้อย แลกกับกฎที่รวมศูนย์ ประกอบเข้าด้วยกันได้ และเป็นไปไม่ได้ที่จะลืม
import { HttpError } from './http-error.js';
export function validateUser(req, res, next) {
const { email, name } = req.body ?? {};
const errors = [];
if (typeof email !== 'string' || !email.includes('@')) {
errors.push('email: invalid format');
}
if (typeof name !== 'string' || name.trim().length < 2) {
errors.push('name: 2 characters minimum');
}
if (errors.length > 0) {
return next(new HttpError(400, 'Invalid body', errors));
}
next();
}Express รู้จัก error middleware จาก ลายเซ็นสี่พารามิเตอร์ (err, req, res, next) ของมัน เมื่อลงทะเบียนไว้ท้ายสุด หลังจาก route ทั้งหมด ด้วย app.use(errorHandler) มันจะกลายเป็นทางออกข้อผิดพลาดเพียงทางเดียวของ API คือ รูปแบบคำตอบที่เป็นเนื้อเดียวกัน การบันทึก log ที่จุดเดียว และไม่มีการรั่วไหลของข้อมูลภายใน ส่วน controller นั้นก็เพียงแค่เรียก next(err)
export function errorHandler(err, req, res, next) {
if (res.headersSent) {
return next(err);
}
const status = err.status ?? 500;
const body = { error: err.message };
if (err.details) {
body.details = err.details;
}
if (status >= 500) {
console.error(err);
body.error = 'Internal server error';
}
res.status(status).json(body);
}