Clone
export class Router {
constructor() {
this.routes = [];
}
addRoute(method, pattern, handler) {
this.routes.push({ method, pattern, handler });
}
get(pattern, handler) {
this.addRoute('GET', pattern, handler);
}
post(pattern, handler) {
this.addRoute('POST', pattern, handler);
}
put(pattern, handler) {
this.addRoute('PUT', pattern, handler);
}
delete(pattern, handler) {
this.addRoute('DELETE', pattern, handler);
}
all(pattern, handler) {
this.addRoute('*', pattern, handler);
}
async handle(request, env, ctx) {
const url = new URL(request.url);
const path = url.pathname;
const method = request.method;
for (const route of this.routes) {
if (route.method !== '*' && route.method !== method) continue;
const params = this.matchRoute(route.pattern, path);
if (params) {
request.params = params;
request.query = Object.fromEntries(url.searchParams);
return await route.handler(request, env, ctx);
}
}
return new Response('Not Found', { status: 404 });
}
matchRoute(pattern, path) {
if (pattern === '*') return {};
// Handle wildcard patterns like /api/*
if (pattern.endsWith('/*')) {
const base = pattern.slice(0, -2);
if (path.startsWith(base + '/') || path === base) {
return {};
}
return null;
}
// Handle exact matches
if (pattern === path) return {};
// Handle parameterized routes like /:slug
const patternParts = pattern.split('/');
const pathParts = path.split('/');
if (patternParts.length !== pathParts.length) return null;
const params = {};
for (let i = 0; i < patternParts.length; i++) {
if (patternParts[i].startsWith(':')) {
const paramName = patternParts[i].slice(1);
params[paramName] = pathParts[i];
} else if (patternParts[i] !== pathParts[i]) {
return null;
}
}
return params;
}
}