Open
Description
Important
This issue is dependent on #3971 and is on hold until that's merged.
Use case
As part of the Event Handler implementation (#3251), we need a system to store, organize, and manage routes that are registered through the BaseRouter's HTTP method decorators (get()
, post()
, etc.).
The Route Management System provides the internal infrastructure to:
- Store registered routes in an organized, efficient manner
- Detect route conflicts and provide helpful warnings to developers
- Organize routes for efficient lookup and processing
- Maintain route metadata needed for resolution and debugging
This system acts as the storage layer between route registration (BaseRouter
) and route matching (separate issue), ensuring routes are properly organized and accessible.
Solution/User Experience
Note
The code snippets below are provided as reference only - they are not exhaustive and final implementation might vary.
Core Route Management Architecture
// Internal Route representation
class Route {
readonly id: string; // Unique route identifier
readonly method: string; // 'GET', 'POST', etc.
readonly path: string; // Original path: '/users/:id'
readonly handler: RouteHandler; // The function to execute
readonly registeredAt: Date; // When route was registered
constructor(method: string, path: string, handler: RouteHandler) {
this.id = `${method}:${path}`;
this.method = method.toUpperCase();
this.path = path;
this.handler = handler;
this.registeredAt = new Date();
}
}
// Route storage and organization
class RouteRegistry {
private routes: Map<string, Route> = new Map();
private routesByMethod: Map<string, Route[]> = new Map();
// Add a route to the registry
addRoute(route: Route): void {
// Conflict detection
if (this.routes.has(route.id)) {
this.handleRouteConflict(route);
}
// Store route
this.routes.set(route.id, route);
// Organize by method for efficient lookup
if (!this.routesByMethod.has(route.method)) {
this.routesByMethod.set(route.method, []);
}
this.routesByMethod.get(route.method)!.push(route);
}
// Retrieve routes for matching
getRoutesByMethod(method: string): Route[] {
return this.routesByMethod.get(method.toUpperCase()) || [];
}
getAllRoutes(): Route[] {
return Array.from(this.routes.values());
}
// Development helpers
getRouteCount(): number {
return this.routes.size;
}
getRouteSummary(): RouteSummary {
// Provide debugging information
}
}
Integration with BaseRouter
// BaseRouter uses RouteRegistry for storage
abstract class BaseRouter {
protected routeRegistry = new RouteRegistry();
get(path: string, handler: RouteHandler): void {
const route = new Route('GET', path, handler);
this.routeRegistry.addRoute(route);
}
post(path: string, handler: RouteHandler): void {
const route = new Route('POST', path, handler);
this.routeRegistry.addRoute(route);
}
// ... other HTTP methods
// Expose routes for matching system (next issue)
protected getRegisteredRoutes(): Route[] {
return this.routeRegistry.getAllRoutes();
}
protected getRoutesByMethod(method: string): Route[] {
return this.routeRegistry.getRoutesByMethod(method);
}
}
Route Compilation System
// Convert user-friendly routes to executable patterns
class RouteCompiler {
private static readonly PARAM_PATTERN = /:(\w+)/g;
private static readonly SAFE_CHARS = '-._~()\'!*:@,;=+&$';
private static readonly UNSAFE_CHARS = '%<> \\[\\]{}|^';
static compile(path: string): CompiledRoute {
// Convert /users/:id/posts/:postId to regex with named groups
const paramNames: string[] = [];
const regexPattern = path.replace(
RouteCompiler.PARAM_PATTERN,
(match, paramName) => {
paramNames.push(paramName);
return `(?<${paramName}>[${RouteCompiler.SAFE_CHARS}${RouteCompiler.UNSAFE_CHARS}\\w]+)`;
}
);
// Add anchors for exact matching
const finalPattern = `^${regexPattern}$`;
return {
originalPath: path,
regex: new RegExp(finalPattern),
paramNames,
isDynamic: paramNames.length > 0
};
}
// Validate route patterns for common issues
static validatePattern(path: string): ValidationResult {
// Check for invalid characters, malformed parameters, etc.
const issues: string[] = [];
// Check for malformed parameters
if (path.includes(':') && !RouteCompiler.PARAM_PATTERN.test(path)) {
issues.push('Malformed parameter syntax. Use :paramName format.');
}
// Check for duplicate parameter names
const params = RouteCompiler.extractParamNames(path);
const duplicates = params.filter((param, index) => params.indexOf(param) !== index);
if (duplicates.length > 0) {
issues.push(`Duplicate parameter names: ${duplicates.join(', ')}`);
}
return {
isValid: issues.length === 0,
issues
};
}
private static extractParamNames(path: string): string[] {
const matches = [...path.matchAll(RouteCompiler.PARAM_PATTERN)];
return matches.map(match => match[1]);
}
}
interface CompiledRoute {
originalPath: string;
regex: RegExp;
paramNames: string[];
isDynamic: boolean;
}
Implementation Details
Scope - In Scope:
- Route class: Internal representation of registered routes
- RouteRegistry: Storage and organization of routes
- Route conflict detection: Basic duplicate detection with warnings
- Route organization: Efficient storage by HTTP method
- Development helpers: Route inspection and debugging tools
- Integration points: Clean interface for route matching system
- Route compilation (
:param
→ regex)
Scope - Out of Scope (Future Issues):
- Route pattern matching & execution logic - belongs in Route Matching & Resolution issue
- Route-specific configuration (CORS, middleware, etc.) - future features
Data Flow:
BaseRouter.get() → Route → RouteRegistry.addRoute() → Storage
RouteRegistry.getRoutesByMethod() → Route Matching System → Resolution
Alternative solutions
N/A
Acknowledgment
- This feature request meets Powertools for AWS Lambda (TypeScript) Tenets
- Should this be considered in other Powertools for AWS Lambda languages? i.e. Python, Java, and .NET
Future readers
Please react with 👍 and your use case to help us understand customer demand.
Metadata
Metadata
Assignees
Labels
Type
Projects
Status
On hold