How to type `request.query` in express using TypeScript?
Asked Answered
C

13

59

I'm running an express.js application using TypeScript. Every time I try to process request.query.foo I get the following error:

Argument of type 'string | ParsedQs | string[] | ParsedQs[] | undefined' is not assignable to parameter of type 'string'.
  Type 'undefined' is not assignable to type 'string'.

Setup:

import { Request, Response, Router } from 'express';

const router = Router();

function getHandler(request: Request, response: Response) {
  const { query } = request;

  query.foo; // string | QueryString.ParsedQs | string[] | QueryString.ParsedQs[] | undefined

}

router.route('/')
  .get(getHandler)

Is there a proper way to type request.query without casting?

Colcannon answered 22/8, 2020 at 16:43 Comment(8)
It already has the right type; it's telling you that query.foo might be undefined and that's true.Burrstone
True, but how would I solve the following? if (query.foo) { parseInt(foo, 10) } This would still resolve in Argument of type 'string | ParsedQs | string[] | ParsedQs[]' is not assignable to parameter of type 'string'. Type 'ParsedQs' is not assignable to type 'string'Colcannon
Well that's all true, isn't it? You've only ruled out undefined, it's still not certain to be parseable as an integer. If you have extra information, like it will definitely only ever be a string, you can provide it to the compiler as a type assertion: parseInt(foo as string, 10).Burrstone
Yes, I also came up with this solution. I would prefer to not use any casting if possible. I've seen Request is a Generic which can be enhanced but I would like to only type request.query and leave everything else like request.body and request.params as isColcannon
Then deal with those other possible cases. Look at e.g. typescriptlang.org/docs/handbook/…. Overriding the query type won't change the runtime behaviour (it can't, types and TS don't exist at runtime).Burrstone
I already tried to use TypeGuards but even this does not work function isNumber(num: any): num is number { return Number.isInteger(num); }. It still returns in Type 'ParsedQs & number' is not assignable to type 'string'.Colcannon
It's not clear how you're using that, but also why - none of the options you're trying to narrow from is that it's already a number, you're trying to check if it's a string before trying to parse it.Burrstone
@Colcannon - Check the type as you extract a field from query. const foo = typeof query.foo === "string" ? query.foo : ""; After this, foo is guaranteed to be a string and only a string.Seemly
C
28

Definition

The Request is a generic which accepts additional definitions:

interface Request<
  P = core.ParamsDictionary,
  ResBody = any,
  ReqBody = any,
  ReqQuery = core.Query,
  Locals extends Record<string, any> = Record<string, any>
> extends core.Request<P, ResBody, ReqBody, ReqQuery, Locals> {}

Source: https://github.com/DefinitelyTyped/DefinitelyTyped/blob/89a4f41af507e68daf5f4794ad257f48920275f2/types/express/index.d.ts#L113-L118

Example

import type { Request, Response } from 'express';

interface RequestParams {}

interface ResponseBody {}

interface RequestBody {}

interface RequestQuery {
  foo: string;
}

function getHandler(
  request: Request<RequestParams, ResponseBody, RequestBody, RequestQuery>,
  response: Response
) {
  const { query } = request;

  query.foo; // string
}
Colcannon answered 23/7, 2022 at 10:29 Comment(1)
You really helped me a lot. It's not mentioned anywhere, google did not help either. 😀Denaturalize
E
52

The solution is to use generics in the following manner.

const router = Router();

interface Foo {
    foo: string;
}

function getHandler(request: Request<{}, {}, {}, Foo>, response: Response) {
  const { query } = request;

  query.foo;

}

router.route('/')
  .get(getHandler)
Ene answered 21/12, 2020 at 2:17 Comment(9)
Have you tested this? I'm trying to use this approach and getting the error: Type 'Request' is not generic.Cathodoluminescence
Tested on @types/express 4.17.11. Works great! BTW, it would be better to use Request<unknown, unknown, unknown, Foo>. Otherwise, the ESLint will give error: Don't use {} as a type. {} actually means "any non-nullish value".Tellurite
Or const getHandler: RequestHandler<unknown, unknown, unknown, Foo> = (req, res) => { ... }Microscopium
For anyone else who reads my comment and wonders...it turns out I had not imported Request from the Express types, and so Fetch type of Request was being used.Cathodoluminescence
This only works for the query object. How would I do this for the params and body objects?Sedentary
Similar to @HongboMiao, I arrived at a similar ESLint complaint, but believe that instead of using unknown for each, wouldn't Record<string, unknown> be better as these should always be objects, right?Cathodoluminescence
If you get Type 'Request' is not generic. add import type { Request } from "express";Nomarchy
it's a good practice to make params such as foo?: string optional, since they may not be present.Vinni
Is there anyway to make the foo name generic so I don't have to create an interface for every single route?Subversive
C
28

Definition

The Request is a generic which accepts additional definitions:

interface Request<
  P = core.ParamsDictionary,
  ResBody = any,
  ReqBody = any,
  ReqQuery = core.Query,
  Locals extends Record<string, any> = Record<string, any>
> extends core.Request<P, ResBody, ReqBody, ReqQuery, Locals> {}

Source: https://github.com/DefinitelyTyped/DefinitelyTyped/blob/89a4f41af507e68daf5f4794ad257f48920275f2/types/express/index.d.ts#L113-L118

Example

import type { Request, Response } from 'express';

interface RequestParams {}

interface ResponseBody {}

interface RequestBody {}

interface RequestQuery {
  foo: string;
}

function getHandler(
  request: Request<RequestParams, ResponseBody, RequestBody, RequestQuery>,
  response: Response
) {
  const { query } = request;

  query.foo; // string
}
Colcannon answered 23/7, 2022 at 10:29 Comment(1)
You really helped me a lot. It's not mentioned anywhere, google did not help either. 😀Denaturalize
P
14

I like to use the following approach in my projects.

  const { url } = req.query;
  
  if (typeof url !== "string") {
    throw new ServerError("Query param 'url' has to be of type string", 400);
  }

After checking the type TypeScript won't complain any more for this scope.

Placentation answered 20/7, 2021 at 9:41 Comment(1)
This is a good approach. Handles types well, and is nice and simpleTaejon
D
5

you can do it like this :-

interface Query {
   param1:string;
   param2:string;
};

function getHandler(request: Request, response: Response) {
   const {param1,param2} = request.query as unknown as Query;
}
Dominations answered 21/8, 2021 at 7:3 Comment(2)
Yeah, I know, but I would like to avoid type castingColcannon
Every time you use "as" you basically override the type system, so you should only use it as a last resort. It's a good way to shoot yourself in the foot. Use the new "satisfies" instead if you can. But it doesn't help in this case.Sandpaper
S
3

Instead of tricking typescript, you should be validating your incoming data with actual JS.

If you are only interested in handling foo when it's a string, then validate that it's a string as you extract it from query.

function getHandler(request: Request, response: Response) {
    const { query } = request;

    const foo = (typeof query.foo === "string") ? query.foo : "";

    // Want to error check?
    if (foo === "") {
        // Handle missing foo
    }

    // You now have a guaranteed string that typescript recognizes as a string

    const myNumber = parseInt(foo); // No warnings here
}
Seemly answered 14/3, 2023 at 1:23 Comment(0)
S
2

This is what I use, which seems to be compatible with strict/recommended TS settings:

import type { Request } from "express";

type EmptyObject = Record<string, never>;

export type RequestWithBody<T> = Request<
  EmptyObject,
  EmptyObject,
  T,
  EmptyObject
>;

export type RequestWithQuery<T> = Request<
  EmptyObject,
  EmptyObject,
  EmptyObject,
  T
>;

And then something like:


export async function somethingPost(
  req: RequestWithBody<Payload>,
  res: Response
) {
...
}

I don't know how to type the Response properly. If I use Response<SomethingResponse | string> my function seems to be strict about the json return values, but express doesn't accept it as an argument to post().

The | string is because I either successfully return the response or return an error code with a string message in the body.

Sandpaper answered 29/9, 2023 at 15:20 Comment(0)
F
0

In my humble opinion the simpler and straight forward method is to use the StringConstructor. Any downside of this method in this particular situation is welcome in the comments.

var String: StringConstructor (value?: any) => string Allows manipulation and formatting of text strings and determination and location of substrings within strings.

import { Request, Response, Router } from 'express';

const router = Router();

function getHandler(request: Request, response: Response) {
  const { foo } = request.query;

  String(foo) // string
}

router.route('/').get(getHandler)
Forb answered 2/7, 2022 at 16:3 Comment(1)
If you convert an object to a string, you will get "[object Object]" and if you convert an array to a string you will get coma-separated values. You should handle these edge cases, either by not handling the request or by sending a bad request error to the client when the query parameters are not formatted the way you expectMckoy
U
0

Just an idea:

import { Request, Response, Router } from 'express';

const router = Router();

interface HandlerRequest extends Request {
    query: {
        foo: string
    }
}

function getHandler(request: HandlerRequest, response: Response) {
  const {query: {foo}} = request;

  if (foo) {
    // Do something
  } else {
    // Otherwise...
  }       
}

router.route('/')
  .get(getHandler)
Undertrump answered 21/3, 2023 at 22:10 Comment(0)
M
0

I just stumbled upon this and discovered that express depends on qs, which supports non-standard query string formats like arbitrarily nested arrays and objects.

This is why we are forced to handle cases where request.query.foo is something other than string | undefined: they could actually happen.

The correct course of action is to either ignore the request or let the client know that it's malformed. I saw a lot of answers recommending to simply turn off type checking. This is truly bad advice. You should always investigate whether an error could actually happen at runtime before silencing a type error. It's what TS is for.

Mckoy answered 21/4, 2023 at 18:39 Comment(0)
M
0

You can also try this:

// express.interface.ts

import { Request, Response, NextFunction } from 'express'

interface RequestParams {}

interface ResponseBody {}

interface RequestBody {}

interface RequestQuery {
  /* Add custom parameters to use like `req.query.id` */
  id?: string
}

interface ResponseBody {}

export interface CustomRequest<P = RequestParams, ResBody = ResponseBody, ReqBody = RequestBody, Q = RequestQuery>
  extends Request<P, ResBody, ReqBody, Q> {}

export interface CustomResponse<ResBody = ResponseBody> extends Response<ResBody> {}

export interface CustomNextFunction extends NextFunction {}

Add proper function or parameters on each interfaces. Also you could add custom functions in each CustomRequest, CustomResponse, CustomNextFunction like error handling.

Malleus answered 26/9, 2023 at 3:37 Comment(0)
T
-1

Another possible solution is to use Type Assertion:

function getHandler(request: Request, response: Response) {

  const foo: string = request.query.foo as string

  //do something with foo ...

}
Terrell answered 24/7, 2021 at 23:9 Comment(0)
N
-1

I know this is an old question...

...but here is the best approach (without annoying typecasting):

const { email } = req.query as unknown as {email:string};

Full example:

export async function getUserByEmail(req: Request, res: Response, next: NextFunction) {
    const { email } = req.query as {email:string};
    console.log(email)
}

You do it inline without declaring interfaces.

const { var1, var2, var3 } = req.query as { var1:string, var2:number, var3:boolean }

EDIT:

If you get an error just add

as unknown

Example:

const { email } = req.query as uknown as {email:string};
Norbertonorbie answered 7/4 at 0:17 Comment(0)
J
-2

The best solution for me was to first cast as unknown, and then my custom interface.

interface CustomReqQuery {
   foo: string;
   bar: string;
}

function getHandler(req: Request, res: Response) {
  const { foo, bar } = req.query as unknown as CustomReqQuery;

  ...
}
Justly answered 17/2, 2023 at 22:7 Comment(1)
This could backfire. You should handle or drop bad requests, not cheese the compiler.Mckoy

© 2022 - 2024 — McMap. All rights reserved.