Skip to content

How extend express types on Typescript to add custom properties

Why extend Express default types?

When using express with Typescript sometimes we want to be able to know the type of the request body or params, sometimes we even need to know the types needed in the response body, let's think about another case where for certain requests we expect to receive a custom security token in the header, how can we define this types in Typescript?

Extending Express types

First we need to install the typescript types of express, we can do this with the following command

npm i --save-dev typescript ts-node @types/express

Now we can extend or use the types included on express, first we are going to use them directly, and then we will create our own interfaces extending this types, lets look at the following basic express app

import express, { Application, Request, Response } from "express";

const port = 3015;
// New express application instance
const app: Application = express();

// Allow json in body params
app.use(express.json({ limit: "250mb" }))

/**
 * Route using body params, types for email and password and for response
 */
app.post("/body-params", [], (req: Request<any, any, { email: string, password: string }, any, any>, res: Response<{ success: boolean, data: { email: string }}>) => {
    // Get variables from request body, with types
    const { email, password  } = req.body

    // Because types if we return something that is not expected we get a typing error
    res.status(200).send({ success: true, data: { email } })
})

/**
 * Route using query params and query string with types and expects a string array on response body
 */
app.get("/query-params/:userId/:bookId", [], (req: Request<{ userId: number, bookId: number }, any, any, { bookName: string }, any>, res: Response<string[]>) => {
    /**
     * Get variables from query params
     */
    const { userId, bookId  } = req.params

    /**
     * Get variables from request query
     */
    const { bookName } = req.query

    // Response expect an array of strings
    res.status(200).send(["Book 1", "Book 2"])
})


/**
 * Begin listening on port selected
 */
app.listen(port, () => {
    console.log(`Server is running on port ${port}...`);
});

When using express types we need to use the following generics

Request

P = path params, expects a [key: string]: string dictionary
T = response body, expects any
R = request body, expects any
S ReqQuery = request query, expects any
L Locas = request locals, expects any

Request<P, T, R, S, L>

examples

// Request with any in all Generics
Request<any, any, any ,any ,any>

// Request expecting variables in body and request query
Request<any, any, { userId: number } ,{ bookId: number } ,any>

// Request expecting variables in query params
Request<{ userName: string }, any, any ,any ,any>

Response

S ReqQuery = response body, expects any
L Locas = response locals, expects [string]: any

Response<S, L>

examples

// Response expects string array in response body
Response<string[]>

// Response expects success boolean array and number in data
Response<{ success: boolean, data: number }>

With the following information now we could create our own interfaces extending express types

import { Request, Response } from "express";
import { IncomingHttpHeaders } from 'http';

export interface HeadersWithToken extends IncomingHttpHeaders{
    token?: string
}


/**
 * Request that expects a token variable on headers
 */
export interface AuthenticatedRequest<
    P extends { [key: string]: string } = {
        [key: string]: string;
    },
    T = any,
    R = any,
    S = any,
> extends Request<P, T, R, S> {
    headers: HeadersWithToken,
}


/**
 * Response with types for response body, we expect a generic,
 * so we can put any data we want on the data of the response
 * we can have multiple responses
 */
export interface FormattedResponse<ResponseData> extends Response<{
    success: boolean, data: ResponseData
}>{}

Now we can use our own interfaces

app.get("/test-route", [], (
    req: AuthenticatedRequest,
    res: FormattedResponse<string>
) => {
    const { token } = req.headers

    if(token){
        return res.status(200).send({success: true, data: token})
    }

    return res.status(200).send({success: false, data: "Token Missing"})
})