@devbro/neko-router 0.1.17 → 0.1.18
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 +703 -32
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -1,47 +1,718 @@
|
|
|
1
1
|
# @devbro/neko-router
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
A powerful, flexible routing solution for Node.js and TypeScript applications. Build RESTful APIs and web applications with an intuitive, Express-like API.
|
|
4
4
|
|
|
5
|
-
##
|
|
5
|
+
## Installation
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
7
|
+
```bash
|
|
8
|
+
npm install @devbro/neko-router
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Features
|
|
12
|
+
|
|
13
|
+
- 🎯 **Flexible Routing** - Support for functions and controllers
|
|
14
|
+
- 🛣️ **Path Parameters** - Dynamic route segments with type safety
|
|
15
|
+
- 🔒 **Middleware Support** - Function and class-based middleware
|
|
16
|
+
- 🔄 **Singleton & Per-Request** - Choose middleware lifecycle
|
|
17
|
+
- 📝 **Multiple HTTP Methods** - GET, POST, PUT, DELETE, PATCH, etc.
|
|
18
|
+
- 🎨 **Fluent API** - Chainable, intuitive interface
|
|
19
|
+
- 🛡️ **Type-Safe** - Full TypeScript support
|
|
20
|
+
- ⚡ **High Performance** - Fast route matching and resolution
|
|
21
|
+
|
|
22
|
+
## Quick Start
|
|
23
|
+
|
|
24
|
+
```ts
|
|
25
|
+
import { Router, Request, Response } from '@devbro/neko-router';
|
|
26
|
+
|
|
27
|
+
// Create router instance
|
|
28
|
+
const router = new Router();
|
|
29
|
+
|
|
30
|
+
// Add a simple route
|
|
31
|
+
router.addRoute(['GET'], '/api/users', async (req: Request, res: Response) => {
|
|
32
|
+
res.statusCode = 200;
|
|
33
|
+
return { users: ['Alice', 'Bob', 'Charlie'] };
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
// Resolve and execute
|
|
37
|
+
const req = { url: '/api/users', method: 'GET' } as Request;
|
|
38
|
+
const res = {} as Response;
|
|
39
|
+
|
|
40
|
+
const compiledRoute = router.getCompiledRoute(req, res);
|
|
41
|
+
const result = await compiledRoute.run();
|
|
42
|
+
|
|
43
|
+
console.log(result); // { users: ['Alice', 'Bob', 'Charlie'] }
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## Core Concepts
|
|
47
|
+
|
|
48
|
+
### Basic Routing
|
|
49
|
+
|
|
50
|
+
Define routes for different HTTP methods:
|
|
51
|
+
|
|
52
|
+
```ts
|
|
53
|
+
import { Router, Request, Response } from '@devbro/neko-router';
|
|
54
|
+
|
|
55
|
+
const router = new Router();
|
|
56
|
+
|
|
57
|
+
// GET request
|
|
58
|
+
router.addRoute(['GET'], '/api/posts', async (req: Request, res: Response) => {
|
|
59
|
+
res.statusCode = 200;
|
|
60
|
+
return { posts: [] };
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
// POST request
|
|
64
|
+
router.addRoute(['POST'], '/api/posts', async (req: Request, res: Response) => {
|
|
65
|
+
res.statusCode = 201;
|
|
66
|
+
return { message: 'Post created' };
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
// PUT request
|
|
70
|
+
router.addRoute(['PUT'], '/api/posts/:id', async (req: Request, res: Response) => {
|
|
71
|
+
res.statusCode = 200;
|
|
72
|
+
return { message: `Post ${req.params.id} updated` };
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
// DELETE request
|
|
76
|
+
router.addRoute(['DELETE'], '/api/posts/:id', async (req: Request, res: Response) => {
|
|
77
|
+
res.statusCode = 204;
|
|
78
|
+
return null;
|
|
79
|
+
});
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
### Multiple HTTP Methods
|
|
83
|
+
|
|
84
|
+
Handle multiple methods on the same route:
|
|
85
|
+
|
|
86
|
+
```ts
|
|
87
|
+
// Support both GET and HEAD
|
|
88
|
+
router.addRoute(['GET', 'HEAD'], '/api/health', async (req: Request, res: Response) => {
|
|
89
|
+
res.statusCode = 200;
|
|
90
|
+
return { status: 'healthy' };
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
// Common pattern for resources
|
|
94
|
+
router.addRoute(['GET', 'POST'], '/api/comments', async (req: Request, res: Response) => {
|
|
95
|
+
if (req.method === 'GET') {
|
|
96
|
+
return { comments: [] };
|
|
97
|
+
} else {
|
|
98
|
+
return { message: 'Comment created' };
|
|
99
|
+
}
|
|
100
|
+
});
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
### Path Parameters
|
|
104
|
+
|
|
105
|
+
Capture dynamic segments from the URL:
|
|
106
|
+
|
|
107
|
+
```ts
|
|
108
|
+
// Single parameter
|
|
109
|
+
router.addRoute(['GET'], '/api/users/:userId', async (req: Request, res: Response) => {
|
|
110
|
+
const userId = req.params.userId;
|
|
111
|
+
res.statusCode = 200;
|
|
112
|
+
return { userId, name: 'John Doe' };
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
// Multiple parameters
|
|
116
|
+
router.addRoute(
|
|
117
|
+
['GET'],
|
|
118
|
+
'/api/posts/:postId/comments/:commentId',
|
|
119
|
+
async (req: Request, res: Response) => {
|
|
120
|
+
const { postId, commentId } = req.params;
|
|
121
|
+
res.statusCode = 200;
|
|
122
|
+
return { postId, commentId };
|
|
123
|
+
}
|
|
124
|
+
);
|
|
125
|
+
|
|
126
|
+
// Optional parameters with query strings
|
|
127
|
+
router.addRoute(['GET'], '/api/search/:category', async (req: Request, res: Response) => {
|
|
128
|
+
const category = req.params.category;
|
|
129
|
+
const query = req.query?.q || '';
|
|
130
|
+
res.statusCode = 200;
|
|
131
|
+
return { category, query };
|
|
132
|
+
});
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
## Middleware
|
|
136
|
+
|
|
137
|
+
### Function Middleware
|
|
138
|
+
|
|
139
|
+
Use simple functions as middleware:
|
|
140
|
+
|
|
141
|
+
```ts
|
|
142
|
+
// Logging middleware
|
|
143
|
+
const loggerMiddleware = async (req: Request, res: Response, next: Function) => {
|
|
144
|
+
console.log(`[${req.method}] ${req.url}`);
|
|
145
|
+
await next();
|
|
146
|
+
console.log(`Response: ${res.statusCode}`);
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
// Authentication middleware
|
|
150
|
+
const authMiddleware = async (req: Request, res: Response, next: Function) => {
|
|
151
|
+
const token = req.headers?.authorization;
|
|
152
|
+
|
|
153
|
+
if (!token) {
|
|
154
|
+
res.statusCode = 401;
|
|
155
|
+
return { error: 'Unauthorized' };
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// Verify token...
|
|
159
|
+
req.user = { id: 1, name: 'User' };
|
|
160
|
+
await next();
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
// Apply middleware to route
|
|
164
|
+
router
|
|
165
|
+
.addRoute(['GET'], '/api/protected', async (req: Request, res: Response) => {
|
|
166
|
+
return { user: req.user };
|
|
167
|
+
})
|
|
168
|
+
.addMiddleware([loggerMiddleware, authMiddleware]);
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
### Multiple Middleware
|
|
11
172
|
|
|
12
|
-
|
|
173
|
+
Chain multiple middleware functions:
|
|
13
174
|
|
|
14
175
|
```ts
|
|
15
|
-
|
|
16
|
-
|
|
176
|
+
const middleware1 = async (req: Request, res: Response, next: Function) => {
|
|
177
|
+
console.log('Middleware 1 - Before');
|
|
178
|
+
await next();
|
|
179
|
+
console.log('Middleware 1 - After');
|
|
180
|
+
};
|
|
17
181
|
|
|
18
|
-
|
|
19
|
-
|
|
182
|
+
const middleware2 = async (req: Request, res: Response, next: Function) => {
|
|
183
|
+
console.log('Middleware 2 - Before');
|
|
184
|
+
await next();
|
|
185
|
+
console.log('Middleware 2 - After');
|
|
186
|
+
};
|
|
187
|
+
|
|
188
|
+
router
|
|
189
|
+
.addRoute(['POST'], '/api/data', async (req: Request, res: Response) => {
|
|
190
|
+
return { message: 'Data processed' };
|
|
191
|
+
})
|
|
192
|
+
.addMiddleware([middleware1, middleware2]);
|
|
193
|
+
|
|
194
|
+
// Execution order:
|
|
195
|
+
// Middleware 1 - Before
|
|
196
|
+
// Middleware 2 - Before
|
|
197
|
+
// Route handler
|
|
198
|
+
// Middleware 2 - After
|
|
199
|
+
// Middleware 1 - After
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
### Inline Middleware
|
|
203
|
+
|
|
204
|
+
Add middleware directly in the chain:
|
|
205
|
+
|
|
206
|
+
```ts
|
|
207
|
+
router
|
|
208
|
+
.addRoute(['GET'], '/api/users/:id', async (req: Request, res: Response) => {
|
|
209
|
+
return { userId: req.params.id };
|
|
210
|
+
})
|
|
211
|
+
.addMiddleware(async (req, res, next) => {
|
|
212
|
+
console.log('Accessing user:', req.params.id);
|
|
213
|
+
await next();
|
|
214
|
+
console.log('Request completed with status:', res.statusCode);
|
|
215
|
+
});
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
### Class-Based Middleware
|
|
219
|
+
|
|
220
|
+
Use classes for more complex middleware:
|
|
221
|
+
|
|
222
|
+
```ts
|
|
223
|
+
class AuthenticationMiddleware {
|
|
224
|
+
async handle(req: Request, res: Response, next: Function) {
|
|
225
|
+
// Authentication logic
|
|
226
|
+
const token = req.headers?.authorization;
|
|
227
|
+
|
|
228
|
+
if (!token) {
|
|
229
|
+
res.statusCode = 401;
|
|
230
|
+
throw new Error('No token provided');
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// Verify and attach user
|
|
234
|
+
req.user = await this.verifyToken(token);
|
|
235
|
+
await next();
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
private async verifyToken(token: string) {
|
|
239
|
+
// Token verification logic
|
|
240
|
+
return { id: 1, email: 'user@example.com' };
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// Use the class middleware
|
|
245
|
+
const authMiddleware = new AuthenticationMiddleware();
|
|
246
|
+
|
|
247
|
+
router
|
|
248
|
+
.addRoute(['GET'], '/api/profile', async (req: Request, res: Response) => {
|
|
249
|
+
return { profile: req.user };
|
|
250
|
+
})
|
|
251
|
+
.addMiddleware(authMiddleware.handle.bind(authMiddleware));
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
### Singleton vs Per-Request Middleware
|
|
255
|
+
|
|
256
|
+
```ts
|
|
257
|
+
// Singleton middleware - shared instance across requests
|
|
258
|
+
class CacheMiddleware {
|
|
259
|
+
private cache = new Map();
|
|
260
|
+
|
|
261
|
+
async handle(req: Request, res: Response, next: Function) {
|
|
262
|
+
const key = req.url;
|
|
263
|
+
|
|
264
|
+
if (this.cache.has(key)) {
|
|
265
|
+
return this.cache.get(key);
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
await next();
|
|
269
|
+
this.cache.set(key, res.body);
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
const cacheMiddleware = new CacheMiddleware(); // Single instance
|
|
274
|
+
|
|
275
|
+
// Per-request middleware - new instance for each request
|
|
276
|
+
router
|
|
277
|
+
.addRoute(['GET'], '/api/data', async (req: Request, res: Response) => {
|
|
278
|
+
return { data: 'expensive computation' };
|
|
279
|
+
})
|
|
280
|
+
.addMiddleware(cacheMiddleware.handle.bind(cacheMiddleware));
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
## Route Resolution
|
|
284
|
+
|
|
285
|
+
### Resolving Routes
|
|
286
|
+
|
|
287
|
+
```ts
|
|
288
|
+
const router = new Router();
|
|
289
|
+
|
|
290
|
+
router.addRoute(['GET'], '/api/users', async (req: Request, res: Response) => {
|
|
291
|
+
return { users: [] };
|
|
20
292
|
});
|
|
21
293
|
|
|
294
|
+
// Create request object
|
|
295
|
+
const req = {
|
|
296
|
+
url: '/api/users',
|
|
297
|
+
method: 'GET',
|
|
298
|
+
headers: {},
|
|
299
|
+
query: {},
|
|
300
|
+
} as Request;
|
|
301
|
+
|
|
302
|
+
const res = {} as Response;
|
|
303
|
+
|
|
304
|
+
// Resolve the route
|
|
305
|
+
const resolved = router.resolve(req);
|
|
306
|
+
console.log(resolved); // Route information
|
|
307
|
+
|
|
308
|
+
// Get compiled route and execute
|
|
309
|
+
const compiledRoute = router.getCompiledRoute(req, res);
|
|
310
|
+
const result = await compiledRoute.run();
|
|
311
|
+
|
|
312
|
+
console.log(res.statusCode); // 200
|
|
313
|
+
console.log(result); // { users: [] }
|
|
314
|
+
```
|
|
315
|
+
|
|
316
|
+
### Handling 404
|
|
317
|
+
|
|
318
|
+
```ts
|
|
319
|
+
const req = { url: '/api/nonexistent', method: 'GET' } as Request;
|
|
320
|
+
const res = {} as Response;
|
|
321
|
+
|
|
322
|
+
try {
|
|
323
|
+
const compiledRoute = router.getCompiledRoute(req, res);
|
|
324
|
+
await compiledRoute.run();
|
|
325
|
+
} catch (error) {
|
|
326
|
+
// Handle route not found
|
|
327
|
+
res.statusCode = 404;
|
|
328
|
+
return { error: 'Route not found' };
|
|
329
|
+
}
|
|
330
|
+
```
|
|
331
|
+
|
|
332
|
+
## Real-World Examples
|
|
333
|
+
|
|
334
|
+
### RESTful API
|
|
335
|
+
|
|
336
|
+
```ts
|
|
337
|
+
import { Router, Request, Response } from '@devbro/neko-router';
|
|
338
|
+
|
|
339
|
+
const router = new Router();
|
|
340
|
+
|
|
341
|
+
// Logging middleware
|
|
342
|
+
const logger = async (req: Request, res: Response, next: Function) => {
|
|
343
|
+
const start = Date.now();
|
|
344
|
+
await next();
|
|
345
|
+
const duration = Date.now() - start;
|
|
346
|
+
console.log(`[${req.method}] ${req.url} - ${res.statusCode} (${duration}ms)`);
|
|
347
|
+
};
|
|
348
|
+
|
|
349
|
+
// List all posts
|
|
350
|
+
router
|
|
351
|
+
.addRoute(['GET'], '/api/posts', async (req: Request, res: Response) => {
|
|
352
|
+
const posts = await database.getPosts();
|
|
353
|
+
res.statusCode = 200;
|
|
354
|
+
return { posts };
|
|
355
|
+
})
|
|
356
|
+
.addMiddleware(logger);
|
|
357
|
+
|
|
358
|
+
// Get single post
|
|
359
|
+
router
|
|
360
|
+
.addRoute(['GET'], '/api/posts/:id', async (req: Request, res: Response) => {
|
|
361
|
+
const post = await database.getPost(req.params.id);
|
|
362
|
+
res.statusCode = 200;
|
|
363
|
+
return { post };
|
|
364
|
+
})
|
|
365
|
+
.addMiddleware(logger);
|
|
366
|
+
|
|
367
|
+
// Create post
|
|
22
368
|
router
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
369
|
+
.addRoute(['POST'], '/api/posts', async (req: Request, res: Response) => {
|
|
370
|
+
const newPost = await database.createPost(req.body);
|
|
371
|
+
res.statusCode = 201;
|
|
372
|
+
return { post: newPost };
|
|
373
|
+
})
|
|
374
|
+
.addMiddleware([logger, authMiddleware]);
|
|
27
375
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
376
|
+
// Update post
|
|
377
|
+
router
|
|
378
|
+
.addRoute(['PUT'], '/api/posts/:id', async (req: Request, res: Response) => {
|
|
379
|
+
const updated = await database.updatePost(req.params.id, req.body);
|
|
380
|
+
res.statusCode = 200;
|
|
381
|
+
return { post: updated };
|
|
382
|
+
})
|
|
383
|
+
.addMiddleware([logger, authMiddleware]);
|
|
384
|
+
|
|
385
|
+
// Delete post
|
|
386
|
+
router
|
|
387
|
+
.addRoute(['DELETE'], '/api/posts/:id', async (req: Request, res: Response) => {
|
|
388
|
+
await database.deletePost(req.params.id);
|
|
389
|
+
res.statusCode = 204;
|
|
390
|
+
return null;
|
|
391
|
+
})
|
|
392
|
+
.addMiddleware([logger, authMiddleware]);
|
|
393
|
+
```
|
|
394
|
+
|
|
395
|
+
### Error Handling Middleware
|
|
396
|
+
|
|
397
|
+
```ts
|
|
398
|
+
const errorHandler = async (req: Request, res: Response, next: Function) => {
|
|
399
|
+
try {
|
|
400
|
+
await next();
|
|
401
|
+
} catch (error) {
|
|
402
|
+
console.error('Error:', error);
|
|
403
|
+
res.statusCode = 500;
|
|
404
|
+
return {
|
|
405
|
+
error: 'Internal Server Error',
|
|
406
|
+
message: error.message,
|
|
407
|
+
};
|
|
408
|
+
}
|
|
409
|
+
};
|
|
410
|
+
|
|
411
|
+
router
|
|
412
|
+
.addRoute(['GET'], '/api/risky', async (req: Request, res: Response) => {
|
|
413
|
+
throw new Error('Something went wrong!');
|
|
414
|
+
})
|
|
415
|
+
.addMiddleware(errorHandler);
|
|
416
|
+
```
|
|
417
|
+
|
|
418
|
+
### Request Validation
|
|
419
|
+
|
|
420
|
+
```ts
|
|
421
|
+
const validateCreateUser = async (req: Request, res: Response, next: Function) => {
|
|
422
|
+
const { email, name } = req.body;
|
|
423
|
+
|
|
424
|
+
if (!email || !name) {
|
|
425
|
+
res.statusCode = 400;
|
|
426
|
+
return { error: 'Email and name are required' };
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
if (!email.includes('@')) {
|
|
430
|
+
res.statusCode = 400;
|
|
431
|
+
return { error: 'Invalid email format' };
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
await next();
|
|
435
|
+
};
|
|
436
|
+
|
|
437
|
+
router
|
|
438
|
+
.addRoute(['POST'], '/api/users', async (req: Request, res: Response) => {
|
|
439
|
+
const user = await createUser(req.body);
|
|
440
|
+
res.statusCode = 201;
|
|
441
|
+
return { user };
|
|
442
|
+
})
|
|
443
|
+
.addMiddleware(validateCreateUser);
|
|
444
|
+
```
|
|
445
|
+
|
|
446
|
+
### CORS Middleware
|
|
447
|
+
|
|
448
|
+
```ts
|
|
449
|
+
const corsMiddleware = async (req: Request, res: Response, next: Function) => {
|
|
450
|
+
res.headers = {
|
|
451
|
+
...res.headers,
|
|
452
|
+
'Access-Control-Allow-Origin': '*',
|
|
453
|
+
'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',
|
|
454
|
+
'Access-Control-Allow-Headers': 'Content-Type, Authorization',
|
|
455
|
+
};
|
|
456
|
+
|
|
457
|
+
if (req.method === 'OPTIONS') {
|
|
458
|
+
res.statusCode = 204;
|
|
459
|
+
return null;
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
await next();
|
|
463
|
+
};
|
|
464
|
+
|
|
465
|
+
// Apply to all routes
|
|
466
|
+
router
|
|
467
|
+
.addRoute(
|
|
468
|
+
['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],
|
|
469
|
+
'/api/*',
|
|
470
|
+
async (req: Request, res: Response) => {
|
|
471
|
+
// Route handler
|
|
472
|
+
}
|
|
473
|
+
)
|
|
474
|
+
.addMiddleware(corsMiddleware);
|
|
475
|
+
```
|
|
476
|
+
|
|
477
|
+
### Rate Limiting
|
|
478
|
+
|
|
479
|
+
```ts
|
|
480
|
+
class RateLimiter {
|
|
481
|
+
private requests = new Map<string, number[]>();
|
|
482
|
+
private limit = 100;
|
|
483
|
+
private window = 60000; // 1 minute
|
|
484
|
+
|
|
485
|
+
async handle(req: Request, res: Response, next: Function) {
|
|
486
|
+
const ip = req.headers['x-forwarded-for'] || req.connection?.remoteAddress;
|
|
487
|
+
const now = Date.now();
|
|
488
|
+
|
|
489
|
+
if (!this.requests.has(ip)) {
|
|
490
|
+
this.requests.set(ip, []);
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
const userRequests = this.requests.get(ip)!;
|
|
494
|
+
const recentRequests = userRequests.filter((time) => now - time < this.window);
|
|
495
|
+
|
|
496
|
+
if (recentRequests.length >= this.limit) {
|
|
497
|
+
res.statusCode = 429;
|
|
498
|
+
return { error: 'Too many requests' };
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
recentRequests.push(now);
|
|
502
|
+
this.requests.set(ip, recentRequests);
|
|
503
|
+
|
|
504
|
+
await next();
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
const rateLimiter = new RateLimiter();
|
|
509
|
+
|
|
510
|
+
router
|
|
511
|
+
.addRoute(['GET'], '/api/data', async (req: Request, res: Response) => {
|
|
512
|
+
return { data: 'sensitive information' };
|
|
513
|
+
})
|
|
514
|
+
.addMiddleware(rateLimiter.handle.bind(rateLimiter));
|
|
515
|
+
```
|
|
516
|
+
|
|
517
|
+
## Integration with HTTP Servers
|
|
518
|
+
|
|
519
|
+
### With Node.js HTTP
|
|
520
|
+
|
|
521
|
+
```ts
|
|
522
|
+
import http from 'http';
|
|
523
|
+
import { Router, Request, Response } from '@devbro/neko-router';
|
|
524
|
+
|
|
525
|
+
const router = new Router();
|
|
526
|
+
|
|
527
|
+
// Define routes
|
|
528
|
+
router.addRoute(['GET'], '/api/hello', async (req: Request, res: Response) => {
|
|
529
|
+
res.statusCode = 200;
|
|
530
|
+
return { message: 'Hello World' };
|
|
531
|
+
});
|
|
532
|
+
|
|
533
|
+
// Create HTTP server
|
|
534
|
+
const server = http.createServer(async (req, res) => {
|
|
535
|
+
try {
|
|
536
|
+
const request = req as Request;
|
|
537
|
+
const response = res as Response;
|
|
538
|
+
|
|
539
|
+
const compiledRoute = router.getCompiledRoute(request, response);
|
|
540
|
+
const result = await compiledRoute.run();
|
|
541
|
+
|
|
542
|
+
res.writeHead(response.statusCode || 200, { 'Content-Type': 'application/json' });
|
|
543
|
+
res.end(JSON.stringify(result));
|
|
544
|
+
} catch (error) {
|
|
545
|
+
res.writeHead(404, { 'Content-Type': 'application/json' });
|
|
546
|
+
res.end(JSON.stringify({ error: 'Not Found' }));
|
|
547
|
+
}
|
|
548
|
+
});
|
|
549
|
+
|
|
550
|
+
server.listen(3000, () => {
|
|
551
|
+
console.log('Server running on port 3000');
|
|
552
|
+
});
|
|
553
|
+
```
|
|
554
|
+
|
|
555
|
+
### With Express
|
|
556
|
+
|
|
557
|
+
```ts
|
|
558
|
+
import express from 'express';
|
|
559
|
+
import { Router as NekoRouter, Request, Response } from '@devbro/neko-router';
|
|
39
560
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
let resolved = router.resolve(req);
|
|
561
|
+
const app = express();
|
|
562
|
+
const router = new NekoRouter();
|
|
43
563
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
564
|
+
// Define routes in neko-router
|
|
565
|
+
router.addRoute(['GET'], '/api/users', async (req: Request, res: Response) => {
|
|
566
|
+
res.statusCode = 200;
|
|
567
|
+
return { users: [] };
|
|
568
|
+
});
|
|
569
|
+
|
|
570
|
+
// Use as Express middleware
|
|
571
|
+
app.use(async (req, res, next) => {
|
|
572
|
+
try {
|
|
573
|
+
const request = req as Request;
|
|
574
|
+
const response = res as Response;
|
|
575
|
+
|
|
576
|
+
const compiledRoute = router.getCompiledRoute(request, response);
|
|
577
|
+
const result = await compiledRoute.run();
|
|
578
|
+
|
|
579
|
+
res.status(response.statusCode || 200).json(result);
|
|
580
|
+
} catch (error) {
|
|
581
|
+
next();
|
|
582
|
+
}
|
|
583
|
+
});
|
|
584
|
+
|
|
585
|
+
app.listen(3000);
|
|
47
586
|
```
|
|
587
|
+
|
|
588
|
+
## Best Practices
|
|
589
|
+
|
|
590
|
+
1. **Organize Routes** - Group related routes together
|
|
591
|
+
2. **Use Middleware** - Extract common logic into reusable middleware
|
|
592
|
+
3. **Error Handling** - Always wrap route handlers in error handling middleware
|
|
593
|
+
4. **Validation** - Validate input data before processing
|
|
594
|
+
5. **Type Safety** - Use TypeScript interfaces for request/response types
|
|
595
|
+
6. **Status Codes** - Set appropriate HTTP status codes
|
|
596
|
+
7. **Async/Await** - Use async/await for cleaner asynchronous code
|
|
597
|
+
8. **Naming** - Use descriptive names for routes and middleware
|
|
598
|
+
|
|
599
|
+
## TypeScript Support
|
|
600
|
+
|
|
601
|
+
Full TypeScript definitions included:
|
|
602
|
+
|
|
603
|
+
```ts
|
|
604
|
+
import { Router, Request, Response, Middleware } from '@devbro/neko-router';
|
|
605
|
+
|
|
606
|
+
// Extend Request type
|
|
607
|
+
interface CustomRequest extends Request {
|
|
608
|
+
user?: {
|
|
609
|
+
id: number;
|
|
610
|
+
email: string;
|
|
611
|
+
};
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
// Type-safe route handler
|
|
615
|
+
router.addRoute(['GET'], '/api/profile', async (req: CustomRequest, res: Response) => {
|
|
616
|
+
if (!req.user) {
|
|
617
|
+
res.statusCode = 401;
|
|
618
|
+
return { error: 'Unauthorized' };
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
return { profile: req.user };
|
|
622
|
+
});
|
|
623
|
+
|
|
624
|
+
// Type-safe middleware
|
|
625
|
+
const authMiddleware: Middleware = async (req: CustomRequest, res: Response, next: Function) => {
|
|
626
|
+
// Authentication logic
|
|
627
|
+
await next();
|
|
628
|
+
};
|
|
629
|
+
```
|
|
630
|
+
|
|
631
|
+
## API Reference
|
|
632
|
+
|
|
633
|
+
### `Router`
|
|
634
|
+
|
|
635
|
+
#### Methods
|
|
636
|
+
|
|
637
|
+
##### `addRoute(methods: string[], path: string, handler: Function): Route`
|
|
638
|
+
|
|
639
|
+
Add a new route to the router.
|
|
640
|
+
|
|
641
|
+
```ts
|
|
642
|
+
router.addRoute(['GET', 'POST'], '/api/users', handler);
|
|
643
|
+
```
|
|
644
|
+
|
|
645
|
+
##### `resolve(req: Request): RouteInfo | null`
|
|
646
|
+
|
|
647
|
+
Resolve a request to route information.
|
|
648
|
+
|
|
649
|
+
```ts
|
|
650
|
+
const routeInfo = router.resolve(req);
|
|
651
|
+
```
|
|
652
|
+
|
|
653
|
+
##### `getCompiledRoute(req: Request, res: Response): CompiledRoute`
|
|
654
|
+
|
|
655
|
+
Get a compiled route ready for execution.
|
|
656
|
+
|
|
657
|
+
```ts
|
|
658
|
+
const compiled = router.getCompiledRoute(req, res);
|
|
659
|
+
await compiled.run();
|
|
660
|
+
```
|
|
661
|
+
|
|
662
|
+
### `Route`
|
|
663
|
+
|
|
664
|
+
#### Methods
|
|
665
|
+
|
|
666
|
+
##### `addMiddleware(middleware: Function | Function[]): Route`
|
|
667
|
+
|
|
668
|
+
Add middleware to the route.
|
|
669
|
+
|
|
670
|
+
```ts
|
|
671
|
+
route.addMiddleware([middleware1, middleware2]);
|
|
672
|
+
route.addMiddleware(singleMiddleware);
|
|
673
|
+
```
|
|
674
|
+
|
|
675
|
+
### Types
|
|
676
|
+
|
|
677
|
+
```ts
|
|
678
|
+
interface Request {
|
|
679
|
+
url: string;
|
|
680
|
+
method: string;
|
|
681
|
+
headers?: Record<string, string>;
|
|
682
|
+
params?: Record<string, string>;
|
|
683
|
+
query?: Record<string, any>;
|
|
684
|
+
body?: any;
|
|
685
|
+
[key: string]: any;
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
interface Response {
|
|
689
|
+
statusCode?: number;
|
|
690
|
+
headers?: Record<string, string>;
|
|
691
|
+
body?: any;
|
|
692
|
+
[key: string]: any;
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
type Middleware = (req: Request, res: Response, next: Function) => Promise<any>;
|
|
696
|
+
```
|
|
697
|
+
|
|
698
|
+
## Performance Tips
|
|
699
|
+
|
|
700
|
+
1. **Route Order** - Place specific routes before generic ones
|
|
701
|
+
2. **Middleware Order** - Put fast middleware before slow ones
|
|
702
|
+
3. **Caching** - Cache route resolution results when possible
|
|
703
|
+
4. **Async Operations** - Use async/await efficiently
|
|
704
|
+
5. **Memory Management** - Clean up middleware state appropriately
|
|
705
|
+
|
|
706
|
+
## Contributing
|
|
707
|
+
|
|
708
|
+
Contributions are welcome! Please feel free to submit a Pull Request.
|
|
709
|
+
|
|
710
|
+
## License
|
|
711
|
+
|
|
712
|
+
MIT
|
|
713
|
+
|
|
714
|
+
## Related Packages
|
|
715
|
+
|
|
716
|
+
- [@devbro/neko-http](https://www.npmjs.com/package/@devbro/neko-http) - HTTP client utilities
|
|
717
|
+
- [@devbro/neko-context](https://www.npmjs.com/package/@devbro/neko-context) - Context management
|
|
718
|
+
- [@devbro/pashmak](https://www.npmjs.com/package/@devbro/pashmak) - Full-stack TypeScript framework
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@devbro/neko-router",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.18",
|
|
4
4
|
"description": "general purpose router for URI to controller selection",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"module": "./dist/index.mjs",
|
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
"scripts": {
|
|
19
19
|
"build": "tsup",
|
|
20
20
|
"test": "vitest run",
|
|
21
|
+
"bench": "vitest bench",
|
|
21
22
|
"test:watch": "vitest watch",
|
|
22
23
|
"test:coverage": "vitest run --coverage",
|
|
23
24
|
"format": "eslint . --fix --ext ts,tsx --report-unused-disable-directives --max-warnings 0 ",
|