How to properly use a .d.ts files

If you’ve ever need a global type to be available or perhaps already have a few, then

There are several cases in Node where we may want to extend interfaces, a common such example is when using the library passport that often recommends appending a user object to our express Request object.

Using Typescript, this will often throw an error that user doesn’t exist on object Request, so let’s see how we can change that in order to satisfy the TS compiler.

What is the core issue?

At its root, the problem arises because the Typescript compiler is unable to recognize the user object, and that it should exist on the req object.

This could be true for anything we choose to add onto the req object, any extra properties, objects and so on.

In my projects, I usually use Json Web Token to authorize users, and on my backends I handle that by appending a decoded object to req that contains core information about the authorized user.

So I frequently have this issue, let’s take a look at my preferred way of solving it, which is using a d.ts file to create a global type for the object property.

What the hoot is a d.ts file?

A d.ts file is simply a Typescript convention known as a declaration file.

I recommend you give the official typescript handbook a read through to fully understand the concept, particularly the “do’s and dont’s” section.

What we can use a declaration file for, is to make certain types globally available to Typescript, in order to not only make compilation pass, but also offer the editor language server the types, to ensure type safety, while you’re writing code.

The cool thing about them is that you don’t need to import them anywhere, and they don’t live with your compiled code. They are simply here to provide your language server some peace of mind, that you actually are typing the right things.

If you are familiar with the Definitely Typed repository, this is a lot like that!

Makes sense? Let’s hop into how we might create one!

How I set up a global interface using a d.ts file

Since there are probably numerous ways to go about this, and all roads lead to Rome and all that, why don’t I just show you how I did it?

Step 1: Creating the types folder

So at the root of my project I maintain a types folder, this is also often called typings if you prefer.

At the time of its inception this was built for being extended, so I another nested folder common in which we’ll find three files:

  • package.json (not the one for the project, this is one for types!)
  • main.d.ts
  • interfaces.d.ts

These are here because we expected we’d be building a lot of global times, which time has shown was not necessary, as most types are tightly coupled with a data-model.

All the main.d.ts file does is import all the other types, as so:

// types/common/main.d.ts

/// <reference path="./interfaces.d.ts" />
// Would have more types here if we had any

Step 2: The types package.json

This file contains a simple reference to our main.d.ts file, to explain to our editor that there is a typings file present. You can either use types or typings property in the package.json file to set this. It’s a really short file looking like this:

{
    "name": "common",
    "version": "1.0.0",
    "typings": "main.d.ts"
  }

Remember, this file is not out primary package.json but rather a new one that I have inserted in types/common/package.json.

That’s all there is to it!

… Well except for the next two steps, so lets jump ahead 🙂

Step 3: Create our interfaces.d.ts

The next thing you’ll do is to create an interfaces.d.ts, exactly like I did.

For the example here we are going to add the decoded object to the express req object, as previously mentioned.

Here it is:

// types/common/interfaces.d.ts

import User, { userRoles } from "src/common/entities/user.entity";

export {}

declare global {
	namespace Express {
		export interface Request extends express.Request {
			decoded: ReqDecoded;
			email: string;
			file: any;
			files: any;
			fileKey: any;

			inErr: any;
		}
	}
}

type ReqDecoded = {
	department_id: User['department_id'];
	user_role: userRoles;
	user_id: User['user_id'];
	org_id: User['org_id'];
	original_org_id: string;
	token_identifier: string;
};

As you can tell, not only do we declare the decoded object on the express Request, but also a few other properties.

Admittedly, the file and files are most likely not even necessary when using @types/multer so I should probably look into removing those…

The cool thing here is that we are able to rely on types defined elsewhere in the application, as is the case when I’m defining the type of User properties.

This is super exciting because it means if we are to change the type of say, the department_id, our IDE will immediately flag all the wrong uses of this property!

Step 4: Setting up our tsconfig.json

In order for the types to be registered by the typescript language server, and compiler, we need to tell it about our new type declarations.

The last thing (I promise, this is the last thing) we’ll need to configure for this to work, is to tell typescript about the existence of our declaration file(s).

This can be done in a few ways, but I recommend using the typeRoots property. Setting the types property, as another viable option, requires adding an extra import just to get back to the default functionality of importing any types from node_modules, which I’m not a fan of.

So in your ts.config simple add the following:

"compilerOptions": {
    "typeRoots": [
      "./types"
    ]
  }

Obviousy, if you already have a compilerOptions section (which you most likely do), just add the typeRoots array and you are good to go 🙂

That’s it!

That’s literally all there is to it. If you find any snippets where your req.decoded was being used, and improperly flagged for not existing, that error should now have been quelched.

Not only that, but Typescript is also aware of its existence and contents, meaning you will get type safety, offering a number of options as soon as you type req.decoded anywhere within your codebase.

Neat, right?

Hope you enjoyed this article! If it was in any way helpful and you are looking to support or further level up your Typescript game, consider subscribing to the newsletter below.

Pssst! Yeah you! If you are wondering when you should be using global type declarations, or just keen on the topic, please proceed to my next article on the topic 👈