Building Custom Request Types for a Personalized Express Router in TypeScript (TS2769)

I've been facing challenges integrating custom Request types with TypeScript.

Within my application, I have both public and private routes.

The public routes utilize the Request type from Express. On the other hand, the private routes make use of a custom PrivateRequest type that extends the Request type from Express, defined as follows:

import type { Request } from "express";
import type * as Cookies from "cookies";

export type PrivateRequest = Request & {
  user: User;
  cookies: Cookies;
}

The routing setup for public and private routes is as follows:

const publicRouter = express.Router();
const privateRouter = express.Router();

privateRouter.use([userSession]);

publicRouter.post("/login", login);
privateRouter.get("/api/user", user);

Here's an example of a private route that utilizes the PrivateRequest type, and TypeScript doesn't raise any issues in this scenario:

export default async function user(req: PrivateRequest, res: Response) {
  try {
    res.json({ test: true });
  } catch (err) {
    console.error(err);
    res.status(500).json({ errors: { server: "Server error" } });
  }
}

The problem arises with the private routes, e.g.:

privateRouter.get("/api/user", user);

For the privately defined routes, TypeScript throws this specific error:

TS2769: No overload matches this call

How can I resolve this issue? I've tried various solutions, but nothing seems to work, and I'm unsure of the root cause.

I can eliminate this error by making the user nullable on the PrivateRequest, but this goes against the expected behavior since all private routes are supposed to have a user on the req object. The userSession middleware either returns a 401 error or adds the user to the req object for subsequent private routes. Here's an example of what my userSession middleware looks like:

export default async function userSession(
  req: Request,
  res: Response,
  next: NextFunction
) {
  try {
    req.cookies = new Cookies(req, res);

    // [...] authentication and user entity fetching (throws if either one fails)

    if (user === null) {
      res.status(401).json({ errors: { server: "Unauthorised" } });
    } else {
      // @ts-ignore
      req.user = user;
      next();
    }
  } catch {
    res.status(401).json({ errors: { server: "Unauthorised" } });
  }
}

Answer №1

When using Express's get method, it expects handlers that accept a Request object, not a PrivateRequest object. In order to work with private routes, where you know that properties like user and cookies will be present in the request object, you need to inform TypeScript that this is intentional. Since TypeScript defaults to assuming the handler will only receive a Request, you need to take additional steps to handle private routes.

Here are a couple of ways to approach this:

  1. One option is to use a type assertion, possibly within a custom utility function that mimics the behavior of Express's get method but specifically for private routes:

    const addPrivateGet = (path: string, handler: (req: PrivateRequest, res: Response)) => {
        privateRouter.get(path, handler as unknown as (req: Request, res: Response) => void);
    };
    // ...
    addPrivateGet("/api/user", user);
    
  2. Alternatively, you can utilize an assertion function within your private route handlers. Here's an example of how to define and use such a function:

    function assertIsPrivateRequest(req: Request): asserts req is PrivateRequest {
        if (!("user" in req)) {
            throw new AssertionError(`Invalid request object, missing 'user'`);
        }
        if (!("cookies" in req)) {
            throw new AssertionError(`Invalid request object, missing 'cookies'`);
        }
    }
    

    Below is an instance of how you might implement this function in a private route handler:

    export default async function user(req: Request, res: Response) {
        try {
            assertIsPrivateRequest(req);
            // Access req.user and req.cookies safely here
            res.json({ test: true });
        } catch (err) {
            console.error(error);
            res.status(500).json({ errors: { server: "Server error" } });
        }
    }
    

    This assertion function not only narrows the type of req to PrivateRequest, but also serves as a runtime safety check to catch potential issues if the handler is incorrectly used in a public route or if the middleware fails to add the expected properties.


It may not directly apply to your specific scenario of distinguishing between Request and PrivateRequest, but for others encountering similar challenges, it's worth considering augmenting the default Request type globally using declaration merging. By creating a declaration file that extends the Request interface, you can introduce new properties that are accessible throughout your codebase:

// Augment the Express namespace to add a new property to Request
declare module Express {
    interface Request {
        someNewProperty: string;
    }
}

This modification allows you to directly reference someNewProperty on any Request object within your application:

app.get("/", (req, res) => {
    console.log(req.someNewProperty);
    // Additional logic...
});

Keep in mind that while declaration merging updates the type definition, it's imperative to ensure that the property is added to the request object at runtime using middleware or other mechanisms to maintain accuracy.

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

NodeJS closes the previous server port before establishing a new server connection

During my development and testing process, whenever I make changes, I find myself having to exit the server, implement the updates, and then start a new server. The first time I run the command node server.js, everything works perfectly. However, when I m ...

Is there a way to customize the Color Palette in Material UI using Typescript?

As a newcomer to react and typescript, I am exploring ways to expand the color palette within a global theme. Within my themeContainer.tsx file, import { ThemeOptions } from '@material-ui/core/styles/createMuiTheme'; declare module '@mate ...

Date selection feature in Material UI causing application malfunction when using defaultValue attribute with Typescript

Recently, I discovered the amazing functionality of the Material UI library and decided to try out their date pickers. Everything seemed fine at first, but now I'm facing an issue that has left me puzzled. Below is a snippet of my code (which closely ...

Tips for optimizing HttpRequests within nested for-loops that utilize subscribe()?

Our REST-API is designed to be frontend agnostic, meaning it always sends the IRI to nested resources. This results in multiple HTTP calls needed to retrieve data; first for the parent resource, then its child resources and so on. Each Country has linked E ...

Guide for configuring Supabase authentication details in a NodeJS environment

I've decided to separate my Supabase database interactions from my client Flutter app and create a dedicated NodeJS express app for that purpose. Currently, I have included the credentials of the logged-in user in the header of my API requests as show ...

An error was encountered involving an unexpected token < at the beginning of a JSON file while using express with firebase

After setting up an authentication system in express using firebase auth, I established an endpoint in my express server: app.post('/users/login', (req, res) => { console.log('logging in...'); firebase.auth().signInWithEmail ...

What is the initialization parameter for the context function in Apollo server?

When setting up an Apollo server, the constructor is configured with various options including a context initialization function that is called with each request. According to the documentation, this function receives a parameter object with the request (r ...

Showcase every assortment in mongodb+express

Here is the code I've written to send data to a database: app.post('/thanks', function(req, res) { if (atendees.checkin === req.body.dbstring) { dbConn.then(client => { delete req.fee._id; const db = client.db('myd ...

The Node.js Express framework is having trouble recognizing the npm command within the project directory

I've been encountering an issue while trying to install express via cmd on Windows. The problem arises when I attempt to install dependencies after successfully setting up express and creating a framework skeleton. It prompts me to install dependencie ...

Configuring routers for my node.js application

I've been facing several challenges with setting up the routes for my node.js application. Below is a snippet of my app.js file where I call the route. const express = require("express"); const bodyParser = require("body-parser"); const app = exp ...

Different ways to fulfill the extends type interface requirement in TypeScript

Hey there, I'm looking to create reusable hooks for API requests. Here's the code I have so far: interface DataResponse<Data> { data: Data[]; } export const useRequestInfiniteHooks = <T extends DataResponse<T>>() => { co ...

Having trouble with the middleware stubbing in Node.js using Express, ES6, and

Currently, I am in the process of writing some mocha unit tests for my express router. However, no matter how I attempt to stub the middleware, it seems that the code within the middleware is still being executed. Below you will find my router and test cod ...

Bringing back the mapped attributes from Keycloak for demonstration

I've recently followed a method (source) that allowed me to effortlessly retrieve firstname, lastname, username, and email from Keycloak. I am now attempting to utilize the Keycloak mapping directory to map our LDAP, as shown in this guide (source). M ...

Typescript: Why Lines Are Not Rendering on Canvas When Using a For-Loop

What started out as a fun project to create a graphing utility quickly turned into a serious endeavor... My goal was simple - to create a line graph. Despite my efforts, attempting to use a for-loop in my TypeScript project resulted in no output. In the ...

Developing a foundational template for modules to integrate into various files

Within our Express application, we find ourselves repeatedly using the same modules in our controller files. Is there a more efficient way to centralize these modules in one file and then require that file in our controllers? So far, I have experimented wi ...

Assign a variable with the value returned by a function

Can you help me with this question I have about validating fields with a function using AbstractControl? errorVar: boolean = false function(c: AbstractControl): {[key: string]: string } | null { // validation if 'test' is true or not goes here ...

Can we improve the coding of this as it seems inefficient and uses up too much room?

Do you think there is a more efficient way to write this code? It seems quite impractical and takes up a lot of space. Essentially, it's about the random chance of obtaining a rarity, like acquiring an Uncommon sword. if (Math.random() * 100 < 100 ...

Unable to show information within a view in an Express Node.js application

Just diving into the world of express and node, I'm currently working on a basic express app that retrieves data from a json file. However, when I try to render the data on my post/details view, it doesn't seem to show up. I suspect the issue lie ...

What is the best way to allow the browser to either download a file or open it in a new tab based on what is feasible? (while maintaining the actual file

Currently in my web application, which utilizes Angular for the front-end and .Net Core for the back-end, there is a page where users can click on server-side stored files. The desired behavior is for the file to open directly in a new tab if the browser a ...

Is there a way to create an internal link to another HTML templating engine page within Express.js?

I am currently facing an issue with two Pug files, index.pug and search.pug, stored in a /views folder. In my index.pug file, I have the following line of code: a(href="/search.pug") Search In my JavaScript file, I have specified the view engine as P ...