65

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?

8
  • It already has the right type; it's telling you that query.foo might be undefined and that's true. Commented Aug 22, 2020 at 16:44
  • 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' Commented Aug 22, 2020 at 16:55
  • 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). Commented Aug 22, 2020 at 16:57
  • 1
    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 is Commented Aug 22, 2020 at 16:59
  • 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). Commented Aug 22, 2020 at 17:02

13 Answers 13

57

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)
Sign up to request clarification or add additional context in comments.

9 Comments

Have you tested this? I'm trying to use this approach and getting the error: Type 'Request' is not generic.
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".
Or const getHandler: RequestHandler<unknown, unknown, unknown, Foo> = (req, res) => { ... }
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.
This only works for the query object. How would I do this for the params and body objects?
|
35

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
}

1 Comment

You really helped me a lot. It's not mentioned anywhere, google did not help either. 😀
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.

1 Comment

This is a good approach. Handles types well, and is nice and simple
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;
}

2 Comments

Yeah, I know, but I would like to avoid type casting
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.
4

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
}

Comments

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.

Comments

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)

1 Comment

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 expect
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)

Comments

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.

Comments

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.

Comments

-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 ...

}

Comments

-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};

Comments

-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;

  ...
}

1 Comment

This could backfire. You should handle or drop bad requests, not cheese the compiler.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.