Unable to extend Express Request in TypeScript

Viewed 17430

This question has been asked several times, but none of the answers that were given have worked for me. I'm trying to extend the Express Request object to include a property to store a User object. I created a declaration file, express.d.ts, and placed it in the same directory as my tsconfig.json:

import { User } from "./src/models/user";

declare namespace Express {
    export interface Request {
        user: User;
    }
}

Then I try to make an assignment to it in secured-api.ts:

import express from 'express';
import { userService } from '../services/user';

router.use(async (req, res, next) => {
    try {
        const user = await userService.findByUsername(payload.username);

        // do stuff to user...

        req.user = user;
        next();
    } catch(err) {
        // handle error
    }
});

I get the following error:

src/routes/secured-api.ts:38:21 - error TS2339: Property 'user' does not exist on type 'Request'.

38                 req.user = user;
                       ~~~~

My User class is:

import { Model, RelationMappings } from 'objection';

export class User extends Model {

    public static tableName = 'User';
    public static idColumn = 'username';

    public static jsonSchema = {
        type: 'object',
        required: ['fname', 'lname', 'username', 'email', 'password'],

        properties: {
            fname: { type: 'string', minLength: 1, maxLength: 30 },
            lname: { type: 'string', minLength: 1, maxLength: 30 },
            username: { type: 'string', minLength: 1, maxLength: 20 },
            email: { type: 'string', minLength: 1, maxLength: 320 },
            password: { type: 'string', minLength: 1, maxLength: 128 },
        }
    };

    public static modelPaths = [__dirname];

    public static relationMappings: RelationMappings = {

    };

    public fname!: string;
    public lname!: string;
    public username!: string;
    public email!: string;
    public password!: string;
}

My tsconfig.json is:

{
    "compilerOptions": {
      "target": "es6",                          /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017','ES2018' or 'ESNEXT'. */
      "module": "commonjs",                     /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */
      "lib": ["es2015"],                             /* Specify library files to be included in the compilation. */
      "outDir": "./build",                      /* Redirect output structure to the directory. */
      "strict": true,                           /* Enable all strict type-checking options. */
      "esModuleInterop": true                   /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */
    }
}

My directory structure is:

backend/
    package.json
    tsconfig.json
    express.d.ts
    src/
        models/
            user.ts
        routes/
            secured-api.ts

What am I doing wrong here?

4 Answers

The problem is that you are not augmenting the Express global namespace defined by express you are creating a new namespace in your module (the file becomes a module once you use an import).

The solution is to declare the namespace in global

import { User } from "./src/models/user";

declare global {
    namespace Express {
        export interface Request {
            user: User;
        }
    }
}

Or not use the module import syntax, just reference the type:

declare namespace Express {
    export interface Request {
        user: import("./src/models/user").User;
    }
}

I've had the same issue...

Can't you just use an extended request? like:

interface RequestWithUser extends Request {
    user?: User;
}
router.post('something',(req: RequestWithUser, res: Response, next)=>{
   const user = req.user;
   ...
}

On another note, if you're using async callbacks with express make sure to user express-async wrappers or make sure you know exactly what you're doing. I recommend: awaitjs

I tried a lot of solutions but none of them worked.

but it can be this simple also and working perfectly. and of course you can save AuthenticatedRequest anywhere else.

import { IUser } from './src/_models/user.model';

export interface AuthenticatedRequest extends Request {
  user: IUser;
}
static async getTicket(req: AuthenticatedRequest, res: Response){}

UPDATE:

So I found something interesting from here: Import class in definition file (*d.ts).

declare namespace Express should be first line to typescript count it as global

declare namespace Express {

  interface Request {
    user: import('./src/modules/models/user.model').IUser;
    uploadedImage: { key: string; type: string };
    group: import('./src/modules/models/group.model').IGroup;
  }
}

0

Declaration merging means that the compiler merges two separate declarations declared with the same name into a single definition.

See below example in which the interface declarations are merged in typescript

interface Boy {
height: number;
weight: number;
}

interface Boy {
mark: number;
}

let boy: Boy = {height: 6 , weight: 50, mark: 50}; 

extend request response

Related