104

I have the following piece of code in my express app

router.get('/auth/userInfo', this.validateUser,  (req, res) => {
    res.json(req.user);
});

and my IDE seems to be complaining with the error

error TS2339: Property 'user' does not exist on type 'Request'.

When I compile my typescript code it seems to be throwing this error. Any ideas why this is happening?

RRP
  • 2,563
  • 6
  • 29
  • 52
  • 1
    probably because of missing type on req - fast fix `(req: any, res: any) => ...` otherwise you might need to use some [type info for express](https://stackoverflow.com/questions/36037977/how-to-install-express-in-typings) – Ovidiu Dolha Jun 06 '17 at 07:05

16 Answers16

118

We have a large API written in Express and Typescript, and this is how we handle such scenarios:

We keep the request definitions in one file:

import { Request } from "express"
export interface IGetUserAuthInfoRequest extends Request {
  user: string // or any other type
}

And then in the file where we are writing the controller functions:

import { Response } from "express"
import { IGetUserAuthInfoRequest } from "./definitionfile"

app.get('/auth/userInfo', validateUser,  (req: IGetUserAuthInfoRequest, res: Response) => {
  res.status(200).json(req.user); // Start calling status function to be compliant with Express 5.0
});

Be advised that "user" is not a property that is available natively in the Request object of Express. Make sure that you are using a middleware that adds such property to the request object.

Akshar Patel
  • 5,377
  • 4
  • 20
  • 25
  • 1
    this is also a way to trick typescript and it isn't guaranteed to have a user object as @MadaraUchiha pointed out (...and downvoted me for :-P ). It would be better to have build a specialized router that does all of this. – Lostfields Jun 07 '17 at 12:04
  • 1
    Thanks for your answer :) – Czeran Jul 04 '18 at 16:19
  • 25
    This is giving me `Argument of type '(req: ExtendedRequest, res: Response) => Promise' is not assignable to parameter of type 'Application'. Type '(req: ExtendedRequest, res: Response) => Promise' is missing the following properties from type 'Application': init, defaultConfiguration, engine, set, and 62 more.` – roxxypoxxy May 03 '19 at 10:18
  • 1
    Depending on the implementation of the authorization middleware, it is guaranteed that a user property is part of the req object for all routes that require the user to be authorized, so it shouldn't be a huge problem in most cases. It doesn't solve the underlying problem though, that is correct. – Robert Jul 07 '19 at 07:19
  • @roxxypoxxy I assume you are trying to implement this firebase middleware? https://github.com/firebase/functions-samples/blob/master/authorized-https-endpoint/functions/index.js In practice express can handle async middleware just fine, the express types are too limited in this regard. The real solution would be to write types from scratch to account for req.user and the fact that app.use can handle promises. A quick solution would be: app.use(validateFirebaseIdToken as any). This doesn't compromise on type safety in any way, though it isn't an optimal solution. – Robert Jul 07 '19 at 07:19
  • Really elegant answer. Thank you! OP should select this as accepted answer. – Mina Oct 13 '21 at 12:29
  • 8
    @roxxypoxxy to fix the `Argument of type ... is not assignable to parameter of type ...` error be sure to define `user` as optional in `IGetUserAuthInfoRequest`, i.e. use `user?: string` instead of `user: string` – cprcrack Jun 06 '22 at 16:18
  • 1
    @cprcrack But then we would have to conditionally access user properties. There should be a better way to achieve this – Tayyab Mazhar Jan 25 '23 at 11:20
45

req is probably of type Request from "express" package and user does not exist there. You have to either extend Request with own router handler or cast it to type any or object.

try res.json(req['user']) or res.json( (<any>req).user )

You may also use module/global augmentation

import { Request } from "express"

declare module "express" { 
  export interface Request {
    user: any
  }
}

newer express definition may need to augment the core def instead

declare module 'express-serve-static-core' {
  export interface Request {
    user: any
  }
}

You can also make your own handler wrapper (instead of extending Router functionality in ExpressJs).

import * as express from 'express';

interface IUserRequest extends express.Request {
    user: any
}

function myHandler(handler: (req: IUserRequest, res: express.Response, next?: express.NextFunction) => any) {
    return (req: express.Request, res: express.Response, next: express.NextFunction) => {
        try {
                            
            validateUser(req, res, (err) => { // your validateUser handler that makes a user property in express Request
                if(err)
                     throw err;

                let requestWrapper: IUserRequest = <IUserRequest>req;

                handler(requestWrapper, res, next);
            })                
        }
        catch (ex) {
            next(ex);
        }
    } 
}

let app = express();
// init stuff for express but this.validateUser handler is not needed

app.use('/testuser', myHandler((req, res) => {
    res.json(req.user);
}));

UPDATED: Since Typescript is evolving I would also consider using Type Guards

if (hasUser(req)) {
    console.log(req.user)
}

function hasUser(request: Request): request is Request & { user: number } {
    return 'user' in request && typeof request['user'] == 'number'
}
Lostfields
  • 1,364
  • 1
  • 12
  • 20
  • While these are valid workarounds, they do nothing to solve the underlying problem which TypeScript helpfully points out: It isn't guaranteed that request has a user property. – Madara's Ghost Jun 06 '17 at 09:04
  • @MadaraUchiha I totally agree, thought first about extending Router but that is a bit of work. To make it simple you may use a handler wrapper instead. I edit my answer to include one example. – Lostfields Jun 07 '17 at 11:43
  • 1
    Can you explain the `module/global augmentation` method? I don't know how to set it up with TS 2.5 (and express types installed on @types/express) – BrunoLM Oct 04 '17 at 01:44
  • @BrunoLM try `declare module "express-serve-static-core"` instead, but it is a lot better to use a request handler instead. – Lostfields Oct 10 '17 at 05:41
  • Note that if you choose the typecasting solution `req['user']`, you will need to remove the `"noImplicitAny": true` option from `tsconfig.json`. – Zach Gollwitzer Feb 23 '20 at 13:19
  • Thank you. (req).user fixed the issue for me. Original code was const user = await User.findOne({ email: req.user }); I then changed it to const user = await User.findOne({ email: (req).user }); – Mm Victory Mar 03 '22 at 18:39
41

You need to make a Declaration Merging:

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

To do that you can create a file called types.d.ts at your project src folder (or wherever you want) with the following content:

declare namespace Express {
  export interface Request {
      user: any;
  }
  export interface Response {
      user: any;
  }
}

Here we are telling the compiler to add user propertie to our Request and Response definiton.

Next, we need to attach this to our tsconfig.json.

Example:

{
  "compilerOptions": {
      "module": "commonjs",
      "moduleResolution": "node",
      "pretty": true,
      "sourceMap": true,
      "target": "es6",
      "outDir": "./dist",
      "baseUrl": "./lib"
  },
  "include": [
      "lib/**/*.ts"
  ],
  "exclude": [
      "node_modules"
  ],
  "files":["types.d.ts"]
}

Now, the typescript compiler know that Request, which has a property called user that in my case, can accept any json object. You can restrict the type for string if you want.

The final result is that, no error in VSCode =)

jhoanna
  • 1,797
  • 25
  • 25
Bruno Bastos
  • 786
  • 6
  • 6
20

You're getting this error because there's no type definition for the user property in the the native express Request object. You should install the type definitions for the middleware you're using to add user to the request.

For example, if you're using the passport library for JWT authentication as middleware:

router.get('/auth/userInfo', passport.authenticate('jwt', {session:false}), (req, res, next) => {
  // Get their info from the DB and return it
  User.findOne({ email: req.user.email }, (err, user) => {
    if (err) {return next(err);}
    ...
    ...

You should add the type definitions for passport:

npm install --save @types/passport
duliba
  • 269
  • 2
  • 9
  • How do you import the passport Request interface? – Kriss Apr 13 '19 at 00:37
  • 1
    @Kriss just installing the types as duliba described it should be enough for your type checker to augment express' Request interface. For more information, have a look at the typings file: https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/types/passport/index.d.ts#L16 – Pelle Jacobs Apr 15 '19 at 09:43
  • 4
    This is a very quick fix. Thanks. Worked for me. – Melvin Jun 13 '19 at 10:22
17
  1. create new folder named types inside "src" folder
  2. create file <filename>.d.ts in types folder
  3. add following code:
declare namespace Express {
  export interface Request {
    user: any
  }
}
  • you will notice that error disappeared
  1. inside section typeRoots add the following to tsconfig.json file:
"typeRoots": [
      "./node_module/@types",
      "./src/types"
],
  • you can use type Request of express like this:
import { Request, Response } from "express";
router.get('/auth/userInfo', this.validateUser,  (req: Request, res: Response) => {
    res.json(req.user);
});
  • if you use ts-node you will get the same error when you try to execute your .ts file because ts-node ignore .d.ts fies unless you add flag --files to package.json script like this:
"scripts": {
    .
    .
    "dev": "ts-node --files src/app.ts"
  }
  • for more about this topic you can see this repository that created by Microsoft team.
Islam Goher
  • 413
  • 5
  • 9
  • For me it was adding the `--files` flag to my `ts-node-dev` command (running `ts-node` under the hood). Now I get no errors at startup. For reference see: https://www.npmjs.com/package/ts-node#files – Benyam Ephrem Aug 31 '22 at 15:41
  • I am using `ts-node` with `nodemon`. I had to update my script like `nodemon --files server.ts` – feyzullahyildiz May 02 '23 at 23:03
7

If you're using ts-node and not ts-node-dev, do this:

  1. Create a typings folder in your src folder.
  2. Create a folder within the typings folder with the name of the package you intend to extend.
  3. Create an index.d.ts file with the new folder.

In my case, I am extending express, I ended up with something like this:

  src/
    - typings/
      - express/
        - index.d.ts

within the index.d.ts file I have this:

declare module Express {
    export interface Request {
        bucketUrl: string;
        fileName: string;
    }
}

Remember to update your .tsconfig:

{
  "compilerOptions": {
    "typeRoots" : ["./node_modules/@types", "./typings"]
  }
}
Tolumide
  • 944
  • 9
  • 11
  • As of August 2021, it seems that using `namespace` (instead of `module`) is recommended. https://github.com/typescript-eslint/typescript-eslint/blob/v4.22.0/packages/eslint-plugin/docs/rules/prefer-namespace-keyword.md – Hiroki Aug 20 '21 at 03:41
4

For people using GraphQL, there comes a problem with the graphqlExpress function not accepting your new interface. I was attempting to follow this guide until I ran into this exact problem. I have figured out this solution:

this.app.use("/graphql", bodyParser.json(), this.auth, graphqlExpress((req: AuthRequest | undefined) => ({ 
    schema,
    context: {
        user: req!.user
    }
})));

And in the AuthRequest interface:

import {Request} from "express"

export interface AuthRequest extends Request {
    user?: string
}

Typescript will not allow you to use AuthRequest unless it is also allowed to be undefined. Therefore the | undefined. (source)

After this point, the user object does not 100% exist on the Request object, hence the ? after user.

SpBills
  • 75
  • 1
  • 6
4

In my case, using ? to solve the problem.

import { Request } from "express";

interface MyUserRequest extends Request {
  // Use `user?:` here instead of `user:`.
  user?: string;
}

router.get('/auth/userInfo', (req: MyUserRequest, res) => {
  res.status(200).json(req.user)
});

Just extend "Request" without declaration.

Jay Lu
  • 1,515
  • 8
  • 18
3

Because the "user" property doesn't exist in the native express "Request" object.

There are various workarounds for this issue.

1- Simplest one - ... as any

router.get('/auth/userInfo', this.validateUser,  (req, res) => {
    res.json((req as any).user);
})

2- Add the following code to the app.js or interface file once.

    declare module "express-serve-static-core" {
       interface Request {
           user: any;
       }
    }
ajay_full_stack
  • 494
  • 7
  • 13
2

Add a typeRoots to your tsconfig.json, this will tell typescript where to look to find declaration files. By default typescript looks in /node_modules/@types, but when you specify this property, those defaults are cleared. You can read more here.

tsconfig.json

{
  "compilerOptions": {
    "typeRoots": ["./node_modules/@types", "./src/util"]
  }
}            

types.d.ts

import { User } from '../models/user'

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

Folder structure

node_modules
tsconfig.json
/src/
   /models/
      user.ts
   /util/
      types.d.ts
jeninja
  • 788
  • 1
  • 13
  • 27
1

I was using okta-angular-node with express for this https://developer.okta.com/blog/2018/10/30/basic-crud-angular-and-node

I came up with similar error for req.user. Please check the server/auth.ts inside the above given link.

Type casting worked for me. Try to cast as follows

req.user to (req['user'])

  • Good solution... but unfortunately `eslint` doesn't like this: `error ["user"] is better written in dot notation dot-notation` – misolo Jan 09 '22 at 22:15
1

You can try including the below snippet in your middleware.ts file:

declare module 'express-serve-static-core' {
  interface Request {
    user?: string
  } 
}
1
declare global {
  namespace Express {
    interface Request {
      user?: any
    }
  }
}
Tarik
  • 53
  • 8
0

You need to decorate the request using fastify decorators as mentioned below,

fastify.decorateRequest('user', <pass null or empty string here>)

and handle what should be in the user object.

Official document - https://www.fastify.io/docs/latest/Decorators/

Rafael
  • 499
  • 1
  • 3
  • 12
0

I had a working index.d.ts using other tips here but the fact that it was in the same folder as an unrelated index.ts was causing it to not work. I moved index.d.ts to its own types folder and the types started to get used. I'm guessing there was a collision with index.ts being at the same level.

Just going to share my types/index.d.ts in case it helps someone who is also using Prisma

import { User as PrismaUser } from "../prisma/prismaclient";

declare module "passport" {
  namespace Express {
    interface User extends PrismaUser {}
  }
}

declare module 'express-serve-static-core' {
  interface Request {
    user?: PrismaUser
  }
  interface Response {
    user?: PrismaUser
  }
}


declare module "express-session" {
  interface SessionData {
    user: PrismaUser;
  }
}
Joseph Connolly
  • 891
  • 11
  • 21
-1

Just do

import { Request, Response} from "express";

then do this;

router.get('/auth/userInfo', this.validateUser, (req:any, res:Response) => { res.json(req.user); });