Question
How can I add a custom property to the Express Request object from middleware in TypeScript, so that I can use dot notation such as req.property rather than bracket notation?
app.use((req, res, next) => {
req.property = setProperty();
next();
});
Short Answer
You will learn how TypeScript declaration merging extends Express's Request type, how middleware adds the value at runtime, and how to ensure the property is available before a route uses it.
Concept
Express request objects are ordinary JavaScript objects. A middleware can attach a new property at runtime:
req.property = setProperty();
However, TypeScript checks code against type definitions. Express's built-in Request type does not know that property exists, so TypeScript reports an error even though JavaScript would allow the assignment.
The solution is declaration merging. You add a type declaration that tells TypeScript: “Every Express request may include this property.” TypeScript combines your interface declaration with Express's existing Request interface.
This has two separate parts:
- Type declaration: makes
req.propertyvalid to TypeScript. - Middleware assignment: actually puts a value on the request while the application runs.
The declaration does not create the property by itself. Middleware does.
Mental Model
Think of an Express request as a delivery box moving through a conveyor belt of middleware.
- Middleware can place a label or item in the box:
req.property = value. - TypeScript is the inventory system that tracks what a box is allowed to contain.
- Declaration merging updates the inventory system so it recognizes the new item.
A later route can safely use the item only if the middleware that adds it ran earlier on the conveyor belt.
Syntax and Examples
Create a declaration file, for example src/types/express.d.ts:
export {};
declare global {
namespace Express {
interface Request {
property?: string;
}
}
}
Then add the value in middleware:
import express from "express";
const app = express();
function setProperty(): string {
return "value created by middleware";
}
app.use((req, res, next) => {
req.property = setProperty();
next();
});
app.get("/", (req, res) => {
res.json({ property: req.property });
});
property?: string means the property is optional. This is usually accurate because not every request is guaranteed to pass through the middleware.
Step by Step Execution
Consider this request flow:
app.use((req, res, next) => {
req.requestId = "req-123";
next();
});
app.get("/orders", (req, res) => {
res.json({ id: req.requestId });
});
Assuming requestId was declared on Express.Request, this is what happens:
- A client requests
GET /orders. - Express creates a request object for this request.
- The first middleware runs and assigns
"req-123"toreq.requestId. next()passes the same request object to the next matching handler.- The
/ordersroute receives that same object, includingrequestId. - The route returns
{ "id": "req-123" }.
If the route were registered before the middleware, or mounted outside the middleware's router path, req.requestId might not have been assigned yet.
Real World Use Cases
Custom request properties are useful for data that middleware prepares for later handlers:
- Authentication: attach the authenticated user as
req.user. - Request tracing: attach a unique
req.requestIdfor logs and error reports. - Multi-tenant applications: attach the selected
req.tenantafter reading the host name or token. - Locale selection: attach
req.localeafter parsing headers or cookies. - Authorization context: attach permissions or roles after validating an access token.
For example, a request ID middleware can make all logs for one HTTP request easy to find:
app.use((req, res, next) => {
req.requestId = crypto.randomUUID();
next();
});
Real Codebase Usage
In production code, developers usually combine declaration merging with middleware ordering and defensive checks.
Authentication middleware
app.use((req, res, next) => {
const user = findUserFromToken(req.headers.authorization);
if (!user) {
res.status(401).json({ error: "Unauthorized" });
return;
}
req.user = user;
next();
});
Protected route with a guard clause
Even when a type declares user as optional, a route can validate it before use:
app.get("/profile", (req, res) => {
if (!req.user) {
res.status(401).json({ error: "Unauthorized" });
return;
}
res.json({ name: req.user.name });
});
Common Mistakes
Adding the value without extending the type
This runs in JavaScript but fails TypeScript checking:
app.use((req, res, next) => {
req.property = "hello"; // Property 'property' does not exist on type 'Request'
next();
});
Add a .d.ts declaration file rather than using as any.
Using as any to silence the error
(req as any).property = "hello";
This removes useful type checking. A typo such as req.proprety would no longer be caught. Declaration merging preserves dot notation and type safety.
Forgetting that the property may be missing
If the declaration uses property?: string, this is unsafe:
const upper = req.property.toUpperCase();
Use a guard or a fallback:
Comparisons
| Approach | What it does | When to use it |
|---|---|---|
| Declaration merging | Extends Express's global Request type | A property is used across multiple handlers or files |
req as any | Disables checking for that access | Avoid; it hides mistakes |
| Local intersection type | Types one particular handler's request | Useful for narrowly scoped code, but less convenient across an app |
res.locals | Stores request-scoped values on the response object | Values intended for later middleware or template rendering |
| Passing function arguments | Makes dependencies explicit | Business logic outside Express handlers |
A local type can look like this:
type RequestWithProperty = . & {
: ;
};
Cheat Sheet
// src/types/express.d.ts
export {};
declare global {
namespace Express {
interface Request {
requestId?: string;
}
}
}
// Add the runtime value before dependent routes
app.use((req, res, next) => {
req.requestId = crypto.randomUUID();
next();
});
// Safely read an optional property
if (req.requestId) {
console.log(req.requestId);
}
Rules:
- A
.d.tsfile changes TypeScript's knowledge, not the runtime object. - Middleware must assign the property before another handler reads it.
- Use
?:unless every relevant request is guaranteed to have the value. - Keep request property names specific, such as
user,tenant, or .
FAQ
How do I add a property to Express Request in TypeScript?
Create a .d.ts file that augments Express.Request, then assign the property in middleware. Declaration merging enables typed dot notation such as req.user.
Why does TypeScript say a property does not exist on Request?
Express's original type definitions do not include your custom field. TypeScript needs a declaration merge before it accepts the property.
Does declaration merging add the property at runtime?
No. It only changes type information. Your middleware must assign the value during request handling.
Should my custom request property be optional?
Usually, yes. Make it required only when your routing structure guarantees that the assigning middleware always runs first.
Where should the Express declaration file go?
A common location is src/types/express.d.ts. Any location is fine as long as TypeScript includes it in the project.
Can I use req.property without bracket notation?
Yes. After augmenting Express.Request, TypeScript recognizes normal dot notation.
When should I use res.locals instead of extending req?
Mini Project
Description
Build a request-tracing feature for an Express API. A middleware creates a unique request ID, attaches it to every request, and returns it in both logs and API responses. This is a small version of the tracing context used to investigate production errors.
Goal
Create a typed req.requestId property that is assigned by middleware and used by an API route.
Requirements
Requirement 1 Requirement 2 Requirement 3
Keep learning
Related questions
@Directive vs @Component in Angular: Differences, Use Cases, and When to Use Each
Learn the difference between @Directive and @Component in Angular, including use cases, examples, and when to choose each.
Accessing Input Value from EventTarget in TypeScript
Learn why EventTarget has no value property in TypeScript and safely read values from HTML input events in Angular applications.
Angular (change) vs (ngModelChange): What’s the Difference?
Learn the difference between Angular (change) and (ngModelChange), when each fires, and which one to use in forms and inputs.