Make an Express Request Function Generic
To make this function generic, we'll add TQuery
to it.
This TQuery
will be inferred by what we return from the parser, which should be query.id
.
const makeTypeSafeGet =
<TQuery>(
parser: (queryParams: Request["query"]) => TQuery,
handler: RequestHandler
) =>
(req:
Transcript
0:00 We obviously need to make this function generic. I'm going to add TQuery onto here. This TQuery is going to be inferred by what we return from this parser, because it's going to be id query.id and then it needs to be passed in here.
0:16 I'm going to add TQuery to the end here. This is interesting because I need to make sure that TQuery is actually a certain shape because this should be invalid. We shouldn't be able to just return random numbers here. It should be an object of strings because that's basically how it works.
0:33 You can't pass anything into the query that then gets automatically coerced to a number. It has to be record string string. We can, if we want to, we can do extends record string string.
0:46 Or if we want to, we could say it extends request query, which is quite nice because it just means that we're using the language that the library gives us. Request query, which I think itself, if we dive deep into it and find a query, query query query, here it is.
1:06 It's RecQuery, which is defaulted to ParsedQs. Where is ParsedQs coming from? Types Qs. This is too complicated. We could go and grab ParsedQs from somewhere, but I'm happy with my record string string. We've got that.
1:22 We now need to, or in fact, what we should be seeing now is that this is being captured in the makeTypeSafeGet. We have id string being captured there because this function is returning id. If we don't return id, then that's going to disappear from the inference there.
1:38 This is all good. Now though, rec.query is still being defaulted to QueryString.ParsedQs. We need to find a way to pass it into the request there. The actual spot you need to pass it in is in request handler.
1:57 We saw this in the last explainer where we can say any, any, any, and because we don't care about the params, we don't care about the response body, don't care about the request body, but we do care about the request query.
2:11 What we can do is pass in any, any, any, and then pass in TQuery. Now down the bottom request.query gets passed through our abstraction and comes out as rec.query.id. This is amazing because it means that down here you need zero type annotations in order to get a really nice API.
2:33 This is fantastic. There's an issue though, which is types of property query are incompatible between this request and the request handler request, basically. Basically, this is now incompatible with this.
2:48 We can fix that by passing exactly the same signature to this request because we know that request it takes in P ResBody, ReqBody, ReqQuery, Locals. Request handler takes in P ResBody, ReqBody, ReqQuery, Locals. It takes in exactly the same. It's right there, in fact.
3:07 It takes in the exact same signature for its generics as request. This is really, really common when you have a library with lots of common generics, is they'll have a same signature for one thing and the other to make things easier to pass around.
3:24 We can copy and paste them over and now one is assignable to the other. Our work here is done. Very, very nice. We've now got a lovely, lovely little function which we can take in and we can even replace this with Zod if we wanted to. We just get a beautiful TypSafeGet function.