13

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?

user3814613
  • 249
  • 1
  • 2
  • 11

3 Answers3

26

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;
    }
}
Titian Cernicova-Dragomir
  • 230,986
  • 31
  • 415
  • 357
  • 1
    Thank you very much! I put the `declare global` block in `express.d.ts`, and I had to remove the `declare` from `declare namespace Express` to get it to work, but it works now. – user3814613 Jan 03 '19 at 22:34
  • 1
    @user3814613 wops forgot the extra declare in there, removed it. Sorry – Titian Cernicova-Dragomir Jan 03 '19 at 22:36
  • Kind sir. Is there any way automate this with tsc? perhaps when i have `express_ext.d.ts` in my `src` folder, the content of `express_ext.d.ts` automatically to global declaration in `index.d.ts`? – Fandi Susanto Apr 24 '20 at 16:08
  • 1
    `namespace Express` doesnt seem to work for me. I had to use `declare module 'express'` instead. Any idea why this is the case ? – DollarAkshay Feb 19 '21 at 09:20
  • `declare namespace Express` solution worked after I created `index.d.ts` file inside `express/` directory. – gtmsingh May 16 '21 at 05:30
  • This gives a `typescript-eslint/no-namespace` warning as per the eslint recommended settings. I'll disable it, and personally I don't mind namespaces, but it's weird that this is frowned upon so badly to enter eslint recommended. – JHH Jun 30 '22 at 14:05
7

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

Nuno Sousa
  • 832
  • 7
  • 15
  • 1
    Not sure that will work with `strictFunctionTypes` if `user` is not optional, since the callback is expecting a `req` parameter with more keys than `post` says it can provide – Titian Cernicova-Dragomir Jan 03 '19 at 22:15
  • Yeah that would work as well, but the other answer has a solution that's easier to implement. @TitianCernicova-Dragomir I can only get it to work by making `user` optional. If it's not optional, the compiler complains about the function not being assignable to type `PathParams`. – user3814613 Jan 03 '19 at 22:40
  • 1
    I currently use tthis solution to inject a user into the request for authentication and access control purposes. (User is injected in a passport middleware). User needs to be optional though... so it's not perfect. I dislike the idea of extending another namespace, so ... I think it's a matter of preference. – Nuno Sousa Jan 03 '19 at 23:02
  • Why is the `user` field optional in RequestWithUser? I noticed this won't work if it is not optional – JAM Jul 27 '20 at 15:33
  • I'm going with it. It looks clean the most at least to me. – 6991httam Feb 14 '21 at 12:26
3

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;
  }
}
Eric Aska
  • 611
  • 5
  • 14