Skip to content

Instantly share code, notes, and snippets.

@arisris
Last active November 1, 2022 16:24
Show Gist options
  • Save arisris/1c7eedc5079e4c98e74bbfafd59b2266 to your computer and use it in GitHub Desktop.
Save arisris/1c7eedc5079e4c98e74bbfafd59b2266 to your computer and use it in GitHub Desktop.
Simple Http Router Using JS Proxy Hack
export type TRouter<Value, Method extends string> =
& {
find: (method: Method, pathname: string) => TMatchedRoute<Value>;
}
& {
[k in Lowercase<Method>]: (
pathname: string,
...values: Value[]
) => TRouter<Value, Method>;
};
type TRoute<H, Method> = [Method, URLPattern, H[]];
type TMatchedRoute<Value> =
| (URLPatternComponentResult & { handlers: Value[] })
| undefined;
export function createRouter<
Value = never,
Method extends string = "ALL" | "GET" | "POST" | "PUT" | "PATCH" | "DELETE",
>(
init: [Method, string, Value[]][] = [],
) {
const add = (
...[method, pathname, handlers]: typeof init[number]
): TRoute<Value, Method> => [
method,
new URLPattern({ pathname }),
handlers,
],
routes: TRoute<Value, Method>[] = init.length > 0
? [...init.map((i) => add(...i))]
: [],
find = (
method: Method,
pathname: string,
): TMatchedRoute<Value> => {
let result: TMatchedRoute<Value>;
for (const [rmethod, pattern, handlers] of routes) {
if (method === rmethod || "ALL" === rmethod) {
const match = pattern.exec({ pathname })?.pathname;
if (match) {
if (result) {
result.handlers.push(...handlers);
} else {
result = { ...match, handlers };
}
}
}
}
return result;
},
get = (
_: never,
prop: string,
receiver: never,
) => ("find" === prop
? find
: (pathname: string, ...handlers: Value[]) =>
routes.push(add(prop as never, pathname, handlers)) && receiver);
return new Proxy({}, { get }) as never as TRouter<Value, Method>;
}
// deno-lint-ignore-file
import { createRouter } from "./router.ts";
// deno-lint-ignore-file no-explicit-any
type RHandler = (
req: Request,
params: Record<string, string | number>,
) => Response | Promise<Response> | void | Promise<void>;
const router = createRouter<RHandler>([
// Initial
["GET", "/", [
() => {
console.log("Step 1");
},
async () => {
console.log("Step 2");
},
async () => {
return new Response("Holaaaa")
},
() => {
console.log("Not Called");
},
]],
]);
// you can do
router.get("/:hello", (req, params) => new Response("Hello "+params.hello))
// sample request resolver
const resolveRouterResult = async (req: Request) => {
const { pathname } = new URL(req.url);
const result = router.find(req.method as never, pathname);
if (result) {
for (const handler of result.handlers) {
let res = await handler(req, result.groups)
if (res !== undefined) {
console.log(await res.text());
break;
}
}
} else {
throw new Error("No route are found!");
}
};
await resolveRouterResult(new Request("https://arisris.com"));
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment