How to extend express.Request with tsserver + JSDoc?

379 views Asked by At

I've using tsserver with JSDoc in vim on a JavaScript project. I've run up against the following problem:

/** @type {import('express').Handler} */
function requireUser(req, res, next) {
  if (!req.user) {
    throw new Error("Unauthorized");
  }
  next();
}

Here's the error:

[tsserver] Property 'user' does not exist on type 'Request<ParamsDictionary, any, any, ParsedQs, Record<string, any>>'. [E]

Of course, there are hundreds of places in my code where req.user is referenced.

Here's my current tsconfig.json, for reference:

{
  "compilerOptions": {
    "target": "es2021",
   "module": "commonjs",
    "allowJs": true,
    "checkJs": true,
    "noEmit": true,
    "strict": true,
    "alwaysStrict": true,
    "noUnusedLocals": true,
    "typeRoots": ["./@types", "./node_modules/@types"],
    "esModuleInterop": true,
    "preserveSymlinks": false,
    "maxNodeModuleJsDepth": 20,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true
  },
  "include": ["src/**/*.js"],
  "exclude": ["node_modules"]
}

I've tried putting a file in ./@types/types.d.ts with the whole namespace Express bit for Typescript Declaration Merging, but it doesn't seem to be taking effect.

1

There are 1 answers

0
coolaj86 On

The full explanation is here:

https://github.com/BeyondCodeBootcamp/jsdoc-typescript-starter

In short:

tsconfig.json:

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

./typings/express/index.d.ts:

// You can extend other types that you modify with middleware.
declare namespace Express {
  // Here we tell the linter to expect `user`, `authn`, and `query`
  // to exist on Request objects, and a custom `result` handler to
  // exist on Response objects.
  export interface Request {
    query: Record<string, string>;
    user?: User;
    authn?: any;
  }

  // Here we tell the linter about our special alternative to
  // res.result
  export interface Response {
    result?: (result: any) => void;
  }