Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

help typing Request query with object #5608

Open
GregLtrnr opened this issue Apr 18, 2024 · 2 comments
Open

help typing Request query with object #5608

GregLtrnr opened this issue Apr 18, 2024 · 2 comments

Comments

@GregLtrnr
Copy link

Hello, I'm running into a problem typing filter query using the express router with my request controller

//router.ts
import { listArticles } from "./controller";

const router = express.Router();

router.get("/", listArticles);
//controller.ts

export interface GetArticleListRequestQueryParams {
  page: string;
  pageSize: string;
  [key: string]: string | number | object;
}

export async function listArticles(req: Request<unknown, unknown, unknown, GetArticleListRequestQueryParams>, res: Response){
  try{
      const parameters = req.query
      const page: number = Number(parameters.page) ?? 1;
      const pageSize: number = Number(parameters.pageSize) ?? 10;
      
      if (parameters?.filter && typeof parameters.filter === "object" && "categories" in parameters.filter) {
      //...
      }
  }
}

So basically, I want to be able to get custom filters in my request, like this: GET /article?page=1&pageSize=5&filter[categories]=categId

Using this request, using a console log on req.query, i get this:

//{ page: '1', pageSize: '5', filter: { categories: 'categId' } }

This is exactly what I want, but the issue here is for typing the query param in my req argument, the param I put GetArticleListRequestQueryParams is giving me an error in the router.ts:

No overload matches this call.
  The last overload gave the following error.
    Argument of type '(req: Request<unknown, unknown, unknown, GetArticleListRequestQueryParams, Record<string, any>>, res: Response<any, Record<string, any>>) => Promise<...>' is not assignable to parameter of type 'RequestHandlerParams<ParamsDictionary, unknown, unknown, ParsedQs, Record<string, any>>'.
      Type '(req: Request<unknown, unknown, unknown, GetArticleListRequestQueryParams, Record<string, any>>, res: Response<any, Record<string, any>>) => Promise<...>' is not assignable to type 'RequestHandler<ParamsDictionary, unknown, unknown, ParsedQs, Record<string, any>>'.
        Types of parameters 'req' and 'req' are incompatible.
          Type 'Request<ParamsDictionary, unknown, unknown, ParsedQs, Record<string, any>>' is not assignable to type 'Request<unknown, unknown, unknown, GetArticleListRequestQueryParams, Record<string, any>>'.
            Types of property 'query' are incompatible.
              Type 'ParsedQs' is missing the following properties from type 'GetArticleListRequestQueryParams': page, pageSizets(2769)
index.d.ts(153, 5): The last overload is declared here.

By getting a look to Request.ReqQuery, I need to provide a ParsedQS type, which look like this:

    interface ParsedQs {
        [key: string]: undefined | string | string[] | ParsedQs | ParsedQs[];
    }

So my question is, how can I give objects type to the query params for my filter attribute without making it throw an error ? (I tried putting just string, but then I get a never type on my if (parameters?.filter && typeof parameters.filter === "object" && "categories" in parameters.filter).

The only solution I found is to give the type on the const parameters with a as GetArticleListRequestQueryParams but it's not clean at all :/

thanks for the help 🔥

@fahrradflucht
Copy link

First of all: express doesn't provide first-party typings. You are likely using @types/express which isn't even documented in this project, so this issue probably belongs into DefinitelyTyped/DefinitelyTyped. Not sure what the plans on official TypeScript support will be going forward.

Anyway, I tried to reproduce your problem using the code examples you provided and I can't. If I paste this code into an example typescript project using the following versions, it compiles just fine:

  "devDependencies": {
    "@types/express": "^4.17.21",
    "typescript": "^5.4.5"
  },
  "dependencies": {
    "express": "^4.19.2"
  }

Besides that, I'm not sure what you want to do is sound in the first place. For example, you just "claim" to TypeScript that the parameters have a pageSize property that is a string. However, nothing in your code ensures that that's the case, it might also be undefined, but TypeScript isn't forcing you to check for undefined anymore. So what I'm trying to say is, providing the generic ReqQuery parameter to Request is not any better than casting in your code.

The safe and "correct" way to do this in my opinion is to leave the Request types the default and then have a validation function that acts as a type guard, meaning a function that checks that the query parameters are of the expected type and return that type or throw and throws a validation error otherwise.

@wesleytodd
Copy link
Member

The types are maintained by the DT folks, that said all query parameters are strings. So you need to use something to validate and derive types from them based on what you expect them to be. I do think we have interest in some things which can help here like OpenAPI schema validation and deriving types from the validated input. At some point I want to add that to the package I maintain here. That said, today we are not yet there so you will need to write code which does type assertions on your own.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

4 participants