Skip to content

Instantly share code, notes, and snippets.

@idan
Last active May 20, 2023 18:00
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save idan/445b92e96f1c1d9f1ae0b5ecb6496c09 to your computer and use it in GitHub Desktop.
Save idan/445b92e96f1c1d9f1ae0b5ecb6496c09 to your computer and use it in GitHub Desktop.
basic router in typescript
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