If you’re using TypeScript with Express, you may well hit on TS2339: Property 'whatever' does not exist on type 'RequestQuery'
.
It’s actually pretty easy to solve. Perhaps more frustrating than anything, as you may well have lots of examples of this problem, especially if migrating an older project from JavaScript, or less restrictive TypeScript, to something a little more strict.
Here’s an example from this current project I’m working on:
Which then gives:
[nodemon] 3.0.1
[nodemon] to restart at any time, enter `rs`
[nodemon] watching path(s): *.*
[nodemon] watching extensions: ts,json
[nodemon] starting `ts-node ./src/index.ts`
/home/chris/Development/keyword-db/api/node_modules/ts-node/src/index.ts:859
return new TSError(diagnosticText, diagnosticCodes, diagnostics);
^
TSError: ⨯ Unable to compile TypeScript:
src/controllers/keyword/get-one-with-difficulty.ts:6:5 - error TS2322: Type 'string | string[] | ParsedQs | ParsedQs[] | undefined' is not assignable to type 'string'.
Type 'undefined' is not assignable to type 'string'.
6 keyword: req.query.keyword,
~~~~~~~
src/lib/db/keyword/get-one-with-difficulty.ts:4:3
4 keyword: string;
~~~~~~~
The expected type comes from property 'keyword' which is declared here on type 'GetOneWithDifficulty'
src/controllers/keyword/get-one-with-difficulty.ts:7:5 - error TS2322: Type 'string | string[] | ParsedQs | ParsedQs[] | undefined' is not assignable to type 'string'.
7 tld: req.query.tld,
~~~
src/lib/db/keyword/get-one-with-difficulty.ts:5:3
5 tld: string;
~~~
The expected type comes from property 'tld' which is declared here on type 'GetOneWithDifficulty'
at createTSError (/home/chris/Development/keyword-db/api/node_modules/ts-node/src/index.ts:859:12)
at reportTSError (/home/chris/Development/keyword-db/api/node_modules/ts-node/src/index.ts:863:19)
at getOutput (/home/chris/Development/keyword-db/api/node_modules/ts-node/src/index.ts:1077:36)
at Object.compile (/home/chris/Development/keyword-db/api/node_modules/ts-node/src/index.ts:1433:41)
at Module.m._compile (/home/chris/Development/keyword-db/api/node_modules/ts-node/src/index.ts:1617:30)
at Module._extensions..js (node:internal/modules/cjs/loader:1287:10)
at Object.require.extensions.<computed> [as .ts] (/home/chris/Development/keyword-db/api/node_modules/ts-node/src/index.ts:1621:12)
at Module.load (node:internal/modules/cjs/loader:1091:32)
at Function.Module._load (node:internal/modules/cjs/loader:938:12)
at Module.require (node:internal/modules/cjs/loader:1115:19) {
diagnosticCodes: [ 2322, 2322 ]
}
[nodemon] app crashed - waiting for file changes before starting...
Code language: PHP (php)
So a pretty noisy problem.
The Solution
A fairly easy one.
Let’s say the incoming request looks something like this:
https://192.168.0.54:8200/keyword/get-all-with-difficulty?limit=10000&offset=0
Code language: JavaScript (javascript)
Your query parameters here would be limit
and offset
.
These are entirely ‘made up’ things that we are asking for. This may be just as valid:
https://192.168.0.54:8200/keyword/get-all-with-difficulty?peanuts=hello&sport=quidditch
Code language: JavaScript (javascript)
These are concepts specific to our application. Express, on the other hand, knows there are potentially some query parameters, but it doesn’t know what they are in the specific context not only of our application, but in this very specific request.
Basically, we need to tell it.
That’s what TypeScript is complaining about.
Here’s how to solve it:
import { Request, Response } from "express";
import getOneWithDifficultyQuery from "../../lib/db/keyword/get-one-with-difficulty";
interface RequestParams {}
interface ResponseBody {}
interface RequestBody {}
interface RequestQuery {
keyword: string;
tld: string;
}
const getOneWithDifficulty = async (
req: Request<RequestParams, ResponseBody, RequestBody, RequestQuery>,
res: Response,
) => {
const result = await getOneWithDifficultyQuery({
keyword: req.query.keyword,
tld: req.query.tld,
});
res.status(200).json(result);
};
export default getOneWithDifficulty;
Code language: TypeScript (typescript)
With this change, the TypeScript code now compiles.
And in your particular case, you need to provide your specific query string parameters.
A pointer here: all query parameters are strings.
For example, if you have a URL like this: http://example.com/api/resource?param1=123¶m2=test
, and you access req.query
, you will get:
{
param1: '123',
param2: 'test'
}
Code language: JSON / JSON with Comments (json)
As you can see, even though param1
appears to be a number in the URL, it is still treated as a string by default in Express. If you need these parameters to be of specific data types, you’ll need to manually parse and convert them to the desired types in your code. For example, you can use parseInt
or parseFloat
to convert them to numbers.
Here’s an example:
const param1Number = parseInt(req.query.param1, 10); // Parse as a base-10 integer
const param2Number = parseFloat(req.query.param2);
console.log(param1Number); // Now it's a number
console.log(param2Number); // Now it's a floating-point number
Code language: TypeScript (typescript)
This way, you can ensure that the query parameters are treated as the appropriate data types in your application.
How Can I Figure Out The Types?
It’s one thing being shown what to do. But it’s even more useful to understand why you need to do it in the first place.
Looking back specifically at this line:
req: Request<RequestParams, ResponseBody, RequestBody, RequestQuery>,
Code language: TypeScript (typescript)
How did I know that the Express Request
parameter takes those four specific type parameters?
And why are they in <Brackets>
?
Well, the truth is, I didn’t just know.
I looked it up.
If you use a decent IDE like WebStorm, you can ctrl + click on “things” and be taken to their implementation, or as is the case with TypeScript types, see their definitions.
So, for example, I can hover over Request
and ctrl + click it to be taken to the Request
type definition provided by Express:
That explains the positions of each of the entries I added to the req
definition:
# mine
req: Request<RequestParams, ResponseBody, RequestBody, RequestQuery>,
# theirs
req: Request<P, ResBody, ReqBody, ReqQuery>,
Code language: HTML, XML (xml)
You could just as ‘correctly’ do this:
req: Request<any, unknown, {}, RequestQuery>,
Code language: HTML, XML (xml)
But honestly I like to name the concepts, which is why I create an interact for each, even if it’s empty.
I guess a follow on question for this is then how did I know to type:
req: Request
# or
req: express.Request
Code language: HTTP (http)
In the first place?
That is a good question. I honestly don’t know. Probably via StackOverflow. At this point I just do it out of habit.