@minisylar/express-typed-router 1.0.0 → 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,164 +1,702 @@
1
- # @minisylar/express-typed-router
2
-
3
- A strongly-typed Express router with Zod validation and automatic type inference for params, body, query, and middleware.
4
-
5
- ## Features
6
-
7
- - 🚀 **Full TypeScript support** with automatic type inference for route parameters
8
- - 🛡️ **Zod validation** for request body, query parameters, and route params
9
- - 🔗 **Express.js compatibility** - works with Express 4 and Express 5
10
- - 📝 **JSDoc documentation** with comprehensive examples
11
- - 📦 **ES Modules** and CommonJS support
12
- - 🎯 **Zero runtime overhead** for type checking
13
-
14
- ## Installation
15
-
16
- ```bash
17
- npm install @minisylar/express-typed-router
18
- # or
19
- pnpm add @minisylar/express-typed-router
20
- # or
21
- yarn add @minisylar/express-typed-router
22
- ```
23
-
24
- ## Quick Start
25
-
26
- ```typescript
27
- import express from "express";
28
- import { z } from "zod";
29
- import { createTypedRouter } from "@minisylar/express-typed-router";
30
-
31
- const app = express();
32
- app.use(express.json());
33
-
34
- // Create a typed router
35
- const router = createTypedRouter();
36
-
37
- // Define routes with automatic type inference
38
- router.get(
39
- "/users/:userId",
40
- {
41
- params: z.object({
42
- userId: z.string(),
43
- }),
44
- query: z.object({
45
- include: z.string().optional(),
46
- }),
47
- },
48
- (req, res) => {
49
- // req.params.userId is automatically typed as string
50
- // req.query.include is automatically typed as string | undefined
51
- res.json({
52
- userId: req.params.userId,
53
- include: req.query.include,
54
- });
55
- }
56
- );
57
-
58
- router.post(
59
- "/users",
60
- {
61
- body: z.object({
62
- name: z.string(),
63
- email: z.string().email(),
64
- }),
65
- },
66
- (req, res) => {
67
- // req.body is automatically typed as { name: string; email: string }
68
- const { name, email } = req.body;
69
- res.json({ id: "123", name, email });
70
- }
71
- );
72
-
73
- app.use("/api", router.getRouter());
74
- app.listen(3000);
75
- ```
76
-
77
- ## Advanced Usage
78
-
79
- ### Custom Error Handling
80
-
81
- ```typescript
82
- import { createTypedRouterWithConfig } from "@minisylar/express-typed-router";
83
-
84
- const router = createTypedRouterWithConfig({
85
- errorHandler: (error, req, res, next) => {
86
- if (error.name === "ZodError") {
87
- res.status(400).json({
88
- error: "Validation failed",
89
- details: error.errors,
90
- });
91
- } else {
92
- next(error);
93
- }
94
- },
95
- });
96
- ```
97
-
98
- ### With Middleware
99
-
100
- ```typescript
101
- import { createTypedRouterWithMiddleware } from "@minisylar/express-typed-router";
102
-
103
- const authMiddleware = (req, res, next) => {
104
- // Your auth logic here
105
- next();
106
- };
107
-
108
- const router = createTypedRouterWithMiddleware([authMiddleware]);
109
- ```
110
-
111
- ## Route Parameter Support
112
-
113
- This library supports all Express.js routing patterns with automatic TypeScript inference:
114
-
115
- - **Named parameters**: `/users/:userId` `{ userId: string }`
116
- - **Multiple parameters**: `/users/:userId/books/:bookId` `{ userId: string; bookId: string }`
117
- - **Consecutive parameters**: `/flights/:from-:to` `{ from: string; to: string }`
118
- - **Optional parameters (Express 4)**: `/posts/:id?` → `{ id?: string }`
119
- - **Repeating parameters (Express 5)**: `/files/:path+` → `{ path: string[] }`
120
- - **Wildcard parameters (Express 5)**: `/files/:path*` → `{ path: string[] }`
121
- - **Optional segments (Express 5)**: `{/:optional}` → `{ optional?: string }`
122
- - **Regex constraints**: `/users/:id(\\d+)` → `{ id: string }`
123
-
124
- ## API Reference
125
-
126
- ### `createTypedRouter()`
127
-
128
- Creates a basic typed router instance.
129
-
130
- ### `createTypedRouterWithConfig(config)`
131
-
132
- Creates a typed router with custom configuration.
133
-
134
- ### `createTypedRouterWithMiddleware(middleware)`
135
-
136
- Creates a typed router with pre-applied middleware.
137
-
138
- ### `TypedMiddleware<T>`
139
-
140
- Type for middleware functions with typed request parameters.
141
-
142
- ## Development
143
-
144
- ```bash
145
- # Install dependencies
146
- pnpm install
147
-
148
- # Build the library
149
- pnpm build
150
-
151
- # Run type checking
152
- pnpm type-check
153
-
154
- # Build in watch mode
155
- pnpm build:watch
156
- ```
157
-
158
- ## License
159
-
160
- ISC
161
-
162
- ## Contributing
163
-
164
- Contributions are welcome! Please feel free to submit a Pull Request.
1
+ # @minisylar/express-typed-router
2
+
3
+ A strongly-typed Express router with Zod validation and automatic type inference for params, body, query, and middleware.
4
+
5
+ ## Features
6
+
7
+ - 🚀 **Full TypeScript support** with automatic type inference for route parameters
8
+ - 🛡️ **Zod validation** for request body, query parameters, and route params
9
+ - 🔗 **Express.js compatibility** - works with Express 4 and Express 5
10
+ - 🤝 **Mix with existing Express routes** - seamlessly integrates with your current codebase
11
+ - 📝 **JSDoc documentation** with comprehensive examples
12
+ - 📦 **ES Modules** and CommonJS support
13
+ - 🎯 **Zero runtime overhead** for type checking
14
+
15
+ ## Installation
16
+
17
+ ```bash
18
+ npm install @minisylar/express-typed-router
19
+ # or
20
+ pnpm add @minisylar/express-typed-router
21
+ # or
22
+ yarn add @minisylar/express-typed-router
23
+ ```
24
+
25
+ > **Note:** This package requires Express 4.18.0+ or Express 5.0.0+
26
+
27
+ ## Quick Start
28
+
29
+ ```javascript
30
+ import express from "express";
31
+ import { z } from "zod";
32
+ import { createTypedRouter } from "@minisylar/express-typed-router";
33
+
34
+ const app = express();
35
+ app.use(express.json());
36
+
37
+ // Create a typed router
38
+ const router = createTypedRouter();
39
+
40
+ // Define routes - parameters are automatically typed!
41
+ router.get("/users/:userId", (req, res) => {
42
+ // req.params.userId is automatically inferred as string
43
+ res.json({ userId: req.params.userId });
44
+ });
45
+
46
+ // Add validation with Zod schemas
47
+ router.post(
48
+ "/users",
49
+ {
50
+ bodySchema: z.object({
51
+ name: z.string(),
52
+ email: z.string().email(),
53
+ }),
54
+ },
55
+ (req, res) => {
56
+ // req.body is validated and typed automatically
57
+ const { name, email } = req.body;
58
+ res.json({ id: "123", name, email });
59
+ }
60
+ );
61
+
62
+ const expressRouter = router.getRouter();
63
+ app.use("/api", expressRouter);
64
+ app.listen(3000);
65
+ ```
66
+
67
+ That's it! Your routes now have full type safety and validation.
68
+
69
+ ## Works with Existing Express Routes
70
+
71
+ The typed router seamlessly integrates with your existing Express application - no need to rewrite everything!
72
+
73
+ ```javascript
74
+ import express from "express";
75
+ import { createTypedRouter } from "@minisylar/express-typed-router";
76
+
77
+ const app = express();
78
+ app.use(express.json());
79
+
80
+ // Your existing Express routes continue to work
81
+ app.get("/health", (req, res) => {
82
+ res.json({ status: "ok" });
83
+ });
84
+
85
+ // Existing Express router
86
+ const legacyRouter = express.Router();
87
+ legacyRouter.get("/legacy/:id", (req, res) => {
88
+ res.json({ id: req.params.id });
89
+ });
90
+
91
+ // New typed router with full type safety
92
+ const typedRouter = createTypedRouter();
93
+ typedRouter.get("/users/:userId", (req, res) => {
94
+ // req.params.userId is automatically typed as string
95
+ res.json({ userId: req.params.userId });
96
+ });
97
+
98
+ // Extract the Express router before using it
99
+ const typedExpressRouter = typedRouter.getRouter();
100
+
101
+ // Mix them all together
102
+ app.use("/api/legacy", legacyRouter);
103
+ app.use("/api/v2", typedExpressRouter);
104
+
105
+ // Gradually migrate your routes to get type safety where you need it!
106
+ app.listen(3000);
107
+ ```
108
+
109
+ ## The Main API: `createTypedRouter()`
110
+
111
+ `createTypedRouter()` is the primary and most flexible way to create typed routers. It supports:
112
+
113
+ - **Global middleware** with automatic type merging
114
+ - ✅ **Per-route middleware** with type inference
115
+ - **Zod validation** for params, body, and query
116
+ - **Express 4 & 5 compatibility** with full route pattern support
117
+ - **Chainable API** for easy configuration
118
+
119
+ ### Global Middleware
120
+
121
+ ```typescript
122
+ const router = createTypedRouter()
123
+ .useTypedMiddleware(authMiddleware)
124
+ .useTypedMiddleware(loggingMiddleware)
125
+ .useTypedMiddleware(timestampMiddleware);
126
+
127
+ // All routes automatically get types from all middleware
128
+ router.get("/protected", (req, res) => {
129
+ // req.userId, req.requestId, req.timestamp all available and typed
130
+ });
131
+ ```
132
+
133
+ ### Per-Route Middleware
134
+
135
+ ```typescript
136
+ router.get(
137
+ "/admin/:userId",
138
+ {
139
+ middleware: [adminMiddleware, auditMiddleware] as const,
140
+ },
141
+ (req, res) => {
142
+ // Types from both global AND per-route middleware are merged
143
+ // req.userId (global), req.isAdmin (adminMiddleware), req.auditId (auditMiddleware)
144
+ }
145
+ );
146
+ ```
147
+
148
+ ### Express 4 & 5 Route Pattern Support
149
+
150
+ Works with **all** Express routing patterns:
151
+
152
+ ```typescript
153
+ // Named parameters
154
+ router.get("/users/:userId", handler); // { userId: string }
155
+
156
+ // Multiple parameters
157
+ router.get("/users/:userId/posts/:postId", handler); // { userId: string; postId: string }
158
+
159
+ // Consecutive parameters with separators
160
+ router.get("/flights/:from-:to", handler); // { from: string; to: string }
161
+ router.get("/files/:name.:ext", handler); // { name: string; ext: string }
162
+
163
+ // Optional parameters (Express 4)
164
+ router.get("/posts/:year/:month?", handler); // { year: string; month?: string }
165
+
166
+ // Repeating parameters (Express 5)
167
+ router.get("/files/:path+", handler); // { path: string[] }
168
+
169
+ // Optional repeating (Express 5)
170
+ router.get("/search/:terms*", handler); // { terms?: string[] }
171
+
172
+ // Optional segments (Express 5)
173
+ router.get("/api{/:version}/users", handler); // { version?: string }
174
+
175
+ // Regex constraints
176
+ router.get("/users/:id(\\d+)", handler); // { id: string }
177
+
178
+ // Wildcards
179
+ router.get("/static/*", handler); // { "0": string }
180
+ ```
181
+
182
+ ## Comprehensive Example
183
+
184
+ ```javascript
185
+ import express from "express";
186
+ import { z } from "zod";
187
+ import { createTypedRouter } from "@minisylar/express-typed-router";
188
+
189
+ const app = express();
190
+ app.use(express.json());
191
+
192
+ // Create router and define middleware inline (automatically typed!)
193
+ const router = createTypedRouter()
194
+ .useTypedMiddleware((req, res, next) => {
195
+ const token = req.headers.authorization;
196
+ req.userId = "user123";
197
+ req.isAdmin = token?.includes("admin") || false;
198
+ next();
199
+ })
200
+ .useTypedMiddleware((req, res, next) => {
201
+ req.requestId = Math.random().toString(36);
202
+ console.log(`[${req.requestId}] ${req.method} ${req.path}`);
203
+ next();
204
+ });
205
+
206
+ // Define schemas
207
+ const CreateUserSchema = z.object({
208
+ name: z.string().min(1),
209
+ email: z.string().email(),
210
+ role: z.enum(["user", "admin"]).optional(),
211
+ });
212
+
213
+ const UserQuerySchema = z.object({
214
+ include: z.array(z.string()).optional(),
215
+ limit: z.coerce.number().int().positive().max(100).default(10),
216
+ });
217
+
218
+ // Routes with full type safety
219
+ router.get(
220
+ "/users/:userId",
221
+ {
222
+ querySchema: UserQuerySchema,
223
+ },
224
+ (req, res) => {
225
+ // All properties are automatically typed:
226
+ const { userId } = req.params; // string (auto-inferred from route)
227
+ const { include, limit } = req.query; // from schema validation
228
+ const { userId: authUserId, isAdmin, requestId } = req; // from middleware
229
+
230
+ res.json({
231
+ id: userId,
232
+ authUserId,
233
+ isAdmin,
234
+ requestId,
235
+ include,
236
+ limit,
237
+ });
238
+ }
239
+ );
240
+
241
+ router.post(
242
+ "/users",
243
+ {
244
+ bodySchema: CreateUserSchema,
245
+ },
246
+ (req, res) => {
247
+ const { name, email, role } = req.body; // Fully typed from schema
248
+ const { userId, isAdmin, requestId } = req; // From middleware
249
+
250
+ if (role === "admin" && !isAdmin) {
251
+ return res.status(403).json({ error: "Insufficient permissions" });
252
+ }
253
+
254
+ res.status(201).json({
255
+ id: "new-user-id",
256
+ name,
257
+ email,
258
+ role: role || "user",
259
+ createdBy: userId,
260
+ requestId,
261
+ });
262
+ }
263
+ );
264
+
265
+ // Per-route middleware can also be inline
266
+ router.delete(
267
+ "/users/:userId",
268
+ {
269
+ middleware: [(req, res, next) => {
270
+ if (!req.isAdmin) {
271
+ return res.status(403).json({ error: "Admin required" });
272
+ }
273
+ req.hasAdminAccess = true;
274
+ next();
275
+ }] as const,
276
+ },
277
+ (req, res) => {
278
+ // Types from BOTH global middleware AND per-route middleware
279
+ const { userId } = req.params; // From route
280
+ const { userId: authUserId, requestId } = req; // From global middleware
281
+ const { hasAdminAccess } = req; // From per-route middleware
282
+
283
+ res.json({
284
+ deleted: userId,
285
+ deletedBy: authUserId,
286
+ requestId,
287
+ hasAdminAccess,
288
+ });
289
+ }
290
+ );
291
+
292
+ app.use("/api", router.getRouter());
293
+ app.listen(3000, () => {
294
+ console.log("Server running on http://localhost:3000");
295
+ });
296
+ ```
297
+
298
+ ### With Explicit TypeScript Types
299
+
300
+ For TypeScript users who prefer explicit typing, you can define middleware with generics:
301
+
302
+ ```typescript
303
+ import { TypedMiddleware } from "@minisylar/express-typed-router";
304
+
305
+ // Define typed middleware explicitly
306
+ const authMiddleware: TypedMiddleware<{ userId: string; isAdmin: boolean }> = (
307
+ req,
308
+ res,
309
+ next
310
+ ) => {
311
+ const token = req.headers.authorization;
312
+ req.userId = "user123";
313
+ req.isAdmin = token?.includes("admin") || false;
314
+ next();
315
+ };
316
+
317
+ const loggingMiddleware: TypedMiddleware<{ requestId: string }> = (
318
+ req,
319
+ res,
320
+ next
321
+ ) => {
322
+ req.requestId = Math.random().toString(36);
323
+ console.log(`[${req.requestId}] ${req.method} ${req.path}`);
324
+ next();
325
+ };
326
+
327
+ // Use the explicitly typed middleware
328
+ const router = createTypedRouter()
329
+ .useTypedMiddleware(authMiddleware)
330
+ .useTypedMiddleware(loggingMiddleware);
331
+
332
+ // Rest of the routes work the same way...
333
+ ```
334
+
335
+ ## TypeScript Features
336
+
337
+ For TypeScript users, the library provides advanced type safety features:
338
+
339
+ ### Typed Middleware
340
+
341
+ Define middleware that extends the request object with typed properties:
342
+
343
+ ```typescript
344
+ import { TypedMiddleware } from "@minisylar/express-typed-router";
345
+
346
+ const authMiddleware: TypedMiddleware<{ userId: string; isAdmin: boolean }> = (
347
+ req,
348
+ res,
349
+ next
350
+ ) => {
351
+ req.userId = "user123";
352
+ req.isAdmin = true;
353
+ next();
354
+ };
355
+
356
+ // Add to router - types are automatically merged
357
+ router.useTypedMiddleware(authMiddleware);
358
+
359
+ router.get("/protected", (req, res) => {
360
+ // TypeScript knows about req.userId and req.isAdmin
361
+ const { userId, isAdmin } = req;
362
+ res.json({ userId, isAdmin });
363
+ });
364
+ ```
365
+
366
+ ### Per-Route Middleware with Type Merging
367
+
368
+ ```typescript
369
+ const adminMiddleware: TypedMiddleware<{ hasAdminAccess: true }> = (
370
+ req,
371
+ res,
372
+ next
373
+ ) => {
374
+ if (!req.isAdmin) {
375
+ return res.status(403).json({ error: "Admin required" });
376
+ }
377
+ req.hasAdminAccess = true;
378
+ next();
379
+ };
380
+
381
+ router.get(
382
+ "/admin/:userId",
383
+ {
384
+ middleware: [adminMiddleware] as const,
385
+ },
386
+ (req, res) => {
387
+ // Types from BOTH global and per-route middleware are available
388
+ const { userId } = req.params; // From route params
389
+ const { userId: authUserId } = req; // From global middleware
390
+ const { hasAdminAccess } = req; // From per-route middleware
391
+
392
+ res.json({ userId, authUserId, hasAdminAccess });
393
+ }
394
+ );
395
+ ```
396
+
397
+ ### Advanced Route Parameter Types
398
+
399
+ The library automatically infers complex Express route patterns:
400
+
401
+ ```typescript
402
+ // Express 5 repeating parameters
403
+ router.get("/files/:path+", (req, res) => {
404
+ const { path } = req.params; // string[] - automatically inferred!
405
+ });
406
+
407
+ // Optional parameters
408
+ router.get("/posts/:year/:month?", (req, res) => {
409
+ const { year, month } = req.params; // { year: string; month?: string }
410
+ });
411
+
412
+ // Complex patterns with separators
413
+ router.get("/flights/:from-:to", (req, res) => {
414
+ const { from, to } = req.params; // { from: string; to: string }
415
+ });
416
+ ```
417
+
418
+ ## Alternative API Styles
419
+
420
+ <details>
421
+ <summary><strong>🎯 createTypedRouterWithMiddleware(...middleware)</strong> - Pre-configured with middleware</summary>
422
+
423
+ For developers who prefer setting up all middleware upfront:
424
+
425
+ ```typescript
426
+ import {
427
+ createTypedRouterWithMiddleware,
428
+ TypedMiddleware,
429
+ } from "@minisylar/express-typed-router";
430
+
431
+ const authMiddleware: TypedMiddleware<{ userId: string; isAdmin: boolean }> = (
432
+ req,
433
+ res,
434
+ next
435
+ ) => {
436
+ req.userId = "user123";
437
+ req.isAdmin = true;
438
+ next();
439
+ };
440
+
441
+ const timestampMiddleware: TypedMiddleware<{ timestamp: Date }> = (
442
+ req,
443
+ res,
444
+ next
445
+ ) => {
446
+ req.timestamp = new Date();
447
+ next();
448
+ };
449
+
450
+ // Create router with middleware - types are automatically merged
451
+ const router = createTypedRouterWithMiddleware(
452
+ authMiddleware,
453
+ timestampMiddleware
454
+ );
455
+
456
+ router.get("/protected", (req, res) => {
457
+ // req.userId, req.isAdmin, and req.timestamp are all typed correctly!
458
+ res.json({
459
+ userId: req.userId, // string
460
+ isAdmin: req.isAdmin, // boolean
461
+ timestamp: req.timestamp, // Date
462
+ });
463
+ });
464
+ ```
465
+
466
+ </details>
467
+
468
+ <details>
469
+ <summary><strong>⚙️ createTypedRouterWithConfig(config)</strong> - Custom configuration</summary>
470
+
471
+ For applications that need custom error handling or configuration:
472
+
473
+ ```typescript
474
+ import { createTypedRouterWithConfig } from "@minisylar/express-typed-router";
475
+
476
+ const router = createTypedRouterWithConfig({
477
+ errorHandler: (error, req, res, next) => {
478
+ if (error.name === "ZodError") {
479
+ res.status(400).json({
480
+ error: "Validation failed",
481
+ details: error.errors,
482
+ });
483
+ } else {
484
+ next(error);
485
+ }
486
+ },
487
+ });
488
+
489
+ // Use normally
490
+ router.get("/users/:id", (req, res) => {
491
+ // Custom error handling is automatically applied
492
+ const { id } = req.params;
493
+ res.json({ id });
494
+ });
495
+ ```
496
+
497
+ </details>
498
+
499
+ ## Express 4 & 5 Route Pattern Support
500
+
501
+ This library provides **complete TypeScript inference** for all Express.js routing patterns across both Express 4 and 5:
502
+
503
+ ### Basic Patterns (Express 4 & 5)
504
+
505
+ ```typescript
506
+ // Named parameters
507
+ router.get("/users/:userId", handler);
508
+ // → { userId: string }
509
+
510
+ // Multiple parameters
511
+ router.get("/users/:userId/posts/:postId", handler);
512
+ // → { userId: string; postId: string }
513
+
514
+ // Parameters with separators
515
+ router.get("/flights/:from-:to", handler);
516
+ // → { from: string; to: string }
517
+
518
+ router.get("/files/:name.:ext", handler);
519
+ // → { name: string; ext: string }
520
+ ```
521
+
522
+ ### Advanced Patterns (Express 4)
523
+
524
+ ```typescript
525
+ // Optional parameters
526
+ router.get("/posts/:year/:month?", handler);
527
+ // → { year: string; month?: string }
528
+
529
+ // Regex constraints
530
+ router.get("/users/:id(\\d+)", handler);
531
+ // → { id: string }
532
+
533
+ // Wildcards
534
+ router.get("/files/*", handler);
535
+ // → { "0": string }
536
+
537
+ router.get("/api/*/files/*", handler);
538
+ // → { "0": string; "1": string }
539
+ ```
540
+
541
+ ### Express 5 Enhanced Patterns
542
+
543
+ ```typescript
544
+ // Repeating parameters (one or more)
545
+ router.get("/files/:path+", handler);
546
+ // → { path: string[] }
547
+
548
+ // Optional repeating (zero or more)
549
+ router.get("/search/:terms*", handler);
550
+ // → { terms?: string[] }
551
+
552
+ // Optional segments with braces
553
+ router.get("/api{/:version}/users", handler);
554
+ // → { version?: string }
555
+
556
+ router.get("/files{/:category}/:filename", handler);
557
+ // → { category?: string; filename: string }
558
+ ```
559
+
560
+ ### Real-World Examples
561
+
562
+ ```typescript
563
+ // E-commerce routes
564
+ router.get("/products/:category/:subcategory?", handler);
565
+ // → { category: string; subcategory?: string }
566
+
567
+ // File serving with optional versioning
568
+ router.get("/assets{/:version}/:filename.:ext", handler);
569
+ // → { version?: string; filename: string; ext: string }
570
+
571
+ // API versioning with wildcards
572
+ router.get("/api/v:version/*", handler);
573
+ // → { version: string; "0": string }
574
+
575
+ // Multi-segment paths (Express 5)
576
+ router.get("/docs/:sections+", handler);
577
+ // → { sections: string[] }
578
+ ```
579
+
580
+ All patterns work seamlessly with Zod validation and middleware type inference!
581
+
582
+ ## API Reference
583
+
584
+ ### `createTypedRouter()` - Main API
585
+
586
+ Creates a typed router instance with full flexibility for middleware and validation.
587
+
588
+ ```typescript
589
+ const router = createTypedRouter();
590
+
591
+ // Add global middleware (chainable)
592
+ router.useTypedMiddleware(middleware1)
593
+ .useTypedMiddleware(middleware2);
594
+
595
+ // All HTTP methods supported
596
+ router.get(path, options?, handler)
597
+ router.post(path, options?, handler)
598
+ router.put(path, options?, handler)
599
+ router.patch(path, options?, handler)
600
+ router.delete(path, options?, handler)
601
+ router.options(path, options?, handler)
602
+ router.head(path, options?, handler)
603
+ router.all(path, options?, handler)
604
+ ```
605
+
606
+ **Route Options:**
607
+
608
+ - `bodySchema`: Zod schema for request body validation
609
+ - `querySchema`: Zod schema for query parameter validation
610
+ - `paramsSchema`: Zod schema for route parameter validation (optional - auto-inferred from path)
611
+ - `middleware`: Array of typed middleware functions for this specific route
612
+
613
+ **Examples:**
614
+
615
+ ```typescript
616
+ // Simple route with auto-inferred params
617
+ router.get("/users/:id", (req, res) => {
618
+ const { id } = req.params; // string
619
+ });
620
+
621
+ // With body validation
622
+ router.post(
623
+ "/users",
624
+ {
625
+ bodySchema: z.object({ name: z.string() }),
626
+ },
627
+ (req, res) => {
628
+ const { name } = req.body; // string
629
+ }
630
+ );
631
+
632
+ // With per-route middleware
633
+ router.get(
634
+ "/admin",
635
+ {
636
+ middleware: [authMiddleware, adminMiddleware] as const,
637
+ },
638
+ (req, res) => {
639
+ // Types from both middleware are available
640
+ }
641
+ );
642
+ ```
643
+
644
+ ### `TypedMiddleware<T>`
645
+
646
+ Type for middleware functions that extend the request object.
647
+
648
+ ```typescript
649
+ const authMiddleware: TypedMiddleware<{ userId: string }> = (
650
+ req,
651
+ res,
652
+ next
653
+ ) => {
654
+ req.userId = "123";
655
+ next();
656
+ };
657
+ ```
658
+
659
+ <details>
660
+ <summary><strong>Alternative APIs</strong></summary>
661
+
662
+ ### `createTypedRouterWithConfig(config)`
663
+
664
+ ```typescript
665
+ const router = createTypedRouterWithConfig({
666
+ errorHandler: (error, req, res, next) => {
667
+ // Custom error handling
668
+ },
669
+ });
670
+ ```
671
+
672
+ ### `createTypedRouterWithMiddleware(...middleware)`
673
+
674
+ ```typescript
675
+ const router = createTypedRouterWithMiddleware(middleware1, middleware2);
676
+ ```
677
+
678
+ </details>
679
+
680
+ ## Development
681
+
682
+ ```bash
683
+ # Install dependencies
684
+ pnpm install
685
+
686
+ # Build the library
687
+ pnpm build
688
+
689
+ # Run type checking
690
+ pnpm type-check
691
+
692
+ # Build in watch mode
693
+ pnpm build:watch
694
+ ```
695
+
696
+ ## License
697
+
698
+ ISC
699
+
700
+ ## Contributing
701
+
702
+ Contributions are welcome! Please feel free to submit a Pull Request.