Typed Express Request and Response with TypeScript

By Tomas Nilsson

September 23rd, 2021

person on computer

Writing code with TypeScript is great, but there are some places in the code where it’s really tricky to let TypeScript shine. One of them is when defining routes in Express.

This article goes through some of the ways to add TypeScript to the Express.Request and Express.Response objects.

Setting up Express

Let’s do the basic stuff first.

    import Express from 'express';
    import http from 'http';

    let app: Express.Application | undefined = undefined;

    export function InitializeExpress(port = 3001): void {

        app = Express();
        app.use(Express.urlencoded({ extended: true }));

        app.get('/', function (_req: Express.Request, res: Express.Response) {
            res.status(200).json({
                "Foo": "Bar",
                 "Time": new Date().toISOString()
            });
        });

       // Add 404 handler
       app.use(function (_req: Express.Request, res: Express.Response) {
          res.status(404).send("Not found");
       });

       // Start server (app.listen can also be used)    
       http.createServer(app).listen(port, () =>  
        console.log(`Running at [http://localhost:${port}/`](http://localhost:${port}/`)));

    }

On accessing http://localhost:3001/, this server will return something like:

    {"Foo":"Bar","Time":"2021-09-21T19:32:15.091Z"}

Express.Request.body

The Express.Request object is defined as:

    interface e.Request<P = ParamsDictionary, ResBody = any, ReqBody = any, ReqQuery = QueryString.ParsedQs, Locals extends Record<string, any> = Record<string, any>>

and this type is complex to use. To simplify this, a new type is defined as:

export interface TypedRequestBody<T> extends Express.Request {
    body: T
}

With this new interface it’s possible to use generics to type the body:

    app.post('/login', function (req: TypedRequestBody<{ username: string, password: string }>, res: Express.Response) {

        const success = req.body.username === "foo"
                        && req.body.password === "bar";

        res.status(200).json({ "Success": success });
    });

This will give the TypeScript checking as well as the intellisense:

Express.Request .query

Modifying the query in Express.Request is somewhat more tricky, because Express uses types from express-serve-static-core , which in turn Express doesn’t expose. So the only way to get hold of the proper types is to import { Query } from 'express-static-serve-core'; . The solution is to do:

    import { Query } from 'express-serve-static-core';

    export interface TypedRequestQuery<T extends Query> extends Express.Request {

         query: T

    }

To use it it would look something like:

    app.get('/article/:id', function (req: TypedRequestQuery<{ id: string }>, res: Express.Response) {

        const id = req.query.id;

        // Fetch from database here and return article        

        res.status(200).json({ ID: id });

    });

Express.Request .body & query

Let’s do another type where *both *body and query can be typed:

    export interface TypedRequest<T extends Query, U> extends Express.Request {
        body: U,
        query: T
    }

The usage could look something like:

    app.put('/article/:id', function (req: TypedRequest<{ id: string }, { name: string }>, res: Express.Response) {

            console.log(`Updating article ${req.query.id}`);
            console.log(`setting name to ${req.body.name}`);
            res.status(200).json({ Success: true });

    });

A small security note on incoming requests

TypeScript doesn’t exist at runtime so even the code is setup to expect a payload of:

    { username: string, password: string }

... bad payloads such as this one:

    { username: undefined, password: ["Hello", "World"] }

.. can be submitted in order to “mess” with the route. Therefore, always make sure to always validate the incoming payload.

PS:

If you want to automatically generate validator functions from TypeScript interfaces / Types, then read this article: https://javascript.plainenglish.io/how-to-automatically-convert-typescript-types-to-runtime-validators-5b06ee269b13

Express.Response

Again Express uses types from express-serve-static-core :

    import { Send } from 'express-serve-static-core';

The interface is defined as:

    export interface TypedResponse<ResBody> extends Express.Response {

       json: Send<ResBody, this>;

    }

Usage could look something like:

    app.get('/ping', function (_req: Express.Request, res: TypedResponse<{ Pong: string }>) {

         res.status(200).json({ "Pong": new Date().toISOString() });

    });

And again, if the format of the response doesn’t match the expected result, TypeScript/VSCode (ErrorLens) will inform with an error:

Demo source code?

https://github.com/tomnil/typedexpress

Further Reading

Sharing Types Between Your Frontend and Backend Applications



Continue Learning