Christian Giacomi

My personal software development blog

How to Extend Express when using Typescript

How do you extend the Express framework when using Typescript and add your own type definitions

Posted — Dec 22, 2019   Category — software 

If you have ever used Express with Typescript you have surely defined new types for your application. You might have defined several types for your models, or for your function arguments or return types. You definitely would have had to create just a few, especially if you are using tslint to check and lint your code.

I mention tslint just because it will mark all your variables, the ones without an explicit type, as being implicitly typed to any and of course you will get a nice red underline in your IDE or editor. This will undoubtedly push you to create more custom types.

Anyways, as always I degress…

I believe we have all been in the above mentioned scenario, and that is of course the desired consequence of using typescript. I won't go over why you should use typescript, but there are a couple of situations which will make you think ‘Why did I decide to use Typescript??? Arghh!!!' and even that is part of the experience .

So lets look at a scenario where typescript will make your life a little more difficult, luckily there is a way to get around this problem and continue to enjoy using typescript.

In old Javascript

In nodejs, more specifically javascript, due to the nature of the language one can fluidly create new properties on objects. I know some will say that doing so is really bad, but we have all done it and I am sure we will all continue to do it.

Take a look at the code below.

var car = {
    name: 'Aventador',
    brand: 'Lamborghini',
};

car.color = 'yellow'; // this adds the 'color' property to the 'car' object

This is all fine and dandy and totally valid javascript, so this would naturally also work in nodejs. Duh…

Express

One very cool aspect of working with Express is the ability to extend the framework to support our needs. This is achieved by creating, and/or using middlewares.

Lets look, for a moment, at the Express documentation for middleware functions:

Middleware functions can perform the following tasks:

  • Execute any code.
  • Make changes to the request and the response objects.
  • End the request-response cycle.
  • Call the next middleware function in the stack.

It will happen that within your middleware you will perform some work and you will want to add the result to the request or response object for later. This may look something like this.

var app = express()
var router = express.Router()

function customMiddleware(req, res, next) {
    // do some work here
    // pass the value along the current request-response cycle
    req.customProperty = 'hello world';
    next();
}

// sample handler in express
router.get('/user/:id', customMiddleware, function (req, res, next) {
    // do some work here
    const customValue = req.customProperty; // 'hello world'
    res.render('test')
})

// mount the router on the app
app.use('/', router)

This is a pretty standard way of working with Express. So what is the problem?

Enter Typescript

When using typescript, you will get a compilation error from the typescript compiler if you attempt to access an undeclared property, something like:

error TS2339: Property 'x' does not exist on type 'y'.

So if we take a look at the code above and turn it into valid typescript, you will see that it does not change very much, but you will not be able to compile it because you are now accessing an undeclared property.

import express, { Request, Response } from 'express'

var app = express()
var router = express.Router()

function customMiddleware(req: Request, res: Response, next: any) {
    // do some work here
    req.customProperty = 'hello world'; // error TS2339
    next();
}

// sample handler in express
router.get('/user/:id', customMiddleware, function (req: Request, res: Response) {
    // do some work here
    const customValue = req.customProperty; // error TS2339
    res.render('test')
})

// mount the router on the app
app.use('/', router)

I am sure that like me you will start asking yourself ‘how can I get typescript to know that there is a customProperty on the Request object?’

Well there is one simple way of doing exactly that… letting the typescript compiler know that Request does indeed contain a customProperty property.

Extending Typescript declarations

The correct term is Declaration merging and it means exactly what the name states, here is the official documentation for it.

It simply means that at compilation the typescript compiler will merge separate type declarations into a single definition. Doing so will create an extended type, which will contain the properties of all the declarations together.

Looking at the code above you would extend Express and ensure that the TS compiler knows about our customProperty. Create a new definition file in your project.

//custom.request.d.ts
declare namespace Express {
  export interface Request {
    customProperty?: string
  }
}

The code above simply states that we are adding customProperty as an optional string to the Request interface in the Express namespace.

Now all we have to do is let the compiler know about the new interface declaration. We can do that by simply adding our custom.request.d.ts file to the tsconfig.json in the files section.

{
    "compilerOptions": {
        "module": "commonjs",
        "noImplicitAny": true,
        "removeComments": true,
        "preserveConstEnums": true,
        "sourceMap": true
    },
    "files": [
        "custom.request.d.ts"
    ]
}

This will ensure that the typescript compiler will merge the type declaration found in our custom.request.d.ts file with the one provided by the Express framework.

This way you will not get any compilation errors because of undeclared properties. This way of extending the type declarations is very powerful and flexible. Furthermore it does not pollute the official declarations of the framework.

If this post was helpful tweet it or share it.

See Also