Skip to content

Commit 984ebad

Browse files
committed
refactor(router): migrate to FastRouter with radix tree structure 🌳
- Add allowedExtensions constant for supported file extensions - Add Request API delegation in DeserveRequest - Add new utility modules for better code organization - Convert Handler from functional to class-based architecture - Enhance DeserveRequest with full Request API delegation - Improve error handling with centralized error management - Migrate from URLPattern to FastRouter for O(1) static route lookup - Refactor Router to use Handler instance instead of direct functions - Remove functional approach in favor of object-oriented design - Remove URLPattern dependencies and route caching system - Reorganize Handler class with improved request processing - Simplify route pattern creation with string-based patterns - Update documentation to reflect new routing system - Update import paths configuration in deno.json - Update middleware pipeline management with setter methods
1 parent cfa06cd commit 984ebad

File tree

16 files changed

+884
-387
lines changed

16 files changed

+884
-387
lines changed

deno.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@
8888
"imports": {
8989
"@app/": "./src/",
9090
"@middlewares/": "./src/middlewares/",
91+
"@utils/": "./src/utils/",
9192
"@std/http": "jsr:@std/http@^1.0.21"
9293
},
9394
"publish": {

docs/core-concepts/file-based-routing.md

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -49,10 +49,10 @@ export function POST(req: Request): Response {
4949

5050
## Route Matching Priority
5151

52-
Deserve uses URLPattern for efficient route matching with this priority:
52+
Deserve uses FastRouter with radix tree structure for efficient route matching with this priority:
5353

54-
1. **Exact matches** - `users.ts` matches `/users`
55-
2. **Dynamic routes** - `users/[id].ts` matches `/users/123`
54+
1. **Static routes** - `users.ts` matches `/users` (O(1) lookup)
55+
2. **Dynamic routes** - `users/[id].ts` matches `/users/123` (O(k) tree traversal)
5656
3. **Longer paths** - More specific routes take precedence
5757

5858
## Examples
@@ -73,13 +73,17 @@ export function GET(req: Request): Response {
7373
### Dynamic Routes
7474
```typescript
7575
// routes/users/[id].ts
76-
export function GET(req: Request, params: Record<string, string>) {
76+
import { Send, DeserveRequest } from '@neabyte/deserve'
77+
78+
export function GET(req: DeserveRequest, params: Record<string, string>) {
7779
const { id } = params
7880
return Send.json({ userId: id })
7981
}
8082

8183
// routes/users/[id]/posts/[postId].ts
82-
export function GET(req: Request, params: Record<string, string>) {
84+
import { Send, DeserveRequest } from '@neabyte/deserve'
85+
86+
export function GET(req: DeserveRequest, params: Record<string, string>) {
8387
const { id, postId } = params
8488
return Send.json({ userId: id, postId })
8589
}
@@ -96,3 +100,4 @@ export function GET(req: Request, params: Record<string, string>) {
96100

97101
- [Route Patterns](/core-concepts/route-patterns) - Understanding pattern matching
98102
- [HTTP Methods](/core-concepts/http-methods) - Supported methods
103+
- [Request Handling](/core-concepts/request-handling) - Working with DeserveRequest

docs/core-concepts/http-methods.md

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,9 @@ export async function POST(req: Request): Response {
6262
### PUT - Update Resources
6363
```typescript
6464
// routes/users/[id].ts
65-
export async function PUT(req: Request, params: Record<string, string>) {
65+
import { Send, DeserveRequest } from '@neabyte/deserve'
66+
67+
export async function PUT(req: DeserveRequest, params: Record<string, string>) {
6668
const { id } = params
6769
const data = await req.json()
6870
// Update user with id...
@@ -73,7 +75,9 @@ export async function PUT(req: Request, params: Record<string, string>) {
7375
### PATCH - Partial Updates
7476
```typescript
7577
// routes/users/[id].ts
76-
export async function PATCH(req: Request, params: Record<string, string>) {
78+
import { Send, DeserveRequest } from '@neabyte/deserve'
79+
80+
export async function PATCH(req: DeserveRequest, params: Record<string, string>) {
7781
const { id } = params
7882
const data = await req.json()
7983
// Partial update user with id...
@@ -84,7 +88,9 @@ export async function PATCH(req: Request, params: Record<string, string>) {
8488
### DELETE - Remove Resources
8589
```typescript
8690
// routes/users/[id].ts
87-
export function DELETE(req: Request, params: Record<string, string>) {
91+
import { Send, DeserveRequest } from '@neabyte/deserve'
92+
93+
export function DELETE(req: DeserveRequest, params: Record<string, string>) {
8894
const { id } = params
8995
// Delete user with id...
9096
return Send.json({ message: 'User deleted', id })
@@ -160,3 +166,4 @@ router.onError((req, error) => {
160166

161167
- [Route Patterns](/core-concepts/route-patterns) - Understanding pattern matching
162168
- [File-based Routing](/core-concepts/file-based-routing) - Core routing concepts
169+
- [Request Handling](/core-concepts/request-handling) - Working with DeserveRequest

docs/core-concepts/route-patterns.md

Lines changed: 66 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,10 @@
11
# Route Patterns
22

3-
> **Reference**: [URLPattern API Documentation](https://docs.deno.com/api/web/~/URLPattern)
4-
5-
Deserve uses `URLPattern` for efficient route matching with native pattern support.
3+
Deserve uses a **Simple Router** with radix tree structure algorithm.
64

75
## Pattern Matching
86

9-
Deserve converts file paths to `URLPattern` instances for route matching:
7+
Deserve converts file paths to route patterns using a radix tree implementation:
108

119
```
1210
.
@@ -23,7 +21,9 @@ Use `[param]` syntax for dynamic route segments:
2321
### Single Parameter
2422
```typescript
2523
// routes/users/[id].ts
26-
export function GET(req: Request, params: Record<string, string>) {
24+
import { Send, DeserveRequest } from '@neabyte/deserve'
25+
26+
export function GET(req: DeserveRequest, params: Record<string, string>) {
2727
const { id } = params
2828
return Send.json({ userId: id })
2929
}
@@ -32,7 +32,9 @@ export function GET(req: Request, params: Record<string, string>) {
3232
### Multiple Parameters
3333
```typescript
3434
// routes/users/[id]/posts/[postId].ts
35-
export function GET(req: Request, params: Record<string, string>) {
35+
import { Send, DeserveRequest } from '@neabyte/deserve'
36+
37+
export function GET(req: DeserveRequest, params: Record<string, string>) {
3638
const { id, postId } = params
3739
return Send.json({ userId: id, postId })
3840
}
@@ -41,7 +43,9 @@ export function GET(req: Request, params: Record<string, string>) {
4143
### Nested Parameters
4244
```typescript
4345
// routes/api/v1/users/[userId]/posts/[postId]/comments/[commentId].ts
44-
export function GET(req: Request, params: Record<string, string>) {
46+
import { Send, DeserveRequest } from '@neabyte/deserve'
47+
48+
export function GET(req: DeserveRequest, params: Record<string, string>) {
4549
const { userId, postId, commentId } = params
4650
return Send.json({ userId, postId, commentId })
4751
}
@@ -52,32 +56,32 @@ export function GET(req: Request, params: Record<string, string>) {
5256
### User Management
5357
```
5458
routes/
55-
├── users.ts → /users
56-
├── users/[id].ts → /users/:id
57-
├── users/[id]/profile.ts → /users/:id/profile
58-
├── users/[id]/posts.ts → /users/:id/posts
59-
└── users/[id]/posts/[postId].ts → /users/:id/posts/:postId
59+
├── users.ts → /users
60+
├── users/[id].ts → /users/:id
61+
├── users/[id]/profile.ts → /users/:id/profile
62+
├── users/[id]/posts.ts → /users/:id/posts
63+
└── users/[id]/posts/[postId].ts → /users/:id/posts/:postId
6064
```
6165

6266
### API Versioning
6367
```
6468
routes/
6569
├── api/
6670
│ ├── v1/
67-
│ │ └── users/[id].ts → /api/v1/users/:id
71+
│ │ └── users/[id].ts → /api/v1/users/:id
6872
│ └── v2/
69-
│ └── users/[id].ts → /api/v2/users/:id
73+
│ └── users/[id].ts → /api/v2/users/:id
7074
```
7175

7276
### Blog System
7377
```
7478
routes/
7579
├── blog/
76-
│ ├── [slug].ts → /blog/:slug
80+
│ ├── [slug].ts → /blog/:slug
7781
│ └── [year]/
7882
│ └── [month]/
7983
│ └── [day]/
80-
│ └── [slug].ts → /blog/:year/:month/:day/:slug
84+
│ └── [slug].ts → /blog/:year/:month/:day/:slug
8185
```
8286

8387
## Route Matching Priority
@@ -103,7 +107,9 @@ Extract and validate parameters in your route handlers:
103107

104108
```typescript
105109
// routes/users/[id].ts
106-
export function GET(req: Request, params: Record<string, string>) {
110+
import { Send, DeserveRequest } from '@neabyte/deserve'
111+
112+
export function GET(req: DeserveRequest, params: Record<string, string>) {
107113
const { id } = params
108114
// Validate parameter
109115
if (!id || !/^\d+$/.test(id)) {
@@ -118,7 +124,9 @@ export function GET(req: Request, params: Record<string, string>) {
118124
### Optional Parameters
119125
```typescript
120126
// routes/posts/[slug].ts
121-
export function GET(req: Request, params: Record<string, string>) {
127+
import { Send, DeserveRequest } from '@neabyte/deserve'
128+
129+
export function GET(req: DeserveRequest, params: Record<string, string>) {
122130
const { slug } = params
123131
// Handle post by slug
124132
return Send.json({ slug })
@@ -128,26 +136,58 @@ export function GET(req: Request, params: Record<string, string>) {
128136
### Multiple Segments
129137
```typescript
130138
// routes/categories/[category]/products/[product].ts
131-
export function GET(req: Request, params: Record<string, string>) {
139+
import { Send, DeserveRequest } from '@neabyte/deserve'
140+
141+
export function GET(req: DeserveRequest, params: Record<string, string>) {
132142
const { category, product } = params
133143
return Send.json({ category, product })
134144
}
135145
```
136146

147+
## Performance Benefits
148+
149+
### Static Route Optimization
150+
Static routes (no parameters) are cached for O(1) lookup:
151+
```typescript
152+
// These routes are optimized for instant matching
153+
routes/index.ts/ (cached)
154+
routes/about.ts/about (cached)
155+
routes/users.ts/users (cached)
156+
```
157+
158+
### Dynamic Route Matching
159+
Dynamic routes use radix tree traversal for efficient matching:
160+
```typescript
161+
// These routes use tree traversal
162+
routes/users/[id].ts/users/:id
163+
routes/users/[id]/posts.ts/users/:id/posts
164+
routes/api/v1/[version].ts/api/v1/:version
165+
```
166+
137167
## Pattern Limitations
138168

139169
### Invalid Patterns
140170
```
141-
❌ routes/users/[...id].ts // Rest parameters not supported
142-
❌ routes/users/(group).ts // Groups not supported
143-
❌ routes/users/:id.ts // Colon syntax not supported
171+
❌ routes/users/[...id].ts // Rest parameters not supported
172+
❌ routes/users/(group).ts // Groups not supported
173+
❌ routes/users/:id.ts // Colon syntax not supported
174+
❌ routes/users/*.ts // Wildcard without brackets
175+
❌ routes/users/**.ts // Double wildcard without brackets
176+
```
177+
178+
### Supported Patterns
179+
```
180+
✅ routes/users/[id].ts // Single parameter
181+
✅ routes/users/[id]/posts.ts // Nested parameters
182+
✅ routes/api/v1/[version].ts // API versioning
183+
✅ routes/posts/[slug].ts // Slug-based routing
144184
```
145185

146186
### Valid Patterns
147187
```
148-
✅ routes/users/[id].ts // Single parameter
149-
✅ routes/users/[id]/posts.ts // Nested parameters
150-
✅ routes/api/v1/users.ts // Static segments
188+
✅ routes/users/[id].ts // Single parameter
189+
✅ routes/users/[id]/posts.ts // Nested parameters
190+
✅ routes/api/v1/users.ts // Static segments
151191
```
152192

153193
## Best Practices
@@ -179,3 +219,4 @@ router.onError((req, error) => {
179219

180220
- [HTTP Methods](/core-concepts/http-methods) - Supported methods
181221
- [File-based Routing](/core-concepts/file-based-routing) - Core routing concepts
222+
- [Request Handling](/core-concepts/request-handling) - Working with DeserveRequest

docs/getting-started/installation.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,4 +22,6 @@ This will:
2222

2323
Now that Deserve is installed, let's create your first server!
2424

25-
[Quick Start →](/getting-started/quick-start)
25+
- [Quick Start](/getting-started/quick-start) - Create your first API
26+
- [Server Configuration](/getting-started/server-configuration) - Configure your server
27+
- [Custom Configuration](/getting-started/custom-configuration) - Advanced router options

docs/getting-started/quick-start.md

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -96,10 +96,10 @@ Create `routes/users/[id].ts`:
9696

9797
```typescript
9898
// routes/users/[id].ts
99-
import { Send } from '@neabyte/deserve'
99+
import { Send, DeserveRequest } from '@neabyte/deserve'
100100

101101
// GET /users/:id -> Returns a JSON response with the user id and name
102-
export function GET(req: Request, params: Record<string, string>) {
102+
export function GET(req: DeserveRequest, params: Record<string, string>) {
103103
const { id } = params
104104
return Send.json({ userId: id, name: `User ${id}` })
105105
}
@@ -110,6 +110,34 @@ Test it:
110110
curl http://localhost:8000/users/123
111111
```
112112

113+
## Request Types
114+
115+
Deserve provides two request types:
116+
117+
- **`Request`**: Use for static routes without parameters
118+
- **`DeserveRequest`**: Use for dynamic routes with parameters (like `[id].ts`)
119+
120+
### Static Routes (use `Request`)
121+
```typescript
122+
// routes/index.ts
123+
export function GET(req: Request): Response {
124+
return Send.json({ message: 'Hello!' })
125+
}
126+
```
127+
128+
### Dynamic Routes (use `DeserveRequest`)
129+
```typescript
130+
// routes/users/[id].ts
131+
import { DeserveRequest } from '@neabyte/deserve'
132+
133+
// GET /users/:id -> Returns a JSON response with the user id and query parameters
134+
export function GET(req: DeserveRequest, params: Record<string, string>) {
135+
const userId = req.param('id') // Access route parameter
136+
const query = req.query() // Access query parameters
137+
return Send.json({ userId, query })
138+
}
139+
```
140+
113141
## What's Next?
114142

115143
You now have a working API! Explore more features:

docs/response/file.md

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,9 @@ export async function GET(req: Request): Response {
5050

5151
```typescript
5252
// routes/downloads/[filename].ts
53-
export async function GET(req: Request, params: Record<string, string>): Response {
53+
import { Send, DeserveRequest } from '@neabyte/deserve'
54+
55+
export async function GET(req: DeserveRequest, params: Record<string, string>): Response {
5456
const { filename } = params
5557
// Validate filename to prevent directory traversal
5658
if (filename.includes('..') || filename.includes('/')) {
@@ -141,7 +143,9 @@ export async function GET(req: Request): Response {
141143
## Example Security Implementation
142144

143145
```typescript
144-
export async function GET(req: Request, params: Record<string, string>): Response {
146+
import { Send, DeserveRequest } from '@neabyte/deserve'
147+
148+
export async function GET(req: DeserveRequest, params: Record<string, string>): Response {
145149
const { filename } = params
146150
// Security checks
147151
if (!filename || filename.includes('..') || filename.includes('/')) {

docs/response/html.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,9 @@ export function GET(req: Request): Response {
5252
## Dynamic Content
5353

5454
```typescript
55-
export function GET(req: Request, params: Record<string, string>) {
55+
import { Send, DeserveRequest } from '@neabyte/deserve'
56+
57+
export function GET(req: DeserveRequest, params: Record<string, string>) {
5658
const { id } = params
5759
const html = `
5860
<!DOCTYPE html>

src/Constant.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
1+
/**
2+
* Supported file extensions for route files.
3+
*/
4+
export const allowedExtensions = ['.cjs', '.js', '.jsx', '.mjs', '.ts', '.tsx']
5+
16
/**
27
* Standard HTTP methods supported.
38
*/

0 commit comments

Comments
 (0)