@bepalo/router 1.8.27 → 1.10.31

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
@@ -17,13 +17,41 @@ Please refer to the [change-log](CHANGELOG.md).
17
17
 
18
18
  ## Docs
19
19
 
20
- - [Framework](docs/router-framework.md).
20
+ ### -> [Router](docs/router.md)
21
+
22
+ ### -> [Router Framework](docs/router-framework.md) [new]
23
+
24
+ ## ✨ Features
25
+
26
+ - ⚡ **High Performance** - Built on a radix tree for O(k) route matching (where k is path length)
27
+ - 📦 **Optional file-based framework** - extends the `Router` to load routes from files and folders.
28
+ - 🎯 **Flexible Routing Engine** - Support for path parameters, wildcards (`*`, `.*`, `**`, `.**`), and all HTTP methods
29
+ - 🎭 **Multiple Handler Types** - Filters, hooks, afters, handlers, fallbacks, and catchers
30
+ - 🔌 **Middleware Pipeline** - Chain multiple handlers with early exit capability
31
+ - 🛡️ **Error Handling** - Built-in error catching with contextual error handlers
32
+ - 🔄 **Method-Based Routing** - Separate routing trees for each HTTP method
33
+ - 📦 **Local Dependencies** - Minimal Dependencies — Only internal @bepalo packages
34
+ - 🌐 **Runtime Agnostic** - Works with Bun, Deno, Node.js, and other runtimes
35
+ - 🔧 **TypeScript Ready** - Full type definitions included
36
+ - 🧩 **Composable Router Architecture** - Append one router to another with a path prefix.
37
+ - 🛠️ **Provided Helper Utilities** - Built-in response helpers (json, html, parseBody, upload, etc.)
38
+ - 🛠️ **Provided Middleware Utilities** - CORS, rate limiting, authentication helpers
39
+ - 🔐 **Normalized pathname option** - An option to normalize pathnames.
40
+
41
+ ## Design goals
42
+
43
+ - Server independent routing. `Request` -(handlers)-> `Response`.
44
+ - Predictable, deterministic route matching and pipeline flow.
45
+ - Composable: small routers can be appended under prefixes.
46
+ - Low overhead and streaming-friendly to support large uploads and proxies.
47
+ - Explicit, type-safe context passing for handlers (CTX types).
21
48
 
22
49
  ## 📑 Table of Contents
23
50
 
24
51
  1. [🏆 @bepalo/router](#-bepalorouter)
25
52
  2. [✨ Features](#-features)
26
- 3. [🚀 Get Started](#-get-started)
53
+ 3. [ Design goals](#design-goals)
54
+ 4. [🚀 Get Started](#-get-started)
27
55
  - [📥 Installation](#-installation)
28
56
  - [📦 Basic Usage](#-basic-usage)
29
57
  - [Example](#example)
@@ -31,10 +59,10 @@ Please refer to the [change-log](CHANGELOG.md).
31
59
  - [Bun](#bun)
32
60
  - [Deno](#deno)
33
61
  - [Nodejs](#nodejs)
34
- 4. [📚 Core Concepts](#-core-concepts)
62
+ 5. [📚 Core Concepts](#-core-concepts)
35
63
  - [Handler Types & Execution Order](#handler-types--execution-order)
36
64
  - [Router Context](#router-context)
37
- 5. [📖 API Reference](#-api-reference)
65
+ 6. [📖 API Reference](#-api-reference)
38
66
  - [Router Class](#router-class)
39
67
  - [Constructor](#constructor)
40
68
  - [Configuration Options](#configuration-options)
@@ -49,27 +77,11 @@ Please refer to the [change-log](CHANGELOG.md).
49
77
  - [Path Patterns](#path-patterns)
50
78
  - [Route Priority](#route-priority)
51
79
  - [Router Composition Example](#router-composition-example)
52
- 6. [🎯 Performance](#-performance)
80
+ 7. [🎯 Performance](#-performance)
53
81
  - [Comparison with Other Routers](#comparison-with-other-routers)
54
- 7. [📄 License](#-license)
55
- 8. [🕊️ Thanks and Enjoy](#-thanks-and-enjoy)
56
- 9. [💖 Be a Sponsor](#-be-a-sponsor)
57
-
58
- ## ✨ Features
59
-
60
- - ⚡ **High Performance** - Built on a radix tree for O(k) route matching (where k is path length)
61
- - 🎯 **Flexible Routing Engine** - Support for path parameters, wildcards (`*`, `.*`, `**`, `.**`), and all HTTP methods
62
- - 🎭 **Multiple Handler Types** - Filters, hooks, afters, handlers, fallbacks, and catchers
63
- - 🔌 **Middleware Pipeline** - Chain multiple handlers with early exit capability
64
- - 🛡️ **Error Handling** - Built-in error catching with contextual error handlers
65
- - 🔄 **Method-Based Routing** - Separate routing trees for each HTTP method
66
- - 📦 **Local Dependencies** - Minimal Dependencies — Only internal @bepalo packages
67
- - 🌐 **Runtime Agnostic** - Works with Bun, Deno, Node.js, and other runtimes
68
- - 🔧 **TypeScript Ready** - Full type definitions included
69
- - 🧩 **Composable Router Architecture** - Append one router to another with a path prefix.
70
- - 🛠️ **Built-in Helper Utilities** - Built-in response helpers (json, html, parseBody, upload, etc.)
71
- - 🔐 **Middleware Integration** - CORS, rate limiting, authentication helpers
72
- - 🔐 **Normalized pathname option** - An option to normalize pathnames.
82
+ 8. [📄 License](#-license)
83
+ 9. [🕊️ Thanks and Enjoy](#-thanks-and-enjoy)
84
+ 10. [💖 Be a Sponsor](#-be-a-sponsor)
73
85
 
74
86
  ## 🚀 Get Started
75
87
 
@@ -91,9 +103,6 @@ yarn add @bepalo/router
91
103
 
92
104
  ```ts
93
105
  // Import directly using the URL:
94
-
95
- import { Router } from "npm:@bepalo/router";
96
- // or
97
106
  import { Router } from "jsr:@bepalo/router";
98
107
  ```
99
108
 
@@ -103,36 +112,50 @@ import { Router } from "jsr:@bepalo/router";
103
112
  import {
104
113
  Router,
105
114
  text,
115
+ html,
106
116
  json,
107
117
  type CTXBody,
108
118
  parseBody,
109
- type CTXUpload,
110
- parseUploadStreaming,
111
119
  } from "@bepalo/router";
112
120
  // } from "jsr:@bepalo/router"; // for deno
113
121
 
114
122
  // Create a router instance
115
- const router = new Router();
123
+ const router = new Router({
124
+ defaultHeaders: () => [
125
+ ["X-Powered-By", "@bepalo/router"],
126
+ ["Date", new Date().toUTCString()]
127
+ ],
128
+ defaultCatcher: (req, { error }) => {
129
+ console.error("Error:", error);
130
+ return status(500);
131
+ }
132
+ });
116
133
 
117
134
  // Simple GET route
118
135
  router.handle("GET /", () => text("Hello World!"));
119
136
 
120
137
  // Route with parameters
121
- router.handle("GET /users/:id", (req, ctx) => json({ userId: ctx.params.id }));
138
+ router.handle("GET /users/:id",
139
+ (req, { params }) => json({ userId: ctx.params.id })
140
+ );
122
141
 
123
142
  // POST route with JSON response
124
- router.handle("POST /users", async (req) => {
125
- const body = await req.json();
126
- return json({ created: true, data: body }, { status: 201 });
127
- });
143
+ router.handle<CTXBody>("POST /users", [
144
+ parseBody({ accept: ["application/json"], maxSize: 2048 }),
145
+ async (req, { body }) => {
146
+ return json({ created: true, data: body }, { status: 201 });
147
+ }
148
+ ]);
128
149
 
129
- // 404 fallback
130
- router.fallback("*", () => status(404));
150
+ // Custom fallback handler
151
+ router.fallback("GET /.**",
152
+ () => html(`<h2>404: Not Found!</h2>`, { status: 404 })
153
+ );
131
154
 
132
- // Error handler
133
- router.catch("*", (req, ctx) => {
134
- console.error("Error:", ctx.error);
135
- return status(500, "Something Went Wrong");
155
+ // Custom error handler
156
+ router.catch("ALL /api/**", (req, { error }) => {
157
+ console.error("API Error:", error);
158
+ return json({ error: error.message }, { status: 500 });
136
159
  });
137
160
 
138
161
  // Start server (Bun example)
@@ -140,6 +163,7 @@ Bun.serve({
140
163
  port: 3000,
141
164
  fetch: (req) => router.respond(req),
142
165
  });
166
+ console.log("Server running at http://localhost:3000");
143
167
 
144
168
  // Start server (Deno example)
145
169
  Deno.serve(
@@ -150,7 +174,38 @@ Deno.serve(
150
174
  router.respond.bind(router),
151
175
  );
152
176
 
153
- console.log("Server running at http://localhost:3000");
177
+ ```
178
+
179
+ ## 📚 Core Concepts
180
+
181
+ ### Handler Types & Execution Order
182
+
183
+ The router processes requests in this specific order:
184
+
185
+ 1. Hooks (router.hook()) - Pre-processing, responses are ignored
186
+
187
+ 2. Filters (router.filter()) - Request validation/authentication
188
+
189
+ 3. Handlers (router.handle()) - Main request processing
190
+
191
+ 4. Fallbacks (router.fallback()) - When no handler matches
192
+
193
+ 5. Afters (router.after()) - Post-processing, responses are ignored
194
+
195
+ 6. Catchers (router.catch()) - Error handling
196
+
197
+ ### Router Context
198
+
199
+ Each handler receives a context object and they can be extended as needed.
200
+
201
+ ```ts
202
+ interface RouterContext {
203
+ params: Record<string, string>; // Route parameters
204
+ headers: Headers; // Response headers
205
+ response?: Response; // Final response
206
+ error?: Error; // Uncertain error
207
+ found: { ...: boolean }; // which handler types were found
208
+ }
154
209
  ```
155
210
 
156
211
  ### Example
@@ -286,21 +341,12 @@ router.fallback("GET /api/.**", () =>
286
341
  );
287
342
 
288
343
  // Error handling
289
- router.catch(
290
- [
291
- "GET /api/.**",
292
- "POST /api/.**",
293
- "PUT /api/.**",
294
- "PATCH /api/.**",
295
- "DELETE /api/.**",
296
- ],
297
- [
298
- (req, ctx) => {
299
- console.error("APIError:", ctx.error);
300
- return json({ error: "Something went wrong" }, { status: 500 });
301
- },
302
- ],
303
- );
344
+ router.catch("CRUD /api/.**", [
345
+ (req, ctx) => {
346
+ console.error("APIError:", ctx.error);
347
+ return json({ error: "Something went wrong" }, { status: 500 });
348
+ },
349
+ ]);
304
350
 
305
351
  // Start server
306
352
  Bun.serve({
@@ -321,7 +367,7 @@ console.log("Server listening on http://localhost:3000");
321
367
  #### Bun
322
368
 
323
369
  ```js
324
- // Bun example
370
+ // Serving using Bun
325
371
  Bun.serve({
326
372
  port: 3000,
327
373
  async fetch(req, server) {
@@ -335,7 +381,7 @@ console.log("Server running at http://localhost:3000");
335
381
  #### Deno
336
382
 
337
383
  ```js
338
- // Deno example
384
+ // Serving using Deno
339
385
  Deno.serve(
340
386
  {
341
387
  port: 3000,
@@ -356,7 +402,7 @@ Deno.serve(
356
402
  #### Nodejs
357
403
 
358
404
  ```js
359
- // Node.js compatibility example (uses Fetch bridge; not optimized)
405
+ // Serving using Node.js
360
406
  http
361
407
  .createServer(async (req, res) => {
362
408
  const url = new URL(req.url || "/", `http://${req.headers.host}`);
@@ -404,445 +450,6 @@ http
404
450
  .listen(3000, () => console.log("Server running on port 3000"));
405
451
  ```
406
452
 
407
- ## 📚 Core Concepts
408
-
409
- ### Handler Types & Execution Order
410
-
411
- The router processes requests in this specific order:
412
-
413
- 1. Hooks (router.hook()) - Pre-processing, responses are ignored
414
-
415
- 2. Filters (router.filter()) - Request validation/authentication
416
-
417
- 3. Handlers (router.handle()) - Main request processing
418
-
419
- 4. Fallbacks (router.fallback()) - When no handler matches
420
-
421
- 5. Afters (router.after()) - Post-processing, responses are ignored
422
-
423
- 6. Catchers (router.catch()) - Error handling
424
-
425
- ### Router Context
426
-
427
- Each handler receives a context object with:
428
-
429
- ```ts
430
- interface RouterContext {
431
- params: Record<string, string>; // Route parameters
432
- headers: Headers; // Response headers
433
- address?: SocketAddress | null; // Client address
434
- response?: Response; // Final response
435
- error?: Error; // Caught error
436
- found: {
437
- hooks: boolean; // Whether hooks were found
438
- afters: boolean; // Whether afters were found
439
- filters: boolean; // Whether filters were found
440
- handlers: boolean; // Whether handlers were found
441
- fallbacks: boolean; // Whether fallbacks were found
442
- catchers: boolean; // Whether catchers were found
443
- };
444
- }
445
- ```
446
-
447
- ## 📖 API Reference
448
-
449
- ### Router Class
450
-
451
- #### Constructor
452
-
453
- ```ts
454
- new Router<Context>(config?: RouterConfig<Context>)
455
- ```
456
-
457
- #### Configuration Options:
458
-
459
- ```ts
460
- interface RouterConfig<Context extends RouterContext> {
461
- defaultHeaders?: Array<[string, string]>; // Default response headers
462
- defaultCatcher?: Handler<Context>; // Global error handler
463
- defaultFallback?: Handler<Context>; // Global fallback handler
464
- enable?: HandlerEnable; // Handler types enable/disable
465
- normalizeTrailingSlash?: boolean; // treat '/abc/' and '/abc' as the same
466
- }
467
- ```
468
-
469
- #### Handler Registration Methods
470
-
471
- All methods support method chaining and return the router instance. However `Response` type responses from hooks and afters are ignored unlike the rest.
472
-
473
- ```ts
474
- // Register a hook (pre-processing)
475
- router.hook(
476
- urls: "*" | MethodPath | MethodPath[],
477
- pipeline: Handler<Context> | Pipeline<Context>,
478
- options?: HandlerOptions
479
- )
480
-
481
- // Register a filter (request validation)
482
- router.filter(
483
- urls: "*" | MethodPath | MethodPath[],
484
- pipeline: Handler<Context> | Pipeline<Context>,
485
- options?: HandlerOptions
486
- )
487
-
488
- // Register a main handler
489
- router.handle(
490
- urls: "*" | MethodPath | MethodPath[],
491
- pipeline: Handler<Context> | Pipeline<Context>,
492
- options?: HandlerOptions
493
- )
494
-
495
- // Register a fallback handler (404)
496
- router.fallback(
497
- urls: "*" | MethodPath | MethodPath[],
498
- pipeline: Handler<Context> | Pipeline<Context>,
499
- options?: HandlerOptions
500
- )
501
-
502
- // Register an error catcher
503
- router.catch(
504
- urls: "*" | MethodPath | MethodPath[],
505
- pipeline: Handler<Context> | Pipeline<Context>,
506
- options?: HandlerOptions
507
- )
508
-
509
- // Register an after handler (post-processing)
510
- router.after(
511
- urls: "*" | MethodPath | MethodPath[],
512
- pipeline: Handler<Context> | Pipeline<Context>,
513
- options?: HandlerOptions
514
- )
515
- ```
516
-
517
- #### Handler Options:
518
-
519
- ```ts
520
- interface HandlerOptions {
521
- overwrite?: boolean; // Allow overriding existing routes (default: false)
522
- }
523
- ```
524
-
525
- #### Router Composition
526
-
527
- ```ts
528
- // Append another router with a prefix. Does not merge router configurations
529
- router.append(
530
- baseUrl: `/${string}`,
531
- router: Router<Context>,
532
- options?: HandlerOptions
533
- )
534
- ```
535
-
536
- #### Request Processing
537
-
538
- ```ts
539
- // Process a request
540
- router.respond(
541
- req: Request,
542
- context?: Partial<Context>
543
- ): Promise<Response>
544
- ```
545
-
546
- #### Helper Functions
547
-
548
- ```ts
549
- import {
550
- Status, // Status enum
551
- status, // HTTP status response
552
- redirect, // Redirect response with location header set
553
- forward, // forward to other path within the router.
554
- text, // Plain text response
555
- html, // HTML response
556
- json, // JSON response
557
- blob, // Blob response
558
- octetStream, // Octet-stream response
559
- formData, // FormData response
560
- usp, // URLSearchParams response
561
- send, // Smart response (auto-detects content type)
562
- setCookie, // Set cookie header
563
- clearCookie, // Clear cookie
564
- type CTXCookie,
565
- parseCookie, // Cookie parser
566
- type CTXBody,
567
- parseBody, // Body parser
568
- type CTXUpload,
569
- parseUploadStreaming, // multi-part-form-data and file upload stream parser
570
- } from "@bepalo/router";
571
-
572
- const router = new Router();
573
-
574
- // Usage examples
575
- router.handle("GET /text", () => text("Hello World"));
576
- router.handle("GET /html", () => html("<h1>Title</h1>"));
577
- router.handle("GET /json", () => json({ data: "value" }));
578
- router.handle("GET /status", () => status(Status._204_NoContent, null)); // No Content
579
- router.handle("GET /intro.mp4", () => blob(Bun.file("./intro.mp4")));
580
- router.handle("GET /download", () => octetStream(Bun.file("./intro.mp4")));
581
-
582
- router.handle("GET /new-location", () => html("GET new-location"));
583
- // router.handle("POST /new-location", () => html("POST new-location"));
584
- router.handle<CTXBody>("POST /new-location", [
585
- parseBody({ once: true, clone: true }),
586
- (req, { body }) => console.log(body),
587
- forward("/new-location2"),
588
- ]);
589
- router.handle<CTXBody>("POST /new-location2", [
590
- parseBody({ once: true, clone: false }),
591
- (req, { body }) => console.log(body),
592
- () => html("POST new-location2"),
593
- ]);
594
- router.handle("GET /redirected", () => redirect("/new-location"));
595
- router.handle("GET /forwarded", forward("/new-location"));
596
- // this would set the headers:
597
- // "x-forwarded-path": "/forwarded"
598
- // "x-original-path": "/forwarded"
599
- router.handle<CTXBody>("PUT /forwarded-with-new-method", [
600
- parseBody({ once: true, clone: true }),
601
- (req, { body }) => console.log(body),
602
- forward("/new-location", { method: "POST" }),
603
- ]);
604
- // this would set the headers:
605
- // "x-forwarded-method": "PUT"
606
- // "x-forwarded-path": "/new-location"
607
- // "x-original-path": "/forwarded-with-new-method"
608
- router.handle("GET /forwarded-conditional", function (this: Router, req, ctx) {
609
- if (req.headers.get("authorization"))
610
- return forward("/new-location").bind(this)(req, ctx);
611
- // or return forward("/new-location").apply(this, [req, ctx]);
612
- // NOTE: be careful when binding instance `router` instead of `this`
613
- // as it might be called from a different router due to append.
614
- return status(Status._401_Unauthorized);
615
- });
616
-
617
- router.handle<CTXCookie>("GET /cookie", [
618
- parseCookie(),
619
- (req, { cookie }) => json({ cookie }),
620
- ]);
621
-
622
- router.handle<CTXBody>("POST /cookie", [
623
- parseBody(),
624
- (req, { body }) => {
625
- return status(Status._200_OK, "OK", {
626
- // or `, undefined, {` and the status text will be set
627
- headers: [
628
- ...Object.entries(body).map(([name, value]) =>
629
- setCookie(name, String(value), {
630
- path: "/",
631
- expires: Time.after(5).minutes.fromNow()._ms,
632
- }),
633
- ),
634
- ],
635
- });
636
- },
637
- ]);
638
-
639
- router.handle("DELETE /cookie/:name", [
640
- (req, ctx) =>
641
- status(Status._200_OK, undefined, {
642
- headers: [clearCookie(ctx.params.name, { path: "/" })],
643
- }),
644
- ]);
645
-
646
- // be sure to create ./upload folder for this example
647
- router.handle<CTXUpload>("POST /upload", [
648
- (req, ctx) => {
649
- let file: Bun.BunFile;
650
- let fileWriter: Bun.FileSink;
651
- return parseUploadStreaming({
652
- allowedTypes: ["image/jpeg"],
653
- maxFileSize: 5 * 1024 * 1024,
654
- maxTotalSize: 5 * 1024 * 1024 + 1024,
655
- maxFields: 1,
656
- maxFiles: 1,
657
- // uploadIdGenerator: () =>
658
- // `upload_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`,
659
- async onUploadStart(uploadId, totalSize) {
660
- console.log("onUploadStart", { uploadId, totalSize });
661
- },
662
- async onError(uploadId, error) {
663
- console.error("onError", uploadId, error);
664
- },
665
- async onFileError(uploadId, fieldName, fileName, error) {
666
- console.error("onFileError", uploadId, error);
667
- },
668
- async onField(uploadId, fieldName, value) {
669
- console.log("onField", { uploadId, fieldName, value });
670
- },
671
- async onFileStart(uploadId, fieldName, fileName, contentType) {
672
- console.log("onFileStart", {
673
- uploadId,
674
- fieldName,
675
- fileName,
676
- contentType,
677
- });
678
- const ext = fileName.substring(fileName.lastIndexOf("."));
679
- const customFilename = uploadId + ext;
680
- file = Bun.file("./uploads/" + customFilename);
681
- fileWriter = file.writer();
682
- return {
683
- customFilename,
684
- // metadata
685
- };
686
- },
687
- async onFileChunk(uploadId, fieldName, fileName, chunk, offset, isLast) {
688
- const chunkSize = chunk.byteLength;
689
- console.log("onFileChunk", { uploadId, chunkSize, offset, isLast });
690
- fileWriter.write(chunk);
691
- },
692
- async onFileComplete(
693
- uploadId,
694
- fieldName,
695
- fileName,
696
- fileSize,
697
- customFilename,
698
- metadata,
699
- ) {
700
- console.log("onFileComplete", {
701
- uploadId,
702
- fieldName,
703
- fileName,
704
- fileSize,
705
- customFilename,
706
- metadata,
707
- });
708
- if (fileWriter) {
709
- fileWriter.end();
710
- }
711
- },
712
- async onUploadComplete(uploadId, success) {
713
- console.log("onUploadComplete", { uploadId, success });
714
- },
715
- })(req, ctx);
716
- },
717
- (req, { uploadId, fields, files }) => {
718
- console.log({ uploadId, fields, files });
719
- return status(Status._200_OK);
720
- },
721
- ]);
722
- ```
723
-
724
- #### Provided Middleware
725
-
726
- ```ts
727
- import {
728
- type CTXCookie,
729
- type CTXBody,
730
- type CTXAuth,
731
- type CTXUpload,
732
- parseCookie, // Cookie parser
733
- parseBody, // Body parser
734
- parseUploadStreaming, // multi-part-form-data and file upload stream parser
735
- cors, // CORS middleware
736
- limitRate, // Rate limiting
737
- authenticate, // Generic authentication middleware
738
- authorize, // Generic authorization middleware
739
- authBasic, // Basic authentication
740
- authAPIKey, // API key authentication
741
- authJWT, // JWT authentication
742
- } from "@bepalo/router";
743
- ```
744
-
745
- ### 🔧 Advanced Usage
746
-
747
- #### Pipeline (Multiple Handlers)
748
-
749
- ```js
750
- router.handle("POST /api/users", [
751
- // Middleware 1: Parse body
752
- async (req, ctx) => {
753
- const body = await req.json();
754
- ctx.body = body;
755
- },
756
-
757
- // Middleware 2: Validate
758
- (req, ctx) => {
759
- if (!ctx.body.email) {
760
- return text("Email is required", { status: 400 });
761
- }
762
- },
763
-
764
- // Handler: Process request
765
- async (req, ctx) => {
766
- const user = await db.users.create(ctx.body);
767
- return json(user, { status: 201 });
768
- },
769
- ]);
770
- ```
771
-
772
- ### Path Patterns
773
-
774
- ```js
775
- // Named parameters
776
- router.handle("GET /users/:id", handler); // Matches: /users/123
777
-
778
- // Single segment wildcard
779
- router.handle("GET /files/*", handler); // Matches: /files/a, /files/b
780
-
781
- // Single segment wildcard including current path (must be at end)
782
- router.handle("GET /files/.*", handler); // Matches: /files, /files/a, /files/b
783
-
784
- // Multi-segment wildcard (must be at end)
785
- router.handle("GET /docs/**", handler); // Matches: /docs/a, /docs/a/b/c
786
-
787
- // Multi-segment wildcard including current path (must be at end)
788
- router.handle("GET /docs/.**", handler); // Matches: /docs, /docs/a, /docs/a/b/c
789
-
790
- // Mixed patterns
791
- router.handle("GET /api/:version/*/details", handler); // /api/v1/users/details
792
-
793
- // All method-paths
794
- router.handle("*", handler); // GET/POST/PUT/etc /.**
795
- ```
796
-
797
- ### Route Priority
798
-
799
- Routes are matched in this order of priority:
800
-
801
- 1. Exact path matches
802
-
803
- 2. Path parameters (:id) and Single segment wildcards (*, .*)
804
-
805
- 3. Multi-segment wildcards (**, .**)
806
-
807
- ### Router Composition Example
808
-
809
- ```js
810
- // User API routes
811
- const userAPIRouter = new Router();
812
- userAPIRouter.handle("GET /", () => json({ user: {} }));
813
- userAPIRouter.handle <
814
- CTXBody >
815
- ("POST /",
816
- [
817
- parseBody(),
818
- async (req, { body }) => {
819
- return json({ success: true, data: body }, { status: 201 });
820
- },
821
- ]);
822
-
823
- // Session API routes
824
- const sessionAPIRouter = new Router();
825
- sessionAPIRouter.handle("GET /", () => json({ session: {} }));
826
- sessionAPIRouter.handle("POST /", [
827
- parseBody(),
828
- async (req, { body }) => {
829
- return json({ success: true, data: body }, { status: 201 });
830
- },
831
- ]);
832
-
833
- // API v1 router
834
- const v1APIRouter = new Router();
835
- v1APIRouter.handle("GET /status", () => json({ version: "1.0", status: "ok" }));
836
-
837
- // Composition is useful for defining routes in multiple files
838
- // and appending them in other routes.
839
- v1APIRouter.append("/user", userAPIRouter);
840
- v1APIRouter.append("/session", sessionAPIRouter);
841
-
842
- const mainRouter = new Router();
843
- mainRouter.append("/api/v1", v1APIRouter);
844
- ```
845
-
846
453
  ## 🎯 Performance
847
454
 
848
455
  The router uses a radix tree (trie) data structure for route matching, providing: