Typescript typing for decorator middleware patterns

I am thinking about Node middlewares (in various frameworks). Often middlewares will add a property to a request or response object, which is then available for usage by any middlewares registered after it.

One issue with this model is that you don’t get effective typing.

To demonstrate, here’s a highly simplified model of such a framework. In this case, everything in synchronous, and each middleware receives a Request and must also return one (potentially altered).

interface Req {
    path: string;
    method: string;
}

type Middleware = (request: Req) => Req;

class App {

    mws: Middleware[] = [];

    use(mw: Middleware) {
        this.mws.push(mw);
    }

    run(): Req {

        let request = {
            path: '/foo',
            method: 'POST'
        }
        for(const mw of this.mws) {
           request = mw(request);
        }

        return request;

    }

}

To demonstrate the application, I’m defining 2 middlewares. The first one adds a new property to Req and the second one uses that property:

const app = new App();

app.use( req => {
  req.foo = 'bar';
});

app.use( req => {
  console.log(req.foo);
}

Typescript playground link

This is a very common pattern in server-side Javascript frameworks. Unaltered this throws an error:

Property 'foo' does not exist on type 'Req'.

The standard solution to this is using interface declaration merging:

interface Req {
    path: string;
    method: string;
}
interface Req {
    foo: string;
}

This has drawbacks though. This globally makes the change to the types, and we’re actually lying to typescript. The Req type will not have the .foo property until the first middleware is called.

So, I think this is just ‘the way it is’ in Express, Koa, Curveball, but I’m wondering if a different pattern is possible if we wrote a new framework.

I don’t think it’s possible to implement this pseudo code example:

app.use(mw1); // Uses default signature
app.use(mw2); // Type of `req` is now the return type of `mw1`.

I’m thinking it might be possible with chaining:

app
  .use(mw1)
  .use(mw2);

But I’m having a really hard time fully grasping how. Plus it gets even more complex if there is a next function, but lets leave that out for the sake of this question.

More broadly, my question is … what would a great server-side middleware-based framework look like, when it’s written specifically with Typescript in mind. Alternatively, maybe there are other patterns that are even more effective and I’m just too blind to see it.

18 thoughts on “Typescript typing for decorator middleware patterns”

Leave a Comment