How to create custom types in Typescript

typescript Aug 31, 2019

Recently I've been trying some new ways to create my custom types in Typescript. This week specifically I had a revelation on the way I make my types.

Some people might say some of this is not how you define types, or that they're anit-patterns, but this is how I write my code, and hopefully I can shed some light on a new way to visualize creating types. And hopefully someone can point me to a new way to define my types too.

Express

We all know express. And we see

(req: Request, res: Response) => { ... }

a lot. But sometimes we want to add custom properties to express's Request interface, just like passport does with the user and authenticated properties. What I did up until recently was to extend the namespace Express in an index.d.ts file in the typings file within my source code. And then add that to the typings property in my tsconfig.json. And that's worked for me for well over a year. But then testing came up. And as soon as I tried to start writing tests, it caused an issue. Since the file was not within the scope of the test files, I continuously got a typescript compilation error complaining how Property 'id' does not exist on type Request. Well great. Bummer. So what was going to be my new way? I needed a way to write my types and not get this error.

as

I scrapped the index.d.ts idea, because it dawned on me that that's really for modules that aren't already typed. And then I remembered as. It's a handy keyword in Typescript that is used for a multitude of things, one of the most well known being casting. But there is also another way to use it too. We have seen import * as express from 'express'; quite a bit, and this is the important part. Not only can you use it to define what a whole import will be, you can use it to define individual imports as well. For example:

import { Request as ExpressRequest } from 'express';

Now ok, I agree, there is another way to do this, but I'll get to that later, we're going step by step in my revelation here.

Moving on I came up with the following request.ts in my typings folder.

import { Request as ExpressRequest } from 'express';

export interface Request extends ExpressRequest {
    id: string;
    user: User;
}

AHA! Well I could have just done it this way

import { Request } from 'express';

export interface CustomRequest extends Request {
    id: string;
    user: User;
}

which I see done quite a bit. Personally, I just don't like this. I like clean code, and CustomRequest just does not fit my

ts (req: CustomRequest, res: Response) => { ... }

So that is why I imported Request as ExpressRequest. But then I remembered the line we were working with above: import * as express from 'express'; Turns out, I like this line better. So what I came up with is the following:

import * as express from 'express';

export interface Request extends express.Request {
    id: string;
    user: User;
}

Yay! I get to keep my code clean, and this just better explains where the original Request interface is coming. Makes it easier if we were to be importing a lot of the same named types in the same file.

And that's it. Now, I just import my custom interface, named the way I like, from the typings folder, and everyone is happy.

Thanks for reading, and if you've got a different way of making your custom types, please comment so I can see how you make your types!

Chris Santos

Full-stack javascript engineer. Blogger, Nomad, Do-It-Yourself-er — author of express project generator Xpresso