מדריך מקיף להבנה ויישום של TypeScript middleware באפליקציות Express.js. גלו תבניות טיפוסים מתקדמות לקוד חזק וקל לתחזוקה.
TypeScript Middleware: שליטה בתבניות טיפוסים של Express Middleware
Express.js, שהיא פריימוורק (framework) מינימליסטי וגמיש ליישומי ווב ב-Node.js, מאפשרת למפתחים לבנות ממשקי API ויישומי ווב חזקים וסקלאביליים. TypeScript משפרת את Express על ידי הוספת טיפוסים סטטיים, שיפור תחזוקת הקוד ותפיסת שגיאות בשלב מוקדם. פונקציות Middleware הן אבן יסוד ב-Express, המאפשרות ליירט ולעבד בקשות לפני שהן מגיעות למטפלי המסלולים (route handlers) שלכם. מאמר זה בוחן תבניות טיפוסים מתקדמות של TypeScript להגדרה ושימוש ב-Express middleware, תוך שיפור בטיחות הטיפוסים ובהירות הקוד.
הבנת Express Middleware
פונקציות Middleware הן פונקציות שיש להן גישה לאובייקט הבקשה (req), לאובייקט התגובה (res), ולפונקציית ה-middleware הבאה במחזור הבקשה-תגובה של היישום. פונקציות Middleware יכולות לבצע את המשימות הבאות:
- ביצוע כל קוד.
- ביצוע שינויים באובייקטי הבקשה והתגובה.
- סיום מחזור הבקשה-תגובה.
- קריאה לפונקציית ה-middleware הבאה במחסנית.
פונקציות Middleware מבוצעות ברצף כפי שהן מתווספות לאפליקציית Express. מקרי שימוש נפוצים ל-middleware כוללים:
- רישום בקשות (Logging).
- אימות משתמשים (Authentication).
- הרשאת גישה למשאבים (Authorization).
- אימות נתוני בקשה (Request validation).
- טיפול בשגיאות (Error handling).
TypeScript Middleware בסיסי
באפליקציית TypeScript Express בסיסית, פונקציית middleware עשויה להיראות כך:
import { Request, Response, NextFunction } from 'express';
function loggerMiddleware(req: Request, res: Response, next: NextFunction) {
console.log(`Request: ${req.method} ${req.url}`);
next();
}
export default loggerMiddleware;
מידלוור פשוט זה רושם את שיטת הבקשה וה-URL לקונסול. בואו נפרט את הערות הטיפוסים:
Request: מייצג את אובייקט הבקשה של Express.Response: מייצג את אובייקט התגובה של Express.NextFunction: פונקציה, שכאשר מופעלת, מבצעת את המידלוור הבא במחסנית.
תוכלו להשתמש במידלוור זה באפליקציית Express שלכם כך:
import express from 'express';
import loggerMiddleware from './middleware/loggerMiddleware';
const app = express();
const port = 3000;
app.use(loggerMiddleware);
app.get('/', (req, res) => {
res.send('Hello, world!');
});
app.listen(port, () => {
console.log(`Server listening on port ${port}`);
});
תבניות טיפוסים מתקדמות עבור Middleware
בעוד שדוגמת המידלוור הבסיסית פונקציונלית, היא חסרה גמישות ובטיחות טיפוסים עבור תרחישים מורכבים יותר. בואו נבחן תבניות טיפוסים מתקדמות המשפרות את פיתוח המידלוור עם TypeScript.
1. טיפוסי בקשה/תגובה מותאמים אישית
לעתים קרובות, תצטרכו להרחיב את אובייקטי ה-Request או ה-Response עם מאפיינים מותאמים אישית. לדוגמה, לאחר אימות, ייתכן שתרצו להוסיף מאפיין user לאובייקט ה-Request. TypeScript מאפשרת לכם להרחיב טיפוסים קיימים באמצעות מיזוג הצהרות (declaration merging).
// src/types/express/index.d.ts
import { Request as ExpressRequest } from 'express';
declare global {
namespace Express {
interface Request {
user?: {
id: string;
email: string;
// ... other user properties
};
}
}
}
export {}; // This is needed to make the file a module
בדוגמה זו, אנו מרחיבים את ממשק ה-Express.Request כדי לכלול מאפיין user אופציונלי. כעת, במידלוור האימות שלכם, תוכלו לאכלס מאפיין זה:
import { Request, Response, NextFunction } from 'express';
function authenticationMiddleware(req: Request, res: Response, next: NextFunction) {
// Simulate authentication logic
const userId = req.headers['x-user-id'] as string; // Or fetch from a token, etc.
if (userId) {
// In a real application, you would fetch the user from a database
req.user = {
id: userId,
email: `user${userId}@example.com`
};
next();
} else {
res.status(401).send('Unauthorized');
}
}
export default authenticationMiddleware;
ובמטפלי המסלולים שלכם, תוכלו לגשת בבטחה למאפיין ה-req.user:
import express from 'express';
import authenticationMiddleware from './middleware/authenticationMiddleware';
const app = express();
const port = 3000;
app.use(authenticationMiddleware);
app.get('/profile', (req: Request, res: Response) => {
if (req.user) {
res.send(`Hello, ${req.user.email}! Your user ID is ${req.user.id}`);
} else {
// This should never happen if the middleware is working correctly
res.status(500).send('Internal Server Error');
}
});
app.listen(port, () => {
console.log(`Server listening on port ${port}`);
});
2. מפעלי Middleware (Middleware Factories)
מפעלי Middleware הן פונקציות שמחזירות פונקציות middleware. תבנית זו שימושית כאשר עליכם להגדיר middleware עם אפשרויות או תלויות ספציפיות. לדוגמה, שקלו middleware רישום (logging) הרושם הודעות לקובץ ספציפי:
import { Request, Response, NextFunction } from 'express';
import fs from 'fs';
import path from 'path';
function createLoggingMiddleware(logFilePath: string) {
return (req: Request, res: Response, next: NextFunction) => {
const logMessage = `[${new Date().toISOString()}] Request: ${req.method} ${req.url}\n`;
fs.appendFile(logFilePath, logMessage, (err) => {
if (err) {
console.error('Error writing to log file:', err);
}
next();
});
};
}
export default createLoggingMiddleware;
תוכלו להשתמש במפעל middleware זה כך:
import express from 'express';
import createLoggingMiddleware from './middleware/loggingMiddleware';
const app = express();
const port = 3000;
const logFilePath = path.join(__dirname, 'logs', 'requests.log');
app.use(createLoggingMiddleware(logFilePath));
app.get('/', (req, res) => {
res.send('Hello, world!');
});
app.listen(port, () => {
console.log(`Server listening on port ${port}`);
});
3. Middleware אסינכרוני
פונקציות Middleware לעתים קרובות צריכות לבצע פעולות אסינכרוניות, כגון שאילתות מסד נתונים או קריאות API. כדי לטפל בפעולות אסינכרוניות בצורה נכונה, עליכם לוודא שפונקציית ה-next נקראת לאחר שהפעולה האסינכרונית הושלמה. ניתן להשיג זאת באמצעות async/await או Promises.
import { Request, Response, NextFunction } from 'express';
async function asyncMiddleware(req: Request, res: Response, next: NextFunction) {
try {
// Simulate an asynchronous operation
await new Promise(resolve => setTimeout(resolve, 100));
console.log('Asynchronous operation completed');
next();
} catch (error) {
next(error); // Pass the error to the error handling middleware
}
}
export default asyncMiddleware;
חשוב: זכרו לטפל בשגיאות בתוך המידלוור האסינכרוני שלכם ולהעביר אותן למידלוור לטיפול בשגיאות באמצעות next(error). זה מבטיח ששגיאות יטופלו ויתועדו כראוי.
4. Middleware לטיפול בשגיאות
מידלוור לטיפול בשגיאות הוא סוג מיוחד של middleware המטפל בשגיאות המתרחשות במהלך מחזור הבקשה-תגובה. לפונקציות middleware לטיפול בשגיאות יש ארבעה ארגומנטים: err, req, res, ו-next.
import { Request, Response, NextFunction } from 'express';
function errorHandler(err: any, req: Request, res: Response, next: NextFunction) {
console.error(err.stack);
res.status(500).send('Something went wrong!');
}
export default errorHandler;
עליכם לרשום את המידלוור לטיפול בשגיאות אחרי כל המידלוור האחרים ומטפלי המסלולים. Express מזהה מידלוור לטיפול בשגיאות לפי נוכחותם של ארבעת הארגומנטים.
import express from 'express';
import asyncMiddleware from './middleware/asyncMiddleware';
import errorHandler from './middleware/errorHandler';
const app = express();
const port = 3000;
app.use(asyncMiddleware);
app.get('/', (req, res) => {
throw new Error('Simulated error!'); // Simulate an error
});
app.use(errorHandler); // Error handling middleware MUST be registered last
app.listen(port, () => {
console.log(`Server listening on port ${port}`);
});
5. Middleware לאימות בקשות
אימות בקשות הוא היבט מכריע בבניית ממשקי API מאובטחים ואמינים. ניתן להשתמש ב-Middleware כדי לאמת נתוני בקשה נכנסים ולוודא שהם עומדים בקריטריונים מסוימים לפני שהם מגיעים למטפלי המסלולים שלכם. ספריות כמו joi או express-validator יכולות לשמש לאימות בקשות.
הנה דוגמה המשתמשת ב-express-validator:
import { Request, Response, NextFunction } from 'express';
import { body, validationResult } from 'express-validator';
const validateCreateUserRequest = [
body('email').isEmail().normalizeEmail(),
body('password').isLength({ min: 8 }),
(req: Request, res: Response, next: NextFunction) => {
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(400).json({ errors: errors.array() });
}
next();
}
];
export default validateCreateUserRequest;
מידלוור זה מאמת את שדות ה-email וה-password בגוף הבקשה. אם האימות נכשל, הוא מחזיר תגובת 400 Bad Request עם מערך של הודעות שגיאה. תוכלו להשתמש במידלוור זה במטפלי המסלולים שלכם כך:
import express from 'express';
import validateCreateUserRequest from './middleware/validateCreateUserRequest';
const app = express();
const port = 3000;
app.post('/users', validateCreateUserRequest, (req, res) => {
// If validation passes, create the user
res.send('User created successfully!');
});
app.listen(port, () => {
console.log(`Server listening on port ${port}`);
});
6. הזרקת תלויות עבור Middleware
כאשר פונקציות המידלוור שלכם תלויות בשירותים או בתצורות חיצוניות, הזרקת תלויות יכולה לעזור לשפר את יכולת הבדיקה והתחזוקה. תוכלו להשתמש בקונטיינר הזרקת תלויות כמו tsyringe או פשוט להעביר תלויות כארגומנטים למפעלי המידלוור שלכם.
הנה דוגמה המשתמשת במפעל middleware עם הזרקת תלויות:
// src/services/UserService.ts
export class UserService {
async createUser(email: string, password: string): Promise {
// In a real application, you would save the user to a database
console.log(`Creating user with email: ${email} and password: ${password}`);
await new Promise(resolve => setTimeout(resolve, 500)); // Simulate a database operation
}
}
// src/middleware/createUserMiddleware.ts
import { Request, Response, NextFunction } from 'express';
import { UserService } from '../services/UserService';
function createCreateUserMiddleware(userService: UserService) {
return async (req: Request, res: Response, next: NextFunction) => {
try {
const { email, password } = req.body;
await userService.createUser(email, password);
res.status(201).send('User created successfully!');
} catch (error) {
next(error);
}
};
}
export default createCreateUserMiddleware;
// src/app.ts
import express from 'express';
import createCreateUserMiddleware from './middleware/createUserMiddleware';
import { UserService } from './services/UserService';
import errorHandler from './middleware/errorHandler';
const app = express();
const port = 3000;
app.use(express.json()); // Parse JSON request bodies
const userService = new UserService();
const createUserMiddleware = createCreateUserMiddleware(userService);
app.post('/users', createUserMiddleware);
app.use(errorHandler);
app.listen(port, () => {
console.log(`Server listening on port ${port}`);
});
שיטות עבודה מומלצות עבור TypeScript Middleware
- שמרו על פונקציות middleware קטנות וממוקדות. לכל פונקציית middleware צריכה להיות אחריות אחת בלבד.
- השתמשו בשמות תיאוריים עבור פונקציות ה-middleware שלכם. השם צריך להצביע בבירור על מה המידלוור עושה.
- טפלו בשגיאות כראוי. תמיד תפסו שגיאות והעבירו אותן למידלוור לטיפול בשגיאות באמצעות
next(error). - השתמשו בטיפוסי בקשה/תגובה מותאמים אישית כדי לשפר את בטיחות הטיפוסים. הרחיבו את ממשקי ה-
Requestוה-Responseעם מאפיינים מותאמים אישית לפי הצורך. - השתמשו במפעלי middleware כדי להגדיר middleware עם אפשרויות ספציפיות.
- תעדו את פונקציות ה-middleware שלכם. הסבירו מה המידלוור עושה וכיצד יש להשתמש בו.
- בדקו את פונקציות ה-middleware שלכם ביסודיות. כתבו בדיקות יחידה כדי לוודא שפונקציות ה-middleware שלכם עובדות כראוי.
סיכום
TypeScript משפרת באופן משמעותי את פיתוח ה-Express middleware על ידי הוספת טיפוסים סטטיים, שיפור תחזוקת הקוד ותפיסת שגיאות בשלב מוקדם. על ידי שליטה בתבניות טיפוסים מתקדמות כמו טיפוסי בקשה/תגובה מותאמים אישית, מפעלי middleware, middleware אסינכרוני, middleware לטיפול בשגיאות, ו-middleware לאימות בקשות, תוכלו לבנות יישומי Express חזקים, סקלאביליים ובטוחים מבחינת טיפוסים. זכרו לעקוב אחר שיטות עבודה מומלצות כדי לשמור על פונקציות ה-middleware שלכם קטנות, ממוקדות ומתועדות היטב.