Kompleksowy przewodnik po zrozumieniu i implementacji TypeScript middleware w aplikacjach Express.js. Poznaj zaawansowane wzorce typ贸w dla solidnego i 艂atwego w utrzymaniu kodu.
TypeScript Middleware: Opanowanie Wzorc贸w Typ贸w Middleware w Express
Express.js, minimalistyczny i elastyczny framework aplikacji internetowych Node.js, pozwala programistom budowa膰 solidne i skalowalne API oraz aplikacje internetowe. TypeScript wzmacnia Express, dodaj膮c statyczne typowanie, poprawiaj膮c 艂atwo艣膰 utrzymania kodu i wcze艣nie wychwytuj膮c b艂臋dy. Funkcje middleware s膮 podstaw膮 Express, umo偶liwiaj膮c przechwytywanie i przetwarzanie 偶膮da艅, zanim dotr膮 one do procedur obs艂ugi routingu. Ten artyku艂 bada zaawansowane wzorce typ贸w TypeScript do definiowania i wykorzystywania middleware Express, zwi臋kszaj膮c bezpiecze艅stwo typ贸w i klarowno艣膰 kodu.
Zrozumienie Express Middleware
Funkcje middleware to funkcje, kt贸re maj膮 dost臋p do obiektu 偶膮dania (req), obiektu odpowiedzi (res) i nast臋pnej funkcji middleware w cyklu 偶膮danie-odpowied藕 aplikacji. Funkcje middleware mog膮 wykonywa膰 nast臋puj膮ce zadania:
- Wykonywanie dowolnego kodu.
- Wprowadzanie zmian w obiektach 偶膮dania i odpowiedzi.
- Ko艅czenie cyklu 偶膮danie-odpowied藕.
- Wywo艂ywanie nast臋pnej funkcji middleware na stosie.
Funkcje middleware s膮 wykonywane sekwencyjnie, w kolejno艣ci, w jakiej zosta艂y dodane do aplikacji Express. Typowe przypadki u偶ycia middleware obejmuj膮:
- Rejestrowanie 偶膮da艅.
- Uwierzytelnianie u偶ytkownik贸w.
- Autoryzowanie dost臋pu do zasob贸w.
- Walidacja danych 偶膮dania.
- Obs艂uga b艂臋d贸w.
Podstawowe TypeScript Middleware
W podstawowej aplikacji TypeScript Express funkcja middleware mo偶e wygl膮da膰 tak:
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;
Ten prosty middleware rejestruje metod臋 偶膮dania i adres URL w konsoli. Przeanalizujmy adnotacje typ贸w:
Request: Reprezentuje obiekt 偶膮dania Express.Response: Reprezentuje obiekt odpowiedzi Express.NextFunction: Funkcja, kt贸ra po wywo艂aniu wykonuje nast臋pny middleware na stosie.
Mo偶esz u偶y膰 tego middleware w swojej aplikacji Express w nast臋puj膮cy spos贸b:
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}`);
});
Zaawansowane Wzorce Typ贸w dla Middleware
Chocia偶 podstawowy przyk艂ad middleware jest funkcjonalny, brakuje mu elastyczno艣ci i bezpiecze艅stwa typ贸w dla bardziej z艂o偶onych scenariuszy. Zbadajmy zaawansowane wzorce typ贸w, kt贸re poprawiaj膮 rozw贸j middleware za pomoc膮 TypeScript.
1. Niestandardowe Typy 呕膮dania/Odpowiedzi
Cz臋sto trzeba b臋dzie rozszerzy膰 obiekty Request lub Response o niestandardowe w艂a艣ciwo艣ci. Na przyk艂ad po uwierzytelnieniu mo偶esz chcie膰 doda膰 w艂a艣ciwo艣膰 user do obiektu Request. TypeScript umo偶liwia rozszerzanie istniej膮cych typ贸w za pomoc膮 艂膮czenia deklaracji.
// 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
W tym przyk艂adzie rozszerzamy interfejs Express.Request, aby zawiera艂 opcjonaln膮 w艂a艣ciwo艣膰 user. Teraz w middleware uwierzytelniania mo偶esz wype艂ni膰 t臋 w艂a艣ciwo艣膰:
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;
A w procedurach obs艂ugi routingu mo偶esz bezpiecznie uzyska膰 dost臋p do w艂a艣ciwo艣ci 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. Fabryki Middleware
Fabryki middleware to funkcje, kt贸re zwracaj膮 funkcje middleware. Ten wzorzec jest przydatny, gdy trzeba skonfigurowa膰 middleware z okre艣lonymi opcjami lub zale偶no艣ciami. Na przyk艂ad rozwa偶 middleware rejestruj膮cy, kt贸ry rejestruje wiadomo艣ci w okre艣lonym pliku:
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;
Mo偶esz u偶y膰 tej fabryki middleware w nast臋puj膮cy spos贸b:
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. Asynchroniczne Middleware
Funkcje middleware cz臋sto musz膮 wykonywa膰 operacje asynchroniczne, takie jak zapytania do bazy danych lub wywo艂ania API. Aby poprawnie obs艂ugiwa膰 operacje asynchroniczne, musisz upewni膰 si臋, 偶e funkcja next jest wywo艂ywana po zako艅czeniu operacji asynchronicznej. Mo偶na to osi膮gn膮膰 za pomoc膮 async/await lub 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;
Wa偶ne: Pami臋taj, aby obs艂ugiwa膰 b艂臋dy w middleware asynchronicznym i przekazywa膰 je do middleware obs艂ugi b艂臋d贸w za pomoc膮 next(error). Zapewnia to, 偶e b艂臋dy s膮 poprawnie obs艂ugiwane i rejestrowane.
4. Middleware Obs艂ugi B艂臋d贸w
Middleware obs艂ugi b艂臋d贸w to specjalny typ middleware, kt贸ry obs艂uguje b艂臋dy, kt贸re wyst臋puj膮 podczas cyklu 偶膮danie-odpowied藕. Funkcje middleware obs艂ugi b艂臋d贸w maj膮 cztery argumenty: err, req, res i 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;
Musisz zarejestrowa膰 middleware obs艂ugi b艂臋d贸w po wszystkich innych middleware i procedurach obs艂ugi routingu. Express identyfikuje middleware obs艂ugi b艂臋d贸w po obecno艣ci czterech argument贸w.
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 Walidacji 呕膮da艅
Walidacja 偶膮da艅 jest kluczowym aspektem budowania bezpiecznych i niezawodnych API. Middleware mo偶e by膰 u偶ywany do walidacji przychodz膮cych danych 偶膮da艅 i zapewnienia, 偶e spe艂niaj膮 one okre艣lone kryteria, zanim dotr膮 do procedur obs艂ugi routingu. Biblioteki takie jak joi lub express-validator mog膮 by膰 u偶ywane do walidacji 偶膮da艅.
Oto przyk艂ad u偶ycia 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;
Ten middleware waliduje pola email i password w tre艣ci 偶膮dania. Je艣li walidacja nie powiedzie si臋, zwraca odpowied藕 400 Bad Request z tablic膮 komunikat贸w o b艂臋dach. Mo偶esz u偶y膰 tego middleware w swoich procedurach obs艂ugi routingu w nast臋puj膮cy spos贸b:
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. Wstrzykiwanie Zale偶no艣ci dla Middleware
Gdy funkcje middleware zale偶膮 od zewn臋trznych us艂ug lub konfiguracji, wstrzykiwanie zale偶no艣ci mo偶e pom贸c w poprawie testowalno艣ci i 艂atwo艣ci utrzymania. Mo偶esz u偶y膰 kontenera wstrzykiwania zale偶no艣ci, takiego jak tsyringe, lub po prostu przekazywa膰 zale偶no艣ci jako argumenty do fabryk middleware.
Oto przyk艂ad u偶ycia fabryki middleware z wstrzykiwaniem zale偶no艣ci:
// 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}`);
});
Najlepsze Praktyki dla TypeScript Middleware
- Utrzymuj ma艂e i skoncentrowane funkcje middleware. Ka偶da funkcja middleware powinna mie膰 jedn膮 odpowiedzialno艣膰.
- U偶ywaj opisowych nazw dla funkcji middleware. Nazwa powinna jasno wskazywa膰, co robi middleware.
- Prawid艂owo obs艂uguj b艂臋dy. Zawsze przechwytuj b艂臋dy i przekazuj je do middleware obs艂ugi b艂臋d贸w za pomoc膮
next(error). - U偶ywaj niestandardowych typ贸w 偶膮da艅/odpowiedzi, aby zwi臋kszy膰 bezpiecze艅stwo typ贸w. W razie potrzeby rozszerzaj interfejsy
RequestiResponseo niestandardowe w艂a艣ciwo艣ci. - U偶ywaj fabryk middleware do konfigurowania middleware z okre艣lonymi opcjami.
- Dokumentuj funkcje middleware. Wyja艣nij, co robi middleware i jak nale偶y go u偶ywa膰.
- Dok艂adnie przetestuj funkcje middleware. Napisz testy jednostkowe, aby upewni膰 si臋, 偶e funkcje middleware dzia艂aj膮 poprawnie.
Wnioski
TypeScript znacznie poprawia rozw贸j middleware Express, dodaj膮c statyczne typowanie, poprawiaj膮c 艂atwo艣膰 utrzymania kodu i wcze艣nie wychwytuj膮c b艂臋dy. Opanowuj膮c zaawansowane wzorce typ贸w, takie jak niestandardowe typy 偶膮da艅/odpowiedzi, fabryki middleware, asynchroniczne middleware, middleware obs艂ugi b艂臋d贸w i middleware walidacji 偶膮da艅, mo偶esz budowa膰 solidne, skalowalne i bezpieczne pod wzgl臋dem typ贸w aplikacje Express. Pami臋taj, aby przestrzega膰 najlepszych praktyk, aby funkcje middleware by艂y ma艂e, skoncentrowane i dobrze udokumentowane.