Skip to content

Commit 225921c

Browse files
feat: add better typing for TsRestRequest (#372)
* feat: add better typing for TsRestRequest * dev(express-types): add tests for req type inference * fix: add type test for params * chore: bump @ts-rest/express * Update late-dingos-search.md * Update late-dingos-search.md --------- Co-authored-by: Michael Angelo Rivera <55844504+michaelangeloio@users.noreply.github.com>
1 parent 5088cf4 commit 225921c

3 files changed

Lines changed: 120 additions & 5 deletions

File tree

.changeset/late-dingos-search.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@ts-rest/express': minor
3+
---
4+
5+
feat: added stronger typing for the `req` object in the route handler for '@ts-rest/express'
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
import { z } from 'zod';
2+
import { initContract } from '@ts-rest/core';
3+
import { AppRouteImplementation } from './types';
4+
import { IncomingHttpHeaders } from 'http';
5+
6+
export type Equal<a, b> = (<T>() => T extends a ? 1 : 2) extends <
7+
T
8+
>() => T extends b ? 1 : 2
9+
? true
10+
: false;
11+
12+
export type Expect<a extends true> = a;
13+
14+
const c = initContract();
15+
16+
const contract = c.router({
17+
postSomethingIndex: {
18+
method: 'POST',
19+
path: `/:something/index.html`,
20+
body: z.object({
21+
echoHtml: z.string(),
22+
}),
23+
query: z.object({
24+
extraPath: z.string().optional(),
25+
}),
26+
headers: z.object({
27+
'x-application-index': z.literal('index'),
28+
}),
29+
pathParams: z.object({
30+
something: z.string(),
31+
}),
32+
responses: {
33+
200: c.otherResponse({
34+
contentType: 'text/html',
35+
body: z.string().regex(/^<([a-z][a-z0-9]*)\b[^>]*>(.*?)<\/\1>$/im),
36+
}),
37+
},
38+
},
39+
});
40+
41+
it('should have type inference on req', () => {
42+
type PostIndexImplementation = AppRouteImplementation<
43+
typeof contract.postSomethingIndex
44+
>;
45+
46+
type PostIndexParam = Parameters<PostIndexImplementation>[0];
47+
48+
type PostIndexReq = PostIndexParam['req'];
49+
50+
type PostIndexBod = PostIndexReq['body'];
51+
52+
type ShouldHaveReq = Expect<
53+
Equal<
54+
PostIndexBod,
55+
{
56+
echoHtml: string;
57+
}
58+
>
59+
>;
60+
61+
type PostIndexHeaders = PostIndexReq['headers'];
62+
63+
type ShouldHaveHeaders = Expect<
64+
Equal<
65+
PostIndexHeaders,
66+
{
67+
'x-application-index': 'index';
68+
} & IncomingHttpHeaders
69+
>
70+
>;
71+
72+
type PostIndexQuery = PostIndexReq['query'];
73+
74+
type ShouldHaveQuery = Expect<
75+
Equal<
76+
PostIndexQuery,
77+
{
78+
extraPath?: string;
79+
}
80+
>
81+
>;
82+
83+
type PostIndexParams = PostIndexReq['params'];
84+
85+
type ShouldHaveParams = Expect<
86+
Equal<
87+
PostIndexParams,
88+
{
89+
something: string;
90+
}
91+
>
92+
>;
93+
});

libs/ts-rest/express/src/lib/types.ts

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,15 @@ import {
44
AppRouteQuery,
55
AppRouter,
66
ServerInferRequest,
7+
ServerInferResponseBody,
78
ServerInferResponses,
89
} from '@ts-rest/core';
9-
import { Express, NextFunction, Response } from 'express-serve-static-core';
10+
import {
11+
Express,
12+
NextFunction,
13+
Response,
14+
Request,
15+
} from 'express-serve-static-core';
1016
import { RequestValidationError } from './request-validation-error';
1117

1218
type AppRouteQueryImplementation<T extends AppRouteQuery> = (
@@ -32,10 +38,21 @@ export type AppRouteImplementation<T extends AppRoute> =
3238
? AppRouteQueryImplementation<T>
3339
: never;
3440

35-
export type TsRestRequest<T extends AppRouter | AppRoute> =
36-
Express['request'] & {
37-
tsRestRoute: FlattenAppRouter<T>;
38-
};
41+
export type TsRestRequest<
42+
T extends AppRouter | AppRoute,
43+
F extends FlattenAppRouter<T> = FlattenAppRouter<T>,
44+
S extends ServerInferRequest<F> = ServerInferRequest<F>
45+
> = Request<
46+
'params' extends keyof S ? S['params'] : Express['request']['params'],
47+
ServerInferResponseBody<F>,
48+
'body' extends keyof S ? S['body'] : Express['request']['body'],
49+
'query' extends keyof S ? S['query'] : Express['request']['query']
50+
> & {
51+
tsRestRoute: F;
52+
headers: 'headers' extends keyof S
53+
? S['headers']
54+
: Express['request']['headers'];
55+
};
3956

4057
export type TsRestRequestHandler<T extends AppRouter | AppRoute> = (
4158
req: TsRestRequest<T>,

0 commit comments

Comments
 (0)