Overriding the Express API
The Express API consists of various methods and properties on the request and response objects. These are inherited by prototype. There are two extension points for the Express API:
- The global prototypes at
express.requestandexpress.response. - App-specific prototypes at
app.requestandapp.response.
Altering the global prototypes will affect all loaded Express apps in the same process. If desired, alterations can be made app-specific by only altering the app-specific prototypes after creating a new app.
Methods
You can override the signature and behavior of existing methods with your own, by assigning a custom function.
Following is an example of overriding the behavior of res.sendStatus.
app.response.sendStatus = function (statusCode, type, message) { // code is intentionally kept simple for demonstration purpose return this.contentType(type).status(statusCode).send(message);};import { type Response } from 'express';
// Broaden the type of sendStatus so call sites accept the new arguments.declare module 'express-serve-static-core' { interface Response { sendStatus(statusCode: number, type: string, message: string): this; }}
app.response.sendStatus = function ( this: Response, statusCode: number, type: string, message: string) { // code is intentionally kept simple for demonstration purpose return this.contentType(type).status(statusCode).send(message);} as Response['sendStatus'];The above implementation completely changes the original signature of res.sendStatus. It now accepts a status code, encoding type, and the message to be sent to the client.
In TypeScript, augmenting express-serve-static-core adds the new overload to the Response type so call sites such as res.sendStatus(404, 'application/json', body) type-check. The assignment is cast with as Response['sendStatus'] because replacing a method with a different signature cannot be checked against the original declaration.
The overridden method may now be used this way:
res.sendStatus(404, 'application/json', '{"error":"resource not found"}');Properties
Properties in the Express API are either:
- Assigned properties (ex:
req.baseUrl,req.originalUrl) - Defined as getters (ex:
req.secure,req.ip)
Since properties under category 1 are dynamically assigned on the request and response objects in the context of the current request-response cycle, their behavior cannot be overridden.
Properties under category 2 can be overwritten using the Express API extensions API.
The following code rewrites how the value of req.ip is to be derived. Now, it simply returns the value of the Client-IP request header.
Object.defineProperty(app.request, 'ip', { configurable: true, enumerable: true, get() { return this.get('Client-IP'); },});import { type Request } from 'express';
Object.defineProperty(app.request, 'ip', { configurable: true, enumerable: true, get(this: Request) { return this.get('Client-IP'); },});Extending the API in TypeScript
The sections above override members that Express already provides. To add your own properties or
methods to the request or response, describe them to TypeScript with
declaration merging on the
Express namespace. Put the augmentation in a .d.ts file that is part of your project. No
tsconfig.json change is needed unless a custom include does not cover its location.
For example, an authentication middleware may attach a user to the request, and you might add a
sendError helper to the response:
interface User { id: string; name: string;}
declare global { namespace Express { interface Request { user?: User; } interface Response { sendError(status: number, message: string): this; } }}
export {};The additions are now known throughout the application:
import { type Request, type Response, type NextFunction } from 'express';
app.use((req: Request, res: Response, next: NextFunction) => { req.user = { id: '1', name: 'Tobi' }; next();});
app.response.sendError = function (this: Response, status: number, message: string) { return this.status(status).json({ error: message });};
app.get('/', (req: Request, res: Response) => { if (!req.user) { res.sendError(401, 'unauthorized'); return; } res.send(req.user.name);});Caution
Declare custom request properties as optional (user?). The type applies to every request, but
TypeScript cannot know which middleware ran before a given handler, so a required property would be a
false guarantee. Check for the value (as with if (!req.user) above) before relying on it.
Adding a new method needs no cast, unlike overriding an existing method with a different signature, because the method did not previously exist on the type.
Prototype
In order to provide the Express API, the request/response objects passed to Express (via app(req, res), for example) need to inherit from the same prototype chain. By default, this is http.IncomingRequest.prototype for the request and http.ServerResponse.prototype for the response.
Unless necessary, it is recommended that this be done only at the application level, rather than globally. Also, take care that the prototype that is being used matches the functionality as closely as possible to the default prototypes.
// Use FakeRequest and FakeResponse in place of http.IncomingRequest and http.ServerResponse// for the given app referenceObject.setPrototypeOf(Object.getPrototypeOf(app.request), FakeRequest.prototype);Object.setPrototypeOf(Object.getPrototypeOf(app.response), FakeResponse.prototype);