Een uitgebreide handleiding voor het begrijpen en implementeren van TypeScript middleware in Express.js applicaties. Verken geavanceerde type patronen voor robuuste en onderhoudbare code.
TypeScript Middleware: Express Middleware Type Patronen Beheersen
Express.js, een minimalistisch en flexibel Node.js web applicatie framework, stelt ontwikkelaars in staat om robuuste en schaalbare API's en web applicaties te bouwen. TypeScript verbetert Express door statische typing toe te voegen, de onderhoudbaarheid van de code te verbeteren en fouten vroegtijdig op te sporen. Middleware functies vormen een hoeksteen van Express, waardoor u requests kunt onderscheppen en verwerken voordat ze de route handlers bereiken. Dit artikel onderzoekt geavanceerde TypeScript type patronen voor het definiƫren en gebruiken van Express middleware, waardoor de type veiligheid en de code duidelijkheid worden verbeterd.
Express Middleware Begrijpen
Middleware functies zijn functies die toegang hebben tot het request object (req), het response object (res) en de volgende middleware functie in de request-response cyclus van de applicatie. Middleware functies kunnen de volgende taken uitvoeren:
- Code uitvoeren.
- Wijzigingen aanbrengen in het request en het response object.
- De request-response cyclus beƫindigen.
- De volgende middleware functie in de stack aanroepen.
Middleware functies worden opeenvolgend uitgevoerd zoals ze aan de Express applicatie worden toegevoegd. Veel voorkomende use cases voor middleware zijn:
- Requests loggen.
- Gebruikers authenticeren.
- Toegang tot resources autoriseren.
- Request data valideren.
- Fouten afhandelen.
Basis TypeScript Middleware
In een basis TypeScript Express applicatie kan een middleware functie er als volgt uitzien:
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;
Deze eenvoudige middleware logt de request method en URL naar de console. Laten we de type annotaties eens nader bekijken:
Request: Vertegenwoordigt het Express request object.Response: Vertegenwoordigt het Express response object.NextFunction: Een functie die, indien aangeroepen, de volgende middleware in de stack uitvoert.
U kunt deze middleware als volgt in uw Express applicatie gebruiken:
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}`);
});
Geavanceerde Type Patronen voor Middleware
Hoewel het basis middleware voorbeeld functioneel is, mist het flexibiliteit en type veiligheid voor complexere scenario's. Laten we geavanceerde type patronen verkennen die de middleware ontwikkeling met TypeScript verbeteren.
1. Custom Request/Response Types
Vaak moet u de Request of Response objecten uitbreiden met custom properties. Na authenticatie wilt u bijvoorbeeld een user property aan het Request object toevoegen. TypeScript stelt u in staat om bestaande types uit te breiden met behulp van 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
In dit voorbeeld breiden we de Express.Request interface uit met een optionele user property. Nu kunt u in uw authenticatie middleware deze property vullen:
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;
En in uw route handlers kunt u veilig toegang krijgen tot de req.user property:
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 Factories
Middleware factories zijn functies die middleware functies retourneren. Dit patroon is handig wanneer u middleware moet configureren met specifieke opties of dependencies. Denk bijvoorbeeld aan een logging middleware die berichten naar een specifiek bestand logt:
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;
U kunt deze middleware factory als volgt gebruiken:
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. Asynchrone Middleware
Middleware functies moeten vaak asynchrone bewerkingen uitvoeren, zoals database queries of API calls. Om asynchrone bewerkingen correct af te handelen, moet u ervoor zorgen dat de next functie wordt aangeroepen nadat de asynchrone bewerking is voltooid. U kunt dit bereiken met behulp van async/await of 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;
Belangrijk: Vergeet niet om fouten binnen uw asynchrone middleware af te handelen en deze door te geven aan de error handling middleware met behulp van next(error). Dit zorgt ervoor dat fouten correct worden afgehandeld en gelogd.
4. Error Handling Middleware
Error handling middleware is een speciaal type middleware dat fouten afhandelt die optreden tijdens de request-response cyclus. Error handling middleware functies hebben vier argumenten: err, req, res en 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;
U moet error handling middleware na alle andere middleware en route handlers registreren. Express identificeert error-handling middleware door de aanwezigheid van de vier argumenten.
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. Request Validation Middleware
Request validatie is een cruciaal aspect van het bouwen van veilige en betrouwbare API's. Middleware kan worden gebruikt om inkomende request data te valideren en ervoor te zorgen dat deze aan bepaalde criteria voldoet voordat deze uw route handlers bereikt. Bibliotheken zoals joi of express-validator kunnen worden gebruikt voor request validatie.
Hier is een voorbeeld met 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;
Deze middleware valideert de email en password velden in de request body. Als de validatie mislukt, retourneert het een 400 Bad Request response met een array van foutmeldingen. U kunt deze middleware als volgt in uw route handlers gebruiken:
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 voor Middleware
Wanneer uw middleware functies afhankelijk zijn van externe services of configuraties, kan dependency injection helpen om de testbaarheid en onderhoudbaarheid te verbeteren. U kunt een dependency injection container zoals tsyringe gebruiken of eenvoudigweg dependencies doorgeven als argumenten aan uw middleware factories.
Hier is een voorbeeld met behulp van een middleware factory met 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}`);
});
Best Practices voor TypeScript Middleware
- Houd middleware functies klein en gefocust. Elke middleware functie moet een enkele verantwoordelijkheid hebben.
- Gebruik beschrijvende namen voor uw middleware functies. De naam moet duidelijk aangeven wat de middleware doet.
- Handel fouten correct af. Vang altijd fouten op en geef ze door aan de error handling middleware met behulp van
next(error). - Gebruik custom request/response types om de type veiligheid te verbeteren. Breid de
RequestenResponseinterfaces indien nodig uit met custom properties. - Gebruik middleware factories om middleware te configureren met specifieke opties.
- Documenteer uw middleware functies. Leg uit wat de middleware doet en hoe deze moet worden gebruikt.
- Test uw middleware functies grondig. Schrijf unit tests om ervoor te zorgen dat uw middleware functies correct werken.
Conclusie
TypeScript verbetert de ontwikkeling van Express middleware aanzienlijk door statische typing toe te voegen, de onderhoudbaarheid van de code te verbeteren en fouten vroegtijdig op te sporen. Door geavanceerde type patronen zoals custom request/response types, middleware factories, asynchrone middleware, error handling middleware en request validatie middleware te beheersen, kunt u robuuste, schaalbare en type-veilige Express applicaties bouwen. Vergeet niet om best practices te volgen om uw middleware functies klein, gefocust en goed gedocumenteerd te houden.