Last active
May 20, 2023 18:00
-
-
Save idan/445b92e96f1c1d9f1ae0b5ecb6496c09 to your computer and use it in GitHub Desktop.
basic router in typescript
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
export type Route = { | |
method: "*" | "GET" | "POST" | "PUT" | "PATCH" | "DELETE" | "HEAD" | "OPTIONS" | "CONNECT" | "TRACE" | |
path: string | |
regexp: RegExp | |
handler: (request: Request, route: MatchedRoute) => Promise<Response> | |
} | |
export type MatchedRoute = Route & { | |
url: URL | |
} | |
// excellent regex from kwhitely/itty-router | |
// it takes a path and turns it into a regex for later matching | |
function pathToRegexp(path: string): RegExp { | |
return RegExp(`^${path | |
.replace(/(\/?)\*/g, '($1.*)?') // trailing wildcard | |
.replace(/(\/$)|((?<=\/)\/)/, '') // remove trailing slash or double slash from joins | |
.replace(/(:(\w+)\+)/, '(?<$2>.*)') // greedy params | |
.replace(/:(\w+)(\?)?(\.)?/g, '$2(?<$1>[^/]+)$2$3') // named params | |
.replace(/\.(?=[\w(])/, '\\.') // dot in path | |
.replace(/\)\.\?\(([^\[]+)\[\^/g, '?)\\.?($1(?<=\\.)[^\\.') // optional image format | |
}/*$`) | |
} | |
export class Router { | |
routes: Route[] = [] | |
add(method: Route["method"], path: Route["path"], handler: Route["handler"]) { | |
this.routes.push({ | |
method, | |
path, | |
handler, | |
regexp: pathToRegexp(path) | |
}) | |
} | |
match(request: Request, url: URL): Route | undefined { | |
return this.routes | |
.filter(route => route.method === request.method || route.method === "*") | |
.find(route => route.regexp.test(url.pathname)) | |
} | |
async handle(request: Request): Promise<Response> { | |
let response, match, url = new URL(request.url) | |
const route = this.match(request, url) | |
if (route) { | |
return route.handler(request, {...route, url}) | |
} else { | |
return new Response(null, { status: 404 }) | |
} | |
} | |
} | |
// Use it like... | |
const router = new Router() | |
router.add("GET", "/users/:username", async (request, route) => { | |
console.log(route.regexp, route.url.pathname) | |
const username = route.regexp.exec(route.url.pathname)?.groups?.username | |
return new Response(`Matched ${route.path}, Hello ${username}!`) | |
}) | |
router.add("POST", "/users", async (request, route) => { | |
const fd = await request.formData() | |
return new Response(`Matched ${route.path}:\n${JSON.stringify(Array.from(fd.entries()))}`) | |
}) | |
router.add("*", "/foo", async (request, route) => { | |
return new Response("FOooooo!") | |
}) | |
// then somewhere where you have a request... | |
router.handle(request) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment