What is the correct way to enhance a generic interface by adding a new generic parameter using declaration merging in TypeScript?

I currently have a simple express application that provides token-based authentication and utilizes Zod for creating schemas to validate incoming data.

Specifically, I have defined two schemas:

  • CreateUserSchema {firstname, lastname, email, pass, passConfirm}
  • LoginUserSchema {email, pass}

Zod allows me to infer types based on these schemas, such as:

type SchemaBasedType = z.infer<typeof schema>
Thus, I have two types: CreateUserRequest and LoginUserRequest, derived from my schemas.

To start, I created a validation middleware like this:

export const validateRequest =
    <T extends ZodTypeAny>(schema: T): RequestHandler =>
    async (req, res, next) => {
        try {
            const userRequestData: Record<string, unknown> = req.body;

            const validationResult = (await schema.spa(userRequestData)) as z.infer<T>;

            if (!validationResult.success) {
                throw new BadRequest(fromZodError(validationResult.error).toString());
            }

            req.payload = validationResult.data;
            next();
        } catch (error: unknown) {
            next(error);
        }
    }; 

As mentioned above, this middleware accepts a schema argument that is typed according to the Zod documentation. I found it beneficial to extend the request object with the "payload" property to store valid data.

However, issues arose when TypeScript didn't recognize the payload type. This is where declaration merging comes into play. Initially, I attempted something like this:

declare global {
    namespace Express {
        export interface Request {
            payload?: any;
            
        }
    }
}

Yet, this approach did not seem ideal, as we know the specific signature of our payload. Thus, I experimented with a union type based on Zod types:

payload?: CreateUserRequest | LoginUserRequest;

This method revealed discrepancies when some fields in a narrower type were missing in another type.

Subsequently, I explored using a generic approach:

declare global {
    namespace Express {
        export interface Request<T> {
            payload?: T;
            
        }
    }
}

While this seemed promising, the Request interface already had 5 generic arguments. This raised questions about how the merging would occur—would my generic argument be first or last?

Feeling uncertain, I sought advice online and discovered an alternate strategy:

declare global {
    namespace Express {
        export interface Request<
            Payload = any,
            P = ParamsDictionary,
            ResBody = any,
            ReqBody = any,
            ReqQuery = ParsedQs,
            LocalsObj extends Record<string, any> = Record<string, any>
        > {
            payload?: Payload;
        }
    }
}

Although this solution provided helpful hints, assigning "any" as the type felt inadequate given the types inferred by Zod. Without specifying Payload = any, I wouldn't receive type hints.

Struggling with these complexities, as I am not well-versed in TypeScript and backend architecture, I'm stuck at this point.

Ultimately, my goal is to achieve something like this:

authRouter.post("/register", validateRequest(createUserSchema), AuthController.register);
where the compiler recognizes the payload signature as equal to CreateUserRequest.
authRouter.post("/login", validateRequest(loginUserSchema), AuthController.login)
; where the compiler identifies the payload signature as equal to LoginUserRequest.

How can I properly specify the expected types and effectively manage them?

Answer â„–1

After dedicating time to finding a solution, I have reached the following conclusions:

  • As pointed out by @jcalz, we cannot make a generic interface more generic;
  • All my attempts to ensure that a payload property is definitely typed according to a passed schema by validating it in a middleware without using a generic parameter have not been successful.
type Parse = <T extends ZodTypeAny>(schema: T, data: unknown) => z.infer<T>
export type ParsedObj = ReturnType<Parse>

In this case, where the payload is:

declare global {
  namespace Express {
    export interface Request {
      payload?: ParsedObj;
    }
  }
}

My efforts have fallen short because it relies on a generic argument that is 'any' before the call, resulting in receiving 'any' throughout the rest of the code.

Therefore, I can only rely on the result of zod validation, as it selects only the properties described in the schema and eliminates any unused properties.

If you have a better approach based on your experience with express, I welcome your input.

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

Why isn't the Nunjucks extends directive functioning when the template is stored in a different directory and being used

I have successfully integrated nunjucks templating into my express app. Below is the directory structure of my project: . nunjucks-project |__ api |__ node_modules |__ views |__ templates |__ layouts |__ default.html ...

In the desktop view, the onClick button requires two clicks to trigger the onClick function, while in the mobile view, it only takes one click as expected

Here are the topics I want to display as buttons: const paperTopics = [ "Teaching Aptitude", "Research Aptitude", "Comprehension", "Communication", "Mathematical Reasoning and Aptitude", ...

Optimal strategies for managing multiple organizations on a couch

Currently, I have a node express app using nano with couchdb as the backend and it's working perfectly. Now, my goal is to expand this setup to cater to multiple organisations. For example, I want to implement wildcard DNS records that will allow eac ...

Once the array has been initialized, I am encountering an issue where it appears as undefined within the "then" block of a promise function

Here is a snippet of my code: Using Typescript: console.log(this.arr); myService.getData(). then(data =>{ console.log(this.arr); this.arr[0].myData = data; }); When I check the logs in Chrome, here's what I see: 1. arr.length=1 - in ...

The CastError occurred when trying to convert the value "0" (string type) to an ObjectId at the path "_id" for the model "Users" during the findOne operation and other related operations

Recently, I started exploring mongodb and nodejs. Unfortunately, I encountered an error when trying to retrieve a specific user using req.params.id from the database. app.get('/user/:id', (req,res) => { console.log(req.params.id) Users ...

Encountering difficulties retrieving images from the upload directory using React and Node

In developing my MERN application, I have encountered an issue with accessing images on the client-side that are saved in the local database using multer. The backend folder structure is as follows: --- api --- controllers --- model --- routes ...

Organize information in a React table following a predetermined sequence, not based on alphabetical order

As a beginner with React, I'm looking to sort my data by the column "Status" in a specific order (B, A, C) and vice versa, not alphabetically. The data structure looks like this: export interface Delivery { id: number; name: string; amount: num ...

Using Angular: How to set the index value from a dropdown to a local variable after a button is clicked

Can someone please provide guidance on how to assign the index value (i = index) to EmployeeIndex: any; after a button click event? Your suggestions are greatly appreciated. Here is my code: HTML <select class="form-control" [(ngModel)]="EmployeeNam ...

The NativeScript error code TS2554 is indicating an expectation of 1 argument, however, none were provided

Trying to utilize the native camera API with NativeScript without any plugins has presented an error when attempting to use the takePicture function: app/shared/camera/camera.service.ts(23,39): error TS2554: Expected 1 argument, but received 0. app/sh ...

A data type labeled as 'undefined' needs to include a method called '[Symbol.iterator]()' which will then return an iterator

I've been working on converting my reducer from JavaScript to TypeScript, but I keep encountering a strange error that I can't seem to resolve. The issue arises when I attempt to use ellipsis for array deconstruction in the reducer [...state.mess ...

A step-by-step guide on integrating a proxy into a Node/Express website

I have a website running on Node with the Express framework. My objective is to collect data from the Yahoo Placefinder API, which does not support JSONP. Therefore, I need to send my JQuery.getJSON request to a proxy. This proxy will then generate an HTT ...

(Node.js) - Error: Attempting to access the method 'getData' from an undefined variable

I've been doing some research for a couple of days now, but I still feel like I'm missing a crucial part of the concept here... Being relatively new to node.js, I'm attempting to invoke a method from a different module in my main class... B ...

The headerStyle does not have any impact on the header component in React-Native

Currently diving into React-Native with Typescript and working on a project. Encountered a bug where the header color isn't changing as expected. Any help or insight would be greatly appreciated! -Viggo index.tsx import React, { Component } from & ...

Creating an asynchronous request mimic similar to AJAX in Angular 7

Within my current project, I am utilizing a full calendar that loads events dynamically from a database. However, I encountered an issue when attempting to add events to the calendar. It seems like there might be a synchronization problem with the httpget ...

How come TypeScript does not detect when a constant is used prior to being assigned?

There's an interesting scenario I came across where TypeScript (3.5.1) seems to approve of the code, but it throws an error as soon as it is executed. It appears that the root cause lies in the fact that value is being declared without being initiali ...

Ways to break down a collection of multiple arrays

Looking to transform an array that consists of multiple arrays into a format suitable for an external API. For example: [ [44.5,43.2,45.1] , [42, 41.2, 48.1] ] transforming into [ [44.5,42], [43.2,41.2] , [45.1, 48.1] ] My current code attempts this ...

store data in mongoose and monogoDB

I am facing an issue with connecting to my mongoDB and saving a new user inside. Although the express server seems to connect to the db successfully, the post request is not being processed. I need assistance in reviewing my code to identify the problem. ...

What is the method for retrieving keys from an object that contain hyphens within them?

Presented here is an object: { "type": "fill", "id": "asdf", "paint": { "fill-color": "#440d0d" } } I am aiming to retrieve the fill-color property. My attempted ...

Is it true that Async methods in Typescript return promises?

I'm currently in the process of familiarizing myself with Async/Await in Typescript. I've been updating existing code, for example: getImportanceTypes(): Promise<void> { return this.importanceTypeService.list() .then(i ...

Arranging Columns in Angular

Here is the code I'm struggling with, as I can't seem to make it work. I don't see any issues with it, but still: In my app.component.html file, I have: <table border="1"> <!-- ADD HEADERS --> <tr> ...