@@ -50,34 +50,34 @@ use `TypedRouter` from `express-typed` instead of `express.Router`, the rest of
5050
5151``` typescript
5252import express from " express" ;
53- import {TypedRouter , ParseRoutes } from " express-typed" ;
53+ import { TypedRouter , ParseRoutes } from " express-typed" ;
5454
5555const app = express ();
5656
5757// // THIS:
5858const router = express .Router ();
5959
6060router .get (" /" , async (req : Request , res : Response ) => {
61- res .send (" Hello World!" ).status (200 );
61+ res .send (" Hello World!" ).status (200 );
6262});
6363router .post (" /" , async (req , res ) => {
64- res .send (req .body ).status (200 );
64+ res .send (req .body ).status (200 );
6565});
6666
6767app .use (router );
6868// // -->
6969// // BECOMES THIS:
7070const typedRouter = new TypedRouter ({
71- get: {
72- " /" : async (req , res ) => {
73- res .send (" Hello World!" ).status (200 );
74- },
71+ get: {
72+ " /" : async (req , res ) => {
73+ res .send (" Hello World!" ).status (200 );
7574 },
76- post: {
77- " / " : async ( req , res ) => {
78- res . send (req . body ). status ( 200 );
79- },
75+ },
76+ post: {
77+ " / " : async (req , res ) => {
78+ res . send ( req . body ). status ( 200 );
8079 },
80+ },
8181});
8282
8383app .use (typedRouter .router );
@@ -87,7 +87,7 @@ export type AppRoutes = ParseRoutes<typeof typedRouter>;
8787// ///
8888
8989app .listen (3000 , () => {
90- console .log (" Server is running on port 3000" );
90+ console .log (" Server is running on port 3000" );
9191});
9292```
9393
@@ -150,12 +150,12 @@ utilizing helper types from `express-typed`.
150150` RouteResResolver ` is used to extract the response type from a specific route.
151151
152152``` ts
153- import {GetRouteResponseInfo , GetRouteResponseInfoHelper } from " express-typed" ;
153+ import { GetRouteResponseInfo , GetRouteResponseInfoHelper } from " express-typed" ;
154154// // RouteResResolver
155155export type RouteResResolver <
156- Path extends keyof AppRoutes ,
157- Method extends keyof AppRoutes [Path ],
158- Info extends keyof GetRouteResponseInfoHelper <AppRoutes , Path , Method > | " body" = " body"
156+ Path extends keyof AppRoutes ,
157+ Method extends keyof AppRoutes [Path ],
158+ Info extends keyof GetRouteResponseInfoHelper <AppRoutes , Path , Method > | " body" = " body"
159159> = GetRouteResponseInfo <AppRoutes , Path , Method , Info >;
160160```
161161
@@ -183,38 +183,38 @@ and `react-query`(from [here](/examples/fullstack_react_express-typed/frontend-d
183183
184184``` ts
185185// queries.ts
186- import {useQuery } from " @tanstack/react-query" ;
187- import axios , {type AxiosStatic } from " axios" ;
188- import type {AppRoutes , RouteResResolver } from " your-backend-package/src/routes/index.routes" ;
186+ import { useQuery } from " @tanstack/react-query" ;
187+ import axios , { type AxiosStatic } from " axios" ;
188+ import type { AppRoutes , RouteResResolver } from " your-backend-package/src/routes/index.routes" ;
189189
190190// an hook to fetch response from server, for any possible method(GET, POST, PUT, DELETE)
191191export const useAppQuery = <Path extends keyof AppRoutes , Method extends Extract <keyof AxiosStatic , keyof AppRoutes [Path ]>>(
192- path : Path ,
193- method : Method
192+ path : Path ,
193+ method : Method
194194) => {
195- return useQuery <RouteResResolver <Path , Method >>({
196- queryKey: [path ],
197- queryFn : async () => {
198- const res = await (axios as any )[method ](` /api${path } ` );
199- return res .data as RouteResResolver <Path , Method >;
200- },
201- });
195+ return useQuery <RouteResResolver <Path , Method >>({
196+ queryKey: [path ],
197+ queryFn : async () => {
198+ const res = await (axios as any )[method ](` /api${path } ` );
199+ return res .data as RouteResResolver <Path , Method >;
200+ },
201+ });
202202};
203203```
204204
205205and usage(see [ here] ( /examples/fullstack_react_express-typed/frontend-demo/src/App.tsx ) ):
206206
207207``` tsx
208- import {useAppQuery } from " ./queries" ;
208+ import { useAppQuery } from " ./queries" ;
209209
210210function App() {
211- const query = useAppQuery (" /" , " get" );
212- const data = query .data ;
213- // ^? const query: UseQueryResult<"Hello world", Error>
211+ const query = useAppQuery (" /" , " get" );
212+ const data = query .data ;
213+ // ^? const query: UseQueryResult<"Hello world", Error>
214214
215- console .log (" data" , data );
215+ console .log (" data" , data );
216216
217- return <>{ JSON .stringify (data )} </>;
217+ return <>{ JSON .stringify (data )} </>;
218218}
219219
220220export default App ;
@@ -233,22 +233,22 @@ export default App;
233233` RouteReqResolver ` is defined on your side with the help of ` GetRouteRequestHelper ` and ` GetRouteRequest ` :
234234
235235``` ts
236- import {GetRouteRequestHelper , GetRouteRequest , TypedRouter } from " express-typed" ;
236+ import { GetRouteRequestHelper , GetRouteRequest , TypedRouter } from " express-typed" ;
237237
238238export type RouteReqResolver <
239- Path extends keyof AppRoutes ,
240- Method extends keyof AppRoutes [Path ],
241- Info extends keyof GetRouteRequestHelper <AppRoutes , Path , Method > = Extract <keyof GetRouteRequestHelper <AppRoutes , Path , Method >, " body" >
239+ Path extends keyof AppRoutes ,
240+ Method extends keyof AppRoutes [Path ],
241+ Info extends keyof GetRouteRequestHelper <AppRoutes , Path , Method > = Extract <keyof GetRouteRequestHelper <AppRoutes , Path , Method >, " body" >
242242> = GetRouteRequest <AppRoutes , Path , Method , Info >;
243243
244244const typedRouter = new TypedRouter ({
245- " /" : {
246- get : (req : TypedRequest <{ body: " bb" ; query: " qq" }>, res ) => {
247- const body = req .body ;
248- const test = res .send (" Home" ).status (200 );
249- return test ;
250- },
245+ " /" : {
246+ get : (req : TypedRequest <{ body: " bb" ; query: " qq" }>, res ) => {
247+ const body = req .body ;
248+ const test = res .send (" Home" ).status (200 );
249+ return test ;
251250 },
251+ },
252252});
253253
254254type HomePageBody = RouteReqResolver <" /" , " get" >;
@@ -261,24 +261,24 @@ example of using this info with react-query mutation(
261261see [ here] ( /examples/fullstack_react_express-typed/frontend-demo/src/mutations.ts ) ):
262262
263263``` ts
264- import {DefaultError , useMutation } from " @tanstack/react-query" ;
264+ import { DefaultError , useMutation } from " @tanstack/react-query" ;
265265import axios from " axios" ;
266- import {AppRoutes , RouteReqResolver , RouteResResolver } from " express-typed-demo/src/routes/index.routes" ;
266+ import { AppRoutes , RouteReqResolver , RouteResResolver } from " express-typed-demo/src/routes/index.routes" ;
267267
268268const useAppMutation = <Path extends keyof AppRoutes , Method extends keyof AppRoutes [Path ]>(path : Path , method : Method ) => {
269- const mutation = useMutation <RouteResResolver <Path , Method >, DefaultError , RouteReqResolver <Path , Method >>({
270- mutationKey: [" mutation" , path , method ],
271- mutationFn : async () => {
272- const res = await (axios as any )[method ](` /api${path } ` );
273- return res .data as RouteResResolver <Path , Method >;
274- },
275- });
276- return mutation ;
269+ const mutation = useMutation <RouteResResolver <Path , Method >, DefaultError , RouteReqResolver <Path , Method >>({
270+ mutationKey: [" mutation" , path , method ],
271+ mutationFn : async () => {
272+ const res = await (axios as any )[method ](` /api${path } ` );
273+ return res .data as RouteResResolver <Path , Method >;
274+ },
275+ });
276+ return mutation ;
277277};
278278
279279// completly type safe
280280const testMutation = useAppMutation (" /mutate" , " post" );
281- testMutation .mutate ({name: " test" });
281+ testMutation .mutate ({ name: " test" });
282282```
283283
284284</details >
@@ -294,7 +294,7 @@ testMutation.mutate({name: "test"});
294294` RoutesWithMethod ` is used to extract all the routes with a specific method from the routes object.
295295
296296``` ts
297- import {GetRoutesWithMethod , GetRouterMethods } from " express-typed" ;
297+ import { GetRoutesWithMethod , GetRouterMethods } from " express-typed" ;
298298// // RoutesWithMethod
299299export type RoutesWithMethod <Method extends GetRouterMethods <AppRoutes >> = GetRoutesWithMethod <AppRoutes , Method >;
300300```
@@ -314,32 +314,32 @@ type PostRoutes = RoutesWithMethod<"post">;
314314then in your frontend codebase, you can define the following react-query hooks:
315315
316316``` ts
317- import {useQuery } from " @tanstack/react-query" ;
317+ import { useQuery } from " @tanstack/react-query" ;
318318import axios from " axios" ;
319- import type {RoutesWithMethod } from " express-typed-demo/src/routes/index.routes" ;
319+ import type { RoutesWithMethod } from " express-typed-demo/src/routes/index.routes" ;
320320
321321// an hook to fetch response from server, for GET method
322322type GetRoutes = RoutesWithMethod <" get" >;
323323export const useAppGetQuery = <P extends keyof GetRoutes >(path : P ) => {
324- return useQuery <GetRoutes [P ]>({
325- queryKey: [path ],
326- queryFn : async () => {
327- const res = await axios .get (` /api${path } ` );
328- return res .data as GetRoutes [P ];
329- },
330- });
324+ return useQuery <GetRoutes [P ]>({
325+ queryKey: [path ],
326+ queryFn : async () => {
327+ const res = await axios .get (` /api${path } ` );
328+ return res .data as GetRoutes [P ];
329+ },
330+ });
331331};
332332
333333// an hook to fetch response from server, for POST method
334334type PostRoutes = RoutesWithMethod <" post" >;
335335export const useAppPostQuery = <P extends keyof PostRoutes >(path : P ) => {
336- return useQuery <PostRoutes [P ]>({
337- queryKey: [path ],
338- queryFn : async () => {
339- const res = await axios .post (` /api${path } ` );
340- return res .data as PostRoutes [P ];
341- },
342- });
336+ return useQuery <PostRoutes [P ]>({
337+ queryKey: [path ],
338+ queryFn : async () => {
339+ const res = await axios .post (` /api${path } ` );
340+ return res .data as PostRoutes [P ];
341+ },
342+ });
343343};
344344```
345345
@@ -357,19 +357,19 @@ see full example [here](/examples/fullstack_react_express-typed/express-typed-de
357357
358358``` typescript
359359import {
360- GetRouteRequest ,
361- GetRouteRequestHelper ,
362- GetRouteResponseInfo ,
363- GetRouteResponseInfoHelper ,
364- GetRouterMethods ,
365- GetRoutesWithMethod ,
366- ParseRoutes ,
367- TypedRequest ,
368- TypedRouter ,
360+ GetRouteRequest ,
361+ GetRouteRequestHelper ,
362+ GetRouteResponseInfo ,
363+ GetRouteResponseInfoHelper ,
364+ GetRouterMethods ,
365+ GetRoutesWithMethod ,
366+ ParseRoutes ,
367+ TypedRequest ,
368+ TypedRouter ,
369369} from " express-typed" ;
370370
371371const typedRouter = new TypedRouter ({
372- // your routes here
372+ // your routes here
373373});
374374
375375export default typedRouter ;
@@ -381,23 +381,88 @@ export default typedRouter;
381381export type AppRoutes = ParseRoutes <typeof typedRouter >;
382382
383383export type RouteResResolver <
384- // example usage
385- Path extends keyof AppRoutes ,
386- Method extends keyof AppRoutes [Path ],
387- Info extends keyof GetRouteResponseInfoHelper <AppRoutes , Path , Method > | " body" = " body"
384+ // example usage
385+ Path extends keyof AppRoutes ,
386+ Method extends keyof AppRoutes [Path ],
387+ Info extends keyof GetRouteResponseInfoHelper <AppRoutes , Path , Method > | " body" = " body"
388388> = GetRouteResponseInfo <AppRoutes , Path , Method , Info >;
389389
390390export type RouteReqResolver <
391- Path extends keyof AppRoutes ,
392- Method extends keyof AppRoutes [Path ],
393- Info extends keyof GetRouteRequestHelper <AppRoutes , Path , Method > = Extract <keyof GetRouteRequestHelper <AppRoutes , Path , Method >, " body" >
391+ Path extends keyof AppRoutes ,
392+ Method extends keyof AppRoutes [Path ],
393+ Info extends keyof GetRouteRequestHelper <AppRoutes , Path , Method > = Extract <keyof GetRouteRequestHelper <AppRoutes , Path , Method >, " body" >
394394> = GetRouteRequest <AppRoutes , Path , Method , Info >;
395395
396396export type RoutesWithMethod <Method extends GetRouterMethods <AppRoutes >> = GetRoutesWithMethod <AppRoutes , Method >;
397397```
398398
399399</details >
400400
401+ ## Quick walkthrough
402+
403+ This demo highlights the key usage and features of express-typed, including type inference, explicit typing, and nested routes. follow the comments line by line to understand the usage better.
404+
405+ ``` ts
406+ import { TypedRequest , TypedResponse , TypedRouter , ParseRoutes } from " express-typed" ;
407+
408+ const typedRouter = new TypedRouter ({
409+ // returned type is inferred
410+ " /" : {
411+ get : (req , res ) => {
412+ return res .send (" get: /" ).status (200 );
413+ },
414+ post : (req , res ) => {
415+ return res .send (" post: /" ).status (200 );
416+ },
417+ },
418+ // request body is explicitly typed, response is inferred based on the return value
419+ " /explicit-req" : {
420+ get : (req : TypedRequest <{ body: { name: string } }>, res ) => {
421+ const body = req .body ;
422+ // ^?
423+ return res .json (req .body ).status (200 );
424+ },
425+ },
426+ // response body is explicitly typed, retrun type must at least extend { name: string }
427+ " /explicit-res" : {
428+ get : (req , res : TypedResponse <{ body: { name: string } }>) => {
429+ return res .json ({ name: " eliav" }).status (200 );
430+ },
431+ },
432+ // nested router are allowed, and fully typed
433+ " /nested" : new TypedRouter ({
434+ " /" : {
435+ get : (req , res ) => {
436+ const test = res .send (" get /nested/" ).status (200 );
437+ return test ;
438+ },
439+ // async methods are supported
440+ post : async (req , res ) => {
441+ const test = (await (await fetch (" https://jsonplaceholder.typicode.com/todos/1" )).json ()) as {
442+ userId: number ;
443+ id: number ;
444+ title: string ;
445+ completed: boolean ;
446+ };
447+ return res .json (test ).status (200 );
448+ },
449+ },
450+ // any of "all" | "get" | "post" | "put" | "delete" | "patch" | "options" | "head" is allowed as a method
451+ " /all" : {
452+ all : (req , res ) => {
453+ return res .send (" responding to all methods" );
454+ },
455+ },
456+ }),
457+ });
458+
459+ export default typedRouter ;
460+
461+ export type AppRoutes = ParseRoutes <typeof typedRouter >;
462+ ```
463+
464+ and pretty much, that's it! you can now use the types defined in ` AppRoutes ` to ensure type safety in your frontend codebase.
465+
401466## Contributing
402467
403468This library is still in its early stages, and that's exactly the time to suggest significant changes.
@@ -416,5 +481,6 @@ like Fastify, Koa, etc.
416481- [x] nested routers support
417482- [x] backend return type inference(the type that the backend returns)
418483- [x] backend request type inference(the type that the backend expects in the request)
484+ - [x] explicitly typed request/response
419485- [ ] type-safe path parameters
420486- [ ] type-safe query parameters
0 commit comments