@minisylar/express-typed-router 1.1.0 → 1.3.0

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
@@ -118,16 +118,28 @@ app.listen(3000);
118
118
 
119
119
  ### Global Middleware
120
120
 
121
+ **Important**: Unlike Express, middleware must be applied using method chaining or capturing returned routers. See the [FAQ section](#faq-and-common-patterns) for details.
122
+
121
123
  ```typescript
124
+ // Method chaining pattern (recommended)
122
125
  const router = createTypedRouter()
123
- .useTypedMiddleware(authMiddleware)
124
- .useTypedMiddleware(loggingMiddleware)
125
- .useTypedMiddleware(timestampMiddleware);
126
+ .useMiddleware(authMiddleware)
127
+ .useMiddleware(loggingMiddleware)
128
+ .useMiddleware(timestampMiddleware);
126
129
 
127
130
  // All routes automatically get types from all middleware
128
131
  router.get("/protected", (req, res) => {
129
132
  // req.userId, req.requestId, req.timestamp all available and typed
130
133
  });
134
+
135
+ // Alternative: capturing returned router
136
+ const baseRouter = createTypedRouter();
137
+ const routerWithMiddleware = baseRouter
138
+ .useMiddleware(authMiddleware)
139
+ .useMiddleware(loggingMiddleware);
140
+
141
+ // Use the router with middleware applied
142
+ routerWithMiddleware.get("/users", handler);
131
143
  ```
132
144
 
133
145
  ### Per-Route Middleware
@@ -136,7 +148,7 @@ router.get("/protected", (req, res) => {
136
148
  router.get(
137
149
  "/admin/:userId",
138
150
  {
139
- middleware: [adminMiddleware, auditMiddleware] as const,
151
+ middleware: [adminMiddleware, auditMiddleware],
140
152
  },
141
153
  (req, res) => {
142
154
  // Types from both global AND per-route middleware are merged
@@ -191,13 +203,13 @@ app.use(express.json());
191
203
 
192
204
  // Create router and define middleware inline (automatically typed!)
193
205
  const router = createTypedRouter()
194
- .useTypedMiddleware((req, res, next) => {
206
+ .useMiddleware((req, res, next) => {
195
207
  const token = req.headers.authorization;
196
208
  req.userId = "user123";
197
209
  req.isAdmin = token?.includes("admin") || false;
198
210
  next();
199
211
  })
200
- .useTypedMiddleware((req, res, next) => {
212
+ .useMiddleware((req, res, next) => {
201
213
  req.requestId = Math.random().toString(36);
202
214
  console.log(`[${req.requestId}] ${req.method} ${req.path}`);
203
215
  next();
@@ -223,7 +235,7 @@ router.get(
223
235
  },
224
236
  (req, res) => {
225
237
  // All properties are automatically typed:
226
- const { userId } = req.params; // string (auto-inferred from route)
238
+ const { userId } = req.params; // string (auto-inferred from route)
227
239
  const { include, limit } = req.query; // from schema validation
228
240
  const { userId: authUserId, isAdmin, requestId } = req; // from middleware
229
241
 
@@ -266,19 +278,21 @@ router.post(
266
278
  router.delete(
267
279
  "/users/:userId",
268
280
  {
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,
281
+ middleware: [
282
+ (req, res, next) => {
283
+ if (!req.isAdmin) {
284
+ return res.status(403).json({ error: "Admin required" });
285
+ }
286
+ req.hasAdminAccess = true;
287
+ next();
288
+ },
289
+ ], // No need for 'as const' - middleware arrays are automatically typed
276
290
  },
277
291
  (req, res) => {
278
292
  // Types from BOTH global middleware AND per-route middleware
279
- const { userId } = req.params; // From route
293
+ const { userId } = req.params; // From route
280
294
  const { userId: authUserId, requestId } = req; // From global middleware
281
- const { hasAdminAccess } = req; // From per-route middleware
295
+ const { hasAdminAccess } = req; // From per-route middleware
282
296
 
283
297
  res.json({
284
298
  deleted: userId,
@@ -326,8 +340,8 @@ const loggingMiddleware: TypedMiddleware<{ requestId: string }> = (
326
340
 
327
341
  // Use the explicitly typed middleware
328
342
  const router = createTypedRouter()
329
- .useTypedMiddleware(authMiddleware)
330
- .useTypedMiddleware(loggingMiddleware);
343
+ .useMiddleware(authMiddleware)
344
+ .useMiddleware(loggingMiddleware);
331
345
 
332
346
  // Rest of the routes work the same way...
333
347
  ```
@@ -354,7 +368,7 @@ const authMiddleware: TypedMiddleware<{ userId: string; isAdmin: boolean }> = (
354
368
  };
355
369
 
356
370
  // Add to router - types are automatically merged
357
- router.useTypedMiddleware(authMiddleware);
371
+ router.useMiddleware(authMiddleware);
358
372
 
359
373
  router.get("/protected", (req, res) => {
360
374
  // TypeScript knows about req.userId and req.isAdmin
@@ -381,7 +395,7 @@ const adminMiddleware: TypedMiddleware<{ hasAdminAccess: true }> = (
381
395
  router.get(
382
396
  "/admin/:userId",
383
397
  {
384
- middleware: [adminMiddleware] as const,
398
+ middleware: [adminMiddleware], // No need for 'as const' - arrays are automatically typed
385
399
  },
386
400
  (req, res) => {
387
401
  // Types from BOTH global and per-route middleware are available
@@ -589,8 +603,8 @@ Creates a typed router instance with full flexibility for middleware and validat
589
603
  const router = createTypedRouter();
590
604
 
591
605
  // Add global middleware (chainable)
592
- router.useTypedMiddleware(middleware1)
593
- .useTypedMiddleware(middleware2);
606
+ router.useMiddleware(middleware1)
607
+ .useMiddleware(middleware2);
594
608
 
595
609
  // All HTTP methods supported
596
610
  router.get(path, options?, handler)
@@ -697,6 +711,177 @@ pnpm build:watch
697
711
 
698
712
  ISC
699
713
 
714
+ ## FAQ and Common Patterns
715
+
716
+ ### Middleware Behavior Differences from Express
717
+
718
+ #### IMPORTANT: Router Middleware and Route Registration
719
+
720
+ When using middleware with `express-typed-router`, there's an important difference from standard Express behavior:
721
+
722
+ **In Express**, middleware added with `router.use()` applies to all routes registered _after_ it:
723
+
724
+ ```javascript
725
+ // Express middleware behavior
726
+ const router = express.Router();
727
+ router.use(authMiddleware); // Apply middleware
728
+ router.get("/route1", handler1); // Has authMiddleware
729
+ router.use(logMiddleware); // Apply another middleware
730
+ router.get("/route2", handler2); // Has BOTH auth and log middleware
731
+ ```
732
+
733
+ **In express-typed-router**, `useMiddleware()` returns a _new router instance_ for type safety:
734
+
735
+ ```typescript
736
+ // ❌ WON'T WORK - middleware not applied to route
737
+ const router = createTypedRouter();
738
+ router.useMiddleware(authMiddleware); // Returns new router that isn't captured
739
+ router.get("/route", handler); // Original router without middleware!
740
+
741
+ // ✅ CORRECT - chain methods (recommended)
742
+ const router = createTypedRouter()
743
+ .useMiddleware(authMiddleware)
744
+ .get("/route", handler);
745
+
746
+ // ✅ CORRECT - chain directly from middleware call
747
+ const router = createTypedRouter();
748
+ router.useMiddleware(authMiddleware).get("/route", handler);
749
+
750
+ // ✅ ALSO CORRECT - use per-route middleware
751
+ const router = createTypedRouter();
752
+ router.get("/route", { middleware: [authMiddleware] }, handler);
753
+ ```
754
+
755
+ This design is necessary for full type safety but requires a different pattern than standard Express.
756
+
757
+ ### Common Express Patterns vs express-typed-router
758
+
759
+ Here are common Express patterns and how to achieve them with express-typed-router:
760
+
761
+ #### Pattern 1: Adding middleware to specific routes
762
+
763
+ **Express:**
764
+
765
+ ```javascript
766
+ const router = express.Router();
767
+ router.get("/public", publicHandler);
768
+ router.use(authMiddleware); // Only affects routes below
769
+ router.get("/private", privateHandler); // Has authMiddleware
770
+ ```
771
+
772
+ **express-typed-router:**
773
+
774
+ ```typescript
775
+ // Option 1: Separate routers
776
+ const publicRouter = createTypedRouter();
777
+ publicRouter.get("/public", publicHandler);
778
+
779
+ const privateRouter = createTypedRouter().useMiddleware(authMiddleware);
780
+ privateRouter.get("/private", privateHandler);
781
+
782
+ // Combine in Express
783
+ app.use(publicRouter.getRouter());
784
+ app.use(privateRouter.getRouter());
785
+
786
+ // Option 2: Per-route middleware
787
+ const router = createTypedRouter();
788
+ router.get("/public", publicHandler);
789
+ router.get("/private", { middleware: [authMiddleware] }, privateHandler);
790
+ ```
791
+
792
+ #### Pattern 2: Adding middleware for a group of routes
793
+
794
+ **Express:**
795
+
796
+ ```javascript
797
+ const router = express.Router();
798
+ router.get("/public", handler);
799
+
800
+ // Only admin routes have auth middleware
801
+ const adminRouter = express.Router();
802
+ adminRouter.use(authMiddleware);
803
+ adminRouter.get("/users", adminHandler1);
804
+ adminRouter.get("/settings", adminHandler2);
805
+
806
+ router.use("/admin", adminRouter);
807
+ ```
808
+
809
+ **express-typed-router:**
810
+
811
+ ```typescript
812
+ const publicRouter = createTypedRouter();
813
+ publicRouter.get("/public", handler);
814
+
815
+ // Admin router with middleware
816
+ const adminRouter = createTypedRouter().useMiddleware(authMiddleware);
817
+ adminRouter.get("/users", adminHandler1);
818
+ adminRouter.get("/settings", adminHandler2);
819
+
820
+ // Combine with Express
821
+ app.use(publicRouter.getRouter());
822
+ app.use("/admin", adminRouter.getRouter());
823
+ ```
824
+
825
+ #### Pattern 3: Middleware with dynamically added routes
826
+
827
+ **Express:**
828
+
829
+ ```javascript
830
+ const router = express.Router();
831
+ router.use(middleware);
832
+
833
+ // Later, routes are added dynamically
834
+ function addRoute(path, handler) {
835
+ router.get(path, handler); // Has middleware
836
+ }
837
+ ```
838
+
839
+ **express-typed-router:**
840
+
841
+ ```typescript
842
+ // Option 1: Pass the router to the function
843
+ const router = createTypedRouter().useMiddleware(middleware);
844
+
845
+ function addRoute(router, path, handler) {
846
+ router.get(path, handler);
847
+ }
848
+
849
+ // Option 2: Factory function
850
+ function createRouteAdder(middleware) {
851
+ const router = createTypedRouter().useMiddleware(middleware);
852
+
853
+ return {
854
+ addRoute: (path, handler) => router.get(path, handler),
855
+ getRouter: () => router.getRouter(),
856
+ };
857
+ }
858
+
859
+ const routeAdder = createRouteAdder(middleware);
860
+ routeAdder.addRoute("/path", handler);
861
+ app.use(routeAdder.getRouter());
862
+ ```
863
+
864
+ ### Using express-typed-router in JavaScript
865
+
866
+ JavaScript users don't need to worry about TypeScript types but should still follow the middleware chaining pattern:
867
+
868
+ ```javascript
869
+ // JavaScript usage
870
+ const { createTypedRouter } = require("@minisylar/express-typed-router");
871
+
872
+ const router = createTypedRouter().useMiddleware((req, res, next) => {
873
+ req.user = { id: "user123" };
874
+ next();
875
+ });
876
+
877
+ router.get("/users", (req, res) => {
878
+ // req.user is available but not typed (JavaScript doesn't have types)
879
+ res.json({ userId: req.user.id });
880
+ });
881
+
882
+ module.exports = router.getRouter();
883
+ ```
884
+
700
885
  ## Contributing
701
886
 
702
887
  Contributions are welcome! Please feel free to submit a Pull Request.
@@ -1,180 +1,2 @@
1
- //#region rolldown:runtime
2
- var __create = Object.create;
3
- var __defProp = Object.defineProperty;
4
- var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
- var __getOwnPropNames = Object.getOwnPropertyNames;
6
- var __getProtoOf = Object.getPrototypeOf;
7
- var __hasOwnProp = Object.prototype.hasOwnProperty;
8
- var __copyProps = (to, from, except, desc) => {
9
- if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
10
- key = keys[i];
11
- if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
12
- get: ((k) => from[k]).bind(null, key),
13
- enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
14
- });
15
- }
16
- return to;
17
- };
18
- var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
19
- value: mod,
20
- enumerable: true
21
- }) : target, mod));
22
-
23
- //#endregion
24
- const express = __toESM(require("express"));
25
- const zod = __toESM(require("zod"));
26
-
27
- //#region src/zod-router.ts
28
- var TypedRouter = class {
29
- router;
30
- constructor() {
31
- this.router = express.default.Router();
32
- }
33
- /**
34
- * Add typed middleware that extends the request with additional properties
35
- * and/or adds properties to response.locals
36
- */
37
- useTypedMiddleware(middleware) {
38
- this.router.use(middleware);
39
- return this;
40
- }
41
- /**
42
- * Get the underlying Express router
43
- */
44
- getRouter() {
45
- return this.router;
46
- }
47
- get(path, optionsOrHandler, handler) {
48
- return this.registerRoute("get", path, optionsOrHandler, handler);
49
- }
50
- post(path, optionsOrHandler, handler) {
51
- return this.registerRoute("post", path, optionsOrHandler, handler);
52
- }
53
- put(path, optionsOrHandler, handler) {
54
- return this.registerRoute("put", path, optionsOrHandler, handler);
55
- }
56
- patch(path, optionsOrHandler, handler) {
57
- return this.registerRoute("patch", path, optionsOrHandler, handler);
58
- }
59
- delete(path, optionsOrHandler, handler) {
60
- return this.registerRoute("delete", path, optionsOrHandler, handler);
61
- }
62
- options(path, optionsOrHandler, handler) {
63
- return this.registerRoute("options", path, optionsOrHandler, handler);
64
- }
65
- head(path, optionsOrHandler, handler) {
66
- return this.registerRoute("head", path, optionsOrHandler, handler);
67
- }
68
- all(path, optionsOrHandler, handler) {
69
- return this.registerRoute("all", path, optionsOrHandler, handler);
70
- }
71
- registerRoute(method, path, optionsOrHandler, handler) {
72
- const middlewares = [];
73
- if (typeof optionsOrHandler === "object") {
74
- const options = optionsOrHandler;
75
- if (options.middleware) middlewares.push(...options.middleware);
76
- if (options.bodySchema) middlewares.push(this.createBodyValidationMiddleware(options.bodySchema));
77
- if (options.querySchema) middlewares.push(this.createQueryValidationMiddleware(options.querySchema));
78
- middlewares.push(handler);
79
- } else middlewares.push(optionsOrHandler);
80
- this.router[method](path, ...middlewares);
81
- return this;
82
- }
83
- createBodyValidationMiddleware(schema) {
84
- return (req, res, next) => {
85
- try {
86
- req.body = schema.parse(req.body);
87
- next();
88
- } catch (error) {
89
- if (error instanceof zod.z.ZodError) res.status(400).json({
90
- error: "Validation failed",
91
- details: error.errors
92
- });
93
- else next(error);
94
- }
95
- };
96
- }
97
- createQueryValidationMiddleware(schema) {
98
- return (req, res, next) => {
99
- try {
100
- req.query = schema.parse(req.query);
101
- next();
102
- } catch (error) {
103
- if (error instanceof zod.z.ZodError) res.status(400).json({
104
- error: "Validation failed",
105
- details: error.errors
106
- });
107
- else next(error);
108
- }
109
- };
110
- }
111
- };
112
- /**
113
- * Create a new strongly-typed Express router instance.
114
- *
115
- * This is the simplest way to get started with @minisylar/express-typed-router.
116
- *
117
- * @example
118
- * import { createTypedRouter } from '@minisylar/express-typed-router';
119
- *
120
- * // Create a router and add a typed GET route
121
- * const router = createTypedRouter();
122
- * router.get('/hello/:name', (req, res) => {
123
- * // req.params.name is typed as string
124
- * res.json({ message: `Hello, ${req.params.name}!` });
125
- * });
126
- *
127
- * // Use with Express
128
- * import express from 'express';
129
- * const app = express();
130
- * app.use('/api', router.getRouter());
131
- */
132
- function createTypedRouter() {
133
- return new TypedRouter();
134
- }
135
- /**
136
- * Create a new typed router with optional configuration.
137
- *
138
- * Use this if you want to add a global error handler or future global options.
139
- *
140
- * @param config - Optional configuration for the router (e.g. error handler).
141
- * @returns A new TypedRouter instance.
142
- *
143
- * @example
144
- * import { createTypedRouterWithConfig } from '@minisylar/express-typed-router';
145
- *
146
- * const router = createTypedRouterWithConfig({
147
- * errorHandler: (err, req, res, next) => {
148
- * res.status(500).json({ error: 'Something went wrong', details: err });
149
- * }
150
- * });
151
- */
152
- function createTypedRouterWithConfig(config) {
153
- const router = new TypedRouter();
154
- if (config?.errorHandler) router.getRouter().use(config.errorHandler);
155
- return router;
156
- }
157
- /**
158
- * Create a new typed router with pre-configured middleware.
159
- *
160
- * This is useful for setting up router-level middleware in a single call.
161
- *
162
- * @param middleware - One or more TypedMiddleware functions to apply to all routes.
163
- * @returns A new TypedRouter instance with the middleware applied.
164
- *
165
- * @example
166
- * import { createTypedRouterWithMiddleware } from '@minisylar/express-typed-router';
167
- *
168
- * const router = createTypedRouterWithMiddleware(authMiddleware, loggingMiddleware);
169
- */
170
- function createTypedRouterWithMiddleware(...middleware) {
171
- let router = new TypedRouter();
172
- for (const mw of middleware) router = router.useTypedMiddleware(mw);
173
- return router;
174
- }
175
-
176
- //#endregion
177
- exports.createTypedRouter = createTypedRouter;
178
- exports.createTypedRouterWithConfig = createTypedRouterWithConfig;
179
- exports.createTypedRouterWithMiddleware = createTypedRouterWithMiddleware;
1
+ var e=Object.create,t=Object.defineProperty,n=Object.getOwnPropertyDescriptor,r=Object.getOwnPropertyNames,i=Object.getPrototypeOf,a=Object.prototype.hasOwnProperty,o=(e,i,o,s)=>{if(i&&typeof i==`object`||typeof i==`function`)for(var c=r(i),l=0,u=c.length,d;l<u;l++)d=c[l],!a.call(e,d)&&d!==o&&t(e,d,{get:(e=>i[e]).bind(null,d),enumerable:!(s=n(i,d))||s.enumerable});return e},s=(n,r,a)=>(a=n==null?{}:e(i(n)),o(r||!n||!n.__esModule?t(a,`default`,{value:n,enumerable:!0}):a,n));const c=s(require(`express`)),l=s(require(`zod`));var u=class{router;constructor(){this.router=c.default.Router()}useMiddleware(e){return this.router.use(e),this}getRouter(){return this.router}get(e,t,n){return this.registerRoute(`get`,e,t,n)}post(e,t,n){return this.registerRoute(`post`,e,t,n)}put(e,t,n){return this.registerRoute(`put`,e,t,n)}patch(e,t,n){return this.registerRoute(`patch`,e,t,n)}delete(e,t,n){return this.registerRoute(`delete`,e,t,n)}options(e,t,n){return this.registerRoute(`options`,e,t,n)}head(e,t,n){return this.registerRoute(`head`,e,t,n)}all(e,t,n){return this.registerRoute(`all`,e,t,n)}registerRoute(e,t,n,r){let i=[];if(typeof n==`object`){let e=n;e.middleware&&i.push(...e.middleware),e.bodySchema&&i.push(this.createBodyValidationMiddleware(e.bodySchema)),e.querySchema&&i.push(this.createQueryValidationMiddleware(e.querySchema)),i.push(r)}else i.push(n);return this.router[e](t,...i),this}createBodyValidationMiddleware(e){return(t,n,r)=>{try{t.body=e.parse(t.body),r()}catch(e){e instanceof l.z.ZodError||e&&typeof e==`object`&&`issues`in e?n.status(400).json({error:`Validation failed`,details:e.errors||e.issues}):r(e)}}}createQueryValidationMiddleware(e){return(t,n,r)=>{try{t.query=e.parse(t.query),r()}catch(e){e instanceof l.z.ZodError||e&&typeof e==`object`&&`issues`in e?n.status(400).json({error:`Validation failed`,details:e.errors||e.issues}):r(e)}}}};function d(){return new u}function f(e){let t=new u;return e?.errorHandler&&t.getRouter().use(e.errorHandler),t}function p(...e){let t=new u;for(let n of e)t=t.useMiddleware(n);return t}exports.createTypedRouter=d,exports.createTypedRouterWithConfig=f,exports.createTypedRouterWithMiddleware=p;
180
2
  //# sourceMappingURL=zod-router.cjs.map