Extend Request Typescript Interface Custom Properties
| |

How To Extend Express Request Interface in Typescript

The problem

You will likely be caught in this situation when you create middleware that processes the request data before it reaches the resource route.

An example of this is JWT, where you will need to decode the token before every request, and put in the decoded data somewhere.

In Javascript, it would have been a simple effort to add an additional property to the request object, as it does not need to strictly follow a certain interface.

However, the story is different in typescript:

express request interface typescript not extended
we cannot create our own custom property in the request object by default

Our Sample Express Project

To start things off, we will have a really simple typescript express app:

index.ts mounts our router and starts the express server.

import express from "express";
import { usersRouter } from "./routes/usersRouter";

const app = express();

app.use("/api/v1", usersRouter);

app.listen(4321, () => {
  console.log("🚀 server started at port 4321");
});

routes/usersRouter.ts has a sample get request.

import express from "express";

export const usersRouter = express.Router();

usersRouter.get("/users", (request, response) => {
  response.status(200).json({});
});

middleware/parseToken.ts is a middleware that will mimic the behavior of parsing a token.

import { Request, Response, NextFunction } from "express";

export const parseToken =
  () => (request: Request, response: Response, next: NextFunction) => {
    // hypothetical token decode
    const tokenData = {
      userId: "7489234",
      iat: "857349534",
    };

    next();
  };

Here is the project starter on Github for you to follow along:


https://github.com/plusreturn/express-request-extend
0 forks.
0 stars.
0 open issues.

Recent commits:

Extend the Request Interface for a Single Request

This can work if we are going to extend the interface for a single or a few requests. All we need to do is just to extend express’ Request interface:

import { Request, Response, NextFunction } from "express";

export interface TokenData {
  userId: string;
  iat: string;
}

export interface TokenRequest extends Request {
  tokenData: TokenData;
}

export const parseToken =
  () => (request: TokenRequest, response: Response, next: NextFunction) => {
    // hypothetical token decode
    const tokenData = {
      userId: "7489234",
      iat: "857349534",
    };

    request.tokenData = tokenData;

    next();
  };

This solution works, but it’s not the best approach for this problem. You can quickly see how tedious this can be once we have to explicitly cast the request object with our TokenRequest interface for every route we will have in our app.

Extend the request interface to the entire app

This is the better approach, with it, we can modify the Request interface for the entire app. To do so, we will need to create a file called index.d.ts to achieve this.

Extending Express Request Interface with our own index.d.ts

So what we are going to do is to extend Express’ existing Request interface by creating index.d.ts at our root directory,

with the following code:

import { Express } from "express-serve-static-core";

declare module "express-serve-static-core" {
  interface Request {}
}

so far, we haven’t modified anything yet.

For our purpose, we want an additional property called tokenData to store our token’s information. To do so, we will add the following to the Request interface:

import { Express } from "express-serve-static-core";

interface TokenData {
  userId: string;
  iat: string;
}

declare module "express-serve-static-core" {
  interface Request {
    tokenData: TokenData;
  }
}

doing this will immediately add the tokenData property to our express Request interface. Typescript does this by merging the two interfaces definitions of Express’ original one, and our customized one.

now if we try to access the property, then ESlint will not complain about it not existing, as well as it will show us all of tokenData properties!

typescript express interface with custom properties extended

if our new property is still not accessible to you, then I recommend going over the Troubleshooting section.

Pitfalls

remember that we are only extending the interface and not the object itself, so if you were to do a nested property like this one:

import { Express } from "express-serve-static-core";

interface TokenData {
  userId: string;
  iat: string;
}

interface Context {
  tokenData: TokenData;
}

declare module "express-serve-static-core" {
  interface Request {
    context: Context;
  }
}

and attempted to add your value in the middleware like this:

import { Request, Response, NextFunction } from "express";

export const parseToken =
  () => (request: Request, response: Response, next: NextFunction) => {
    // hypothetical token decode
    const tokenData = {
      userId: "7489234",
      iat: "857349534",
    };

    request.context.tokenData = tokenData; // ❌ this will not work

    next();
  };

then it will not work. This is because the property context hasn’t been defined yet.

to work around this issue, you can use the spread operator.

import { Request, Response, NextFunction } from "express";

export const parseToken =
  () => (request: Request, response: Response, next: NextFunction) => {
    // hypothetical token decode
    const tokenData = {
      userId: "7489234",
      iat: "857349534",
    };

    request.context = { ...request.context, tokenData: tokenData }; // âś… this will work

    next();
  };

while the spread operator is not exactly required for this particular middleware, it’s better to build the habit to use it early to make sure that you do not overwrite existing properties when you are adding new ones.

I have talked about the spread and rest operators in much more depth in my blog post “6 Awesome Tricks with the Spread and Rest Operators in Typescript and Javascript Objects”, and I highly recommend going over it to understand this concept better.

What is index.d.ts?

index.d.ts is a file created for modules that were written in JavaScript. Express would be one of them, which is why we also need to install @types/express for our Typescript projects to be able to read the module without any typing errors.

In simple terms, that file has interfaces and types that relate to the objects that exist in the Javascript project and adds typings for them.

for example, If we check our node_modules directory, we can find the index.d.ts for express in node_modules/@types/express/index.d.ts

This helped modules developers to add typescript support for their modules without the need to rewrite the entire project in typescript. This is something you would usually see in modules/packages that were developed before typescript was a thing.

Why express-serve-static-core, not express?

this is actually where the Express namespace is initially declared.

you can see that the developers left a comment indicating that they designed it that way so it can be extendable.

express module actually imports that namespace from there.

index.d.ts Location

the location of index.d.ts doesn’t really matter, as long as it is in the project’s directory, of course. Typescript has a glob of **/* for that file. meaning it will try to crawl the entire project’s directory to look for it.

for fanciness sake, I like to keep the index.d.ts file in a folder that mimics the type definition file that it is extending.

in this case, it will be @types/express/index.ds.ts.

of course, if you still have issues that were solved by typeRoots configuration, then you will need to update that one as well.

index.d.ts Name

The file doesn’t have to be named index.d.ts either. All that typescript cares about is the .d.ts extension. we can call the file chocolate_cake.d.ts and it would still work.

but once again, we will pick a proper name for clarity. index.d.ts is the name you would expect for a types definition file.

Troubleshooting

typeRoots config

tsconfig.json has an option called typeRoots, and we can use it to explicitly tell it where our index.d.ts file is if it fails to find it for whatever reason.

if your index.d.ts file is at the root of the project like the beginning of this tutorial, then you can set it like so:

...
"typeRoots": ["index.d.ts"],
...

however, if your index.d.ts is like how I suggested to put in in the location section, then the configuration will be like so:

...
"typeRoots": ["@types/express/index.d.ts"],
...

Restart TS Server

If you are using VSCode, then the changes might not apply immediately. This depends on the size of the project. A good way to make sure your typescript configuration gets applied is to restart the ts server for the project.

We do this by pressing F1 in VSCode, then typing “restart ts” and selecting the option “Typescript: Restart TS Server”.

restart ts server

Conclusion

Trying to figure out how to extend the Express Request interface has been pretty informative. We were able to learn about the index.d.ts file that we find in modules and the one that we create ourselves.

Sing up to get an email notification when new content is published

Leave a Reply

Your email address will not be published. Required fields are marked *

Similar Posts