Extending Types
Extending Types
This page documents how to extend Express request types using TypeScript declaration merging, how to attach a `requestId` to each incoming request, and how to integrate Azure authentication in a clean, layered architecture.
1. Extending Express Request Types
TypeScript allows you to extend existing library types using declaration merging. This is useful when you want to attach additional metadata to the Express `Request` object.
Example: Adding requestId to Express Request
Create a file such as:
presentation/express/types/express.d.ts
Add the following:
import "express-serve-static-core";
declare module "express-serve-static-core" {
interface Request {
requestId?: string;
}
}
This augments the existing Express `Request` interface so that TypeScript recognises `req.requestId` throughout your application.
2. Adding a Request ID Middleware
A `requestId` helps correlate logs, errors, and traces.
Middleware
import { v4 as uuid } from "uuid";
export function requestId(req, res, next) {
const id = uuid();
req.requestId = id;
res.setHeader("X-Request-ID", id);
next();
}
Usage Order in Express
app.use(requestId);
app.use(requestLogger);
app.use(morganConfig);
app.use(corsMiddleware);
app.use(express.json());
app.use("/api", router);
app.use(globalErrorHandler);
The request ID must be registered early so all downstream middleware and handlers can access it.
3. Structured Request Logging
A simple logger that includes the `requestId`:
export function requestLogger(req, res, next) {
const start = Date.now();
res.on("finish", () => {
const duration = Date.now() - start;
console.log(JSON.stringify({
requestId: req.requestId,
method: req.method,
path: req.originalUrl,
status: res.statusCode,
durationMs: duration
}));
});
next();
}
4. Global Error Handler
The global error handler belongs in the Presentation layer. It converts application-level errors into HTTP responses.
import {
AppValidationError,
AppDatabaseError,
AppUnknownError
} from "@dvdrental_api_ts/application/errors";
export function globalErrorHandler(err, req, res, next) {
const requestId = req.requestId;
if (err instanceof AppValidationError) {
return res.status(400).json({
error: "ValidationError",
message: err.message,
requestId
});
}
if (err instanceof AppDatabaseError) {
return res.status(500).json({
error: "DatabaseError",
message: "A database error occurred",
requestId
});
}
if (err instanceof AppUnknownError) {
return res.status(500).json({
error: "UnknownError",
message: "An unexpected error occurred",
requestId
});
}
return res.status(500).json({
error: "InternalServerError",
message: "Internal server error",
requestId
});
}
5. Azure Authentication Example
This example demonstrates how to authenticate incoming API requests using Azure Active Directory (Entra ID) and validate JWT tokens.
Installing Dependencies
npm install @azure/msal-node jsonwebtoken jwks-rsa
Azure JWT Validation Middleware
import jwt from "jsonwebtoken";
import jwksClient from "jwks-rsa";
const client = jwksClient({
jwksUri: `https://login.microsoftonline.com/<tenant-id>/discovery/v2.0/keys`
});
function getKey(header, callback) {
client.getSigningKey(header.kid, (err, key) => {
const signingKey = key.getPublicKey();
callback(null, signingKey);
});
}
export function azureAuth(requiredScope) {
return (req, res, next) => {
const authHeader = req.headers.authorization;
if (!authHeader) {
return res.status(401).json({ message: "Missing Authorization header" });
}
const token = authHeader.replace("Bearer ", "");
jwt.verify(
token,
getKey,
{
audience: "<client-id>",
issuer: `https://login.microsoftonline.com/<tenant-id>/v2.0`
},
(err, decoded) => {
if (err) {
return res.status(401).json({ message: "Invalid token" });
}
if (requiredScope && !decoded.scp?.includes(requiredScope)) {
return res.status(403).json({ message: "Insufficient scope" });
}
req.user = decoded;
next();
}
);
};
}
Protecting a Route
router.get("/secure-data", azureAuth("api.read"), (req, res) => {
res.json({ message: "Secure data", user: req.user });
});
6. Summary
This page covered:
- Extending Express request types using declaration merging
- Adding a `requestId` to each request
- Structured logging with correlation IDs
- A global error handler in the Presentation layer
- Azure authentication using JWT validation
These patterns help maintain a clean architecture while adding observability, traceability, and secure authentication to your Express application.