En omfattende guide til at forstå og implementere TypeScript middleware i Express.js applikationer. Udforsk avancerede type-mønstre for robust og vedligeholdelig kode.
TypeScript Middleware: Mestring af Type-mønstre for Express Middleware
Express.js, et minimalistisk og fleksibelt Node.js webapplikationsframework, giver udviklere mulighed for at bygge robuste og skalerbare API'er og webapplikationer. TypeScript forbedrer Express ved at tilføje statisk typning, forbedre kodevedligeholdelsen og fange fejl tidligt. Middleware-funktioner er en hjørnesten i Express, der giver dig mulighed for at opsnappe og behandle anmodninger, før de når dine rutehandlers. Denne artikel udforsker avancerede TypeScript type-mønstre til at definere og udnytte Express middleware, hvilket forbedrer typesikkerheden og kodeklarheden.
Forståelse af Express Middleware
Middleware-funktioner er funktioner, der har adgang til anmodningsobjektet (req), svarobjektet (res) og den næste middleware-funktion i applikationens anmodning-svar-cyklus. Middleware-funktioner kan udføre følgende opgaver:
- Udfør enhver kode.
- Foretag ændringer i anmodningen og svarobjekterne.
- Afslut anmodning-svar-cyklussen.
- Kald den næste middleware-funktion i stakken.
Middleware-funktioner udføres sekventielt, efterhånden som de tilføjes til Express-applikationen. Almindelige anvendelsestilfælde for middleware inkluderer:
- Logning af anmodninger.
- Autentificering af brugere.
- Autorisering af adgang til ressourcer.
- Validering af anmodningsdata.
- Håndtering af fejl.
Grundlæggende TypeScript Middleware
I en grundlæggende TypeScript Express-applikation kan en middleware-funktion se sådan ud:
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;
Denne simple middleware logger anmodningsmetoden og URL'en til konsollen. Lad os nedbryde typeannotationerne:
Request: Repræsenterer Express-anmodningsobjektet.Response: Repræsenterer Express-svarobjektet.NextFunction: En funktion, der, når den kaldes, udfører den næste middleware i stakken.
Du kan bruge denne middleware i din Express-applikation som dette:
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}`);
});
Avancerede Type-mønstre for Middleware
Mens det grundlæggende middleware-eksempel er funktionelt, mangler det fleksibilitet og typesikkerhed for mere komplekse scenarier. Lad os udforske avancerede type-mønstre, der forbedrer middleware-udvikling med TypeScript.
1. Brugerdefinerede Anmodnings-/Svarstyper
Ofte skal du udvide Request- eller Response-objekterne med brugerdefinerede egenskaber. For eksempel, efter autentificering, vil du måske tilføje en user-egenskab til Request-objektet. TypeScript giver dig mulighed for at udvide eksisterende typer ved hjælp af deklarationsfletning.
// 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
I dette eksempel udvider vi Express.Request-interfacet til at inkludere en valgfri user-egenskab. Nu, i din autentificeringsmiddleware, kan du udfylde denne egenskab:
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;
Og i dine rutehandlers kan du sikkert få adgang til req.user-egenskaben:
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-fabrikker
Middleware-fabrikker er funktioner, der returnerer middleware-funktioner. Dette mønster er nyttigt, når du skal konfigurere middleware med specifikke indstillinger eller afhængigheder. Overvej for eksempel en logningsmiddleware, der logger meddelelser til en bestemt fil:
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;
Du kan bruge denne middleware-fabrik som dette:
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. Asynkron Middleware
Middleware-funktioner skal ofte udføre asynkrone operationer, såsom databaseforespørgsler eller API-kald. For at håndtere asynkrone operationer korrekt skal du sikre, at next-funktionen kaldes, efter at den asynkrone operation er fuldført. Du kan opnå dette ved hjælp af async/await eller 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;
Vigtigt: Husk at håndtere fejl i din asynkrone middleware og send dem til fejlhåndteringsmiddlewaren ved hjælp af next(error). Dette sikrer, at fejl håndteres og logges korrekt.
4. Fejlhåndteringsmiddleware
Fejlhåndteringsmiddleware er en speciel type middleware, der håndterer fejl, der opstår under anmodning-svar-cyklussen. Fejlhåndteringsmiddleware-funktioner har fire argumenter: err, req, res og 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;
Du skal registrere fejlhåndteringsmiddleware efter al anden middleware og rutehandlers. Express identificerer fejlhåndteringsmiddleware ved tilstedeværelsen af de fire argumenter.
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. Anmodningsvalideringsmiddleware
Anmodningsvalidering er et afgørende aspekt af at bygge sikre og pålidelige API'er. Middleware kan bruges til at validere indgående anmodningsdata og sikre, at de opfylder visse kriterier, før de når dine rutehandlers. Biblioteker som joi eller express-validator kan bruges til anmodningsvalidering.
Her er et eksempel ved hjælp af 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;
Denne middleware validerer felterne email og password i anmodningsbodyen. Hvis valideringen mislykkes, returnerer den et 400 Bad Request-svar med en række fejlmeddelelser. Du kan bruge denne middleware i dine rutehandlers som dette:
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. Dependency Injection til Middleware
Når dine middleware-funktioner er afhængige af eksterne tjenester eller konfigurationer, kan dependency injection hjælpe med at forbedre testbarheden og vedligeholdelsen. Du kan bruge en dependency injection container som tsyringe eller blot sende afhængigheder som argumenter til dine middleware-fabrikker.
Her er et eksempel ved hjælp af en middleware-fabrik med dependency injection:
// 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}`);
});
Bedste praksis for TypeScript Middleware
- Hold middleware-funktioner små og fokuserede. Hver middleware-funktion bør have et enkelt ansvar.
- Brug beskrivende navne til dine middleware-funktioner. Navnet skal tydeligt angive, hvad middlewaren gør.
- Håndter fejl korrekt. Fang altid fejl og send dem til fejlhåndteringsmiddlewaren ved hjælp af
next(error). - Brug brugerdefinerede anmodnings-/svarstyper til at forbedre typesikkerheden. Udvid
Request- ogResponse-interfaces med brugerdefinerede egenskaber efter behov. - Brug middleware-fabrikker til at konfigurere middleware med specifikke indstillinger.
- Dokumenter dine middleware-funktioner. Forklar, hvad middlewaren gør, og hvordan den skal bruges.
- Test dine middleware-funktioner grundigt. Skriv enhedstests for at sikre, at dine middleware-funktioner fungerer korrekt.
Konklusion
TypeScript forbedrer udviklingen af Express middleware betydeligt ved at tilføje statisk typning, forbedre kodevedligeholdelsen og fange fejl tidligt. Ved at mestre avancerede type-mønstre som brugerdefinerede anmodnings-/svarstyper, middleware-fabrikker, asynkron middleware, fejlhåndteringsmiddleware og anmodningsvalideringsmiddleware, kan du bygge robuste, skalerbare og typesikre Express-applikationer. Husk at følge bedste praksis for at holde dine middleware-funktioner små, fokuserede og veldokumenterede.