Created
August 13, 2023 07:27
-
-
Save KartikBazzad/4abe3c9a8c57f25d495fa9c808237c08 to your computer and use it in GitHub Desktop.
Simple Express similar API Router For ASTRO with middleware support
This file contains 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
// Router class | |
import type { APIContext } from "astro"; | |
type Method = "GET" | "POST" | "PUT" | "DELETE"; | |
type Route = { | |
method: Method; | |
uri: string; | |
callbacks: Array<(context?: APIContext) => any>; | |
}; | |
export class Router { | |
routes: Array<Route> = []; | |
middlewares: Set<(context: APIContext) => any> = new Set(); | |
constructor() { | |
this.routes = []; | |
this.middlewares = new Set(); | |
return this; | |
} | |
private addRoute( | |
method: Method, | |
uri: string, | |
...handlers: Array<(context?: any) => any> | |
) { | |
if (!uri || !handlers.length) | |
throw new Error("uri or callback must be given"); | |
if (typeof uri !== "string") | |
throw new TypeError("typeof uri must be a string"); | |
if (handlers.every((h) => typeof h !== "function")) | |
throw new TypeError("typeof callback must be a function"); | |
const _route = this.routes.find( | |
(i) => i.uri === uri && i.method === method | |
); | |
if (_route) { | |
throw new Error(`the uri ${uri} with ${method} already exists`); | |
} | |
this.routes.push({ method, uri, callbacks: handlers }); | |
return this; | |
} | |
route(path: string, router: Router) { | |
router.routes.forEach(({ callbacks, method, uri }) => { | |
this.addRoute(method, path + uri, ...callbacks); | |
}); | |
return this; | |
} | |
use(mw: (ctx: APIContext) => any) { | |
this.middlewares.add(mw); | |
} | |
get(uri: string, ...handlers: Array<(context?: any) => any>) { | |
this.addRoute("GET", uri, ...handlers); | |
return this; | |
} | |
post(uri: string, ...handlers: Array<(context?: any) => any>) { | |
this.addRoute("POST", uri, ...handlers); | |
return this; | |
} | |
put(uri: string, ...handlers: Array<(context?: any) => any>) { | |
this.addRoute("PUT", uri, ...handlers); | |
return this; | |
} | |
delete(uri: string, ...handlers: Array<(context?: any) => any>) { | |
this.addRoute("DELETE", uri, ...handlers); | |
return this; | |
} | |
private async handler(route: Route, context: APIContext) { | |
for (const middleware of this.middlewares) { | |
const response = await middleware.call(this, context); | |
if (response instanceof Response) { | |
return response; | |
} | |
} | |
// Run middlewares specific to this route | |
for (const callback of route.callbacks) { | |
const response = await callback.call(this, context); | |
if (response instanceof Response) { | |
return response; | |
} | |
} | |
return route.callbacks.at(-1)?.call(this, context); | |
} | |
async init(context: APIContext) { | |
const path = context.url.pathname; | |
const route = this.routes.find((_route) => { | |
return path.includes(_route.uri); | |
}); | |
if (route) { | |
if (route.method.toLowerCase() === context.request.method.toLowerCase()) { | |
return await this.handler(route, context); | |
} else { | |
return new Response("Not Found, 404", { status: 200 }); | |
} | |
} | |
return new Response("Not Found, 404", { status: 200 }); | |
} | |
} | |
<------------> | |
//[...path].ts | |
import { testRouter } from "@/router/route"; | |
import { Router } from "@/router/router"; | |
import type { APIContext, APIRoute } from "astro"; | |
const router = new Router(); | |
router.use((ctx) => { | |
console.log("middleware"); | |
return new Response("unauthroized", { status: 500 }); | |
}); | |
router.get( | |
"/hello", | |
() => { | |
console.log("Route Middleware"); | |
}, | |
({ url }: APIContext) => { | |
return new Response("Hello World", { status: 200 }); | |
} | |
); | |
router.route("/test-router", testRouter); | |
router.get("/nice", () => { | |
return new Response("Yes", { status: 200 }); | |
}); | |
export const all: APIRoute = async (ctx) => await router.init(ctx); |
Replace trie router with path-to-regex
import type { APIContext } from "astro";
import { pathToRegexp, Key } from "path-to-regexp";
type Method = "GET" | "POST" | "PUT" | "DELETE";
type Route = {
method: Method;
uri: string;
regex: RegExp;
keys: Key[];
callbacks: Array<(context?: APIContext) => any>;
};
// class TrieNode {
// children: Record<string, TrieNode> = {};
// route: Route | null = null;
// }
export default class Router {
routes: Array<Route> = [];
middlewares: Set<(context: APIContext) => any> = new Set();
basePath: string = "/";
constructor() {
this.routes = [];
this.middlewares = new Set();
return this;
}
setBasePath(path: string) {
this.basePath = path;
}
private addRoute(
method: Method,
uri: string,
...handlers: Array<(context?: any) => any>
) {
if (!uri || !handlers.length)
throw new Error("uri or callback must be given");
if (typeof uri !== "string")
throw new TypeError("typeof uri must be a string");
if (handlers.every((h) => typeof h !== "function"))
throw new TypeError("typeof callback must be a function");
const keys: Key[] = [];
const fullPath = this.basePath + uri; // Prepend the base path
const regex = pathToRegexp(fullPath, keys);
const route = { method, uri: fullPath, regex, keys, callbacks: handlers };
this.routes.push(route);
return this;
}
route(path: string, router: Router) {
router.routes.forEach(({ callbacks, method, uri }) => {
this.addRoute(method, path + uri, ...callbacks);
});
return this;
}
use(mw: (ctx: APIContext) => any) {
this.middlewares.add(mw);
}
get(uri: string, ...handlers: Array<(context?: any) => any>) {
this.addRoute("GET", uri, ...handlers);
return this;
}
post(uri: string, ...handlers: Array<(context?: any) => any>) {
this.addRoute("POST", uri, ...handlers);
return this;
}
put(uri: string, ...handlers: Array<(context?: any) => any>) {
this.addRoute("PUT", uri, ...handlers);
return this;
}
delete(uri: string, ...handlers: Array<(context?: any) => any>) {
this.addRoute("DELETE", uri, ...handlers);
return this;
}
private async handler(route: Route, context: APIContext): Promise<Response> {
for (const middleware of this.middlewares) {
const response = await middleware.call(this, context);
if (response instanceof Response) {
return response;
}
}
// Run middlewares specific to this route
for (const callback of route.callbacks) {
const response = await callback.call(this, context);
if (response instanceof Response) {
return response;
}
}
return route.callbacks.at(-1)?.call(this, context);
}
async init(context: APIContext): Promise<Response> {
const path = context.url.pathname;
console.log(path);
const matchingRoute = this.routes.find((route) => {
if (route.method.toLowerCase() !== context.request.method.toLowerCase()) {
return false;
}
const match = path.match(route.regex);
console.log({ [route.uri]: route });
console.log(match);
if (match) {
// context.params = {};
// for (let i = 0; i < route.keys.length; i++) {
// context.params[route.keys[i].name] = match[i + 1];
// }
return true;
}
return false;
});
console.log(matchingRoute);
if (matchingRoute) {
return await this.handler(matchingRoute, context);
} else {
return new Response("Not Found, 404", { status: 404 });
}
}
}
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
UPDATE added trie router