Readme

Decorator Router

Fair simple decorator based router, with GET/POST and middleware support.

demo

live demo: https://yieldray-decorator_router_demo.web.val.run/

import { get, post, all, use, registered, handler, type Context } from "https://esm.town/v/yieldray/decorator_router"; import { parseBearerAuth, transformResponse } from "https://esm.sh/serve-router@1.1.0/utils"; interface User { id: number; name: string; } const users: User[] = [ { id: 0, name: "Alice" }, { id: 1, name: "Ray" }, ]; class _Server { /** * Decorator @get: Parses URLSearchParams into an object as the first parameter. */ @get("/users") getAllUsers() { return users; // Automatically wrapped in a Response.json } @get("/getUserByName") // GET /getUserByName?name=Alice getUserByName({ name }: Record<string, string>) { const user = users.find((u) => u.name === name); if (user) { return user; // Automatically wrapped as Response.json(user) } // Optionally, manually return a Response object return Response.json({ error: "not found" }, { status: 404 }); } @get("/user/:id") // GET /user/123 user(_: unknown, { params: { id } }: Context) { return users.find((u) => u.id === Number(id)); } /** * Decorator @post: Parses the request body into an object as the first parameter. */ @post("/user") // POST /user async createUser(user: User) { if (users.find((u) => u.id === user.id)) { return { error: "already exists!" }; } await users.push(user); // Assume insertion into a database return { ok: true, users }; } @post("/user/:id") // POST /user/123 async updateUser(user: User, { params: { id }, request }: Context) { const token = parseBearerAuth(request.headers.get("Authorization")!); // Additional logic here... } @all("/") home({ request }: { request: Request }) { return { registered, method: request.method, url: request.url, headers: Object.fromEntries(request.headers.entries()), }; } @use("/*") async corsMiddleware({ next, request }: Context) { const resp = await next(); return transformResponse(resp, { headers: { "Access-Control-Allow-Origin": request.headers.get("origin") || "*", }, }); } } // For Deno: Deno.serve(handler); // For val.town: export default handler;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
import ServeRouter from "https://esm.sh/serve-router@1.1.0";
import { parseRequestBody } from "https://esm.sh/serve-router@1.1.0/utils";
export const app = ServeRouter({
onError(error) {
console.log(error);
},
});
const _registered: Array<{ method: string; path: string }> = [];
export const registered: Readonly<typeof _registered> = _registered;
export const handler = (req: Request) => app.fetch(req);
export interface Context {
request: Request;
params: Record<string, string | undefined>;
next: () => Promise<Response>;
}
export function get(path: string) {
return (
target: (query: Record<string, string>, ctx: Context) => unknown,
_context: ClassMethodDecoratorContext,
) => {
_registered.push({ method: "GET", path });
app.get(path, async (request: Request, { params, next }) => {
const resp = await target(Object.fromEntries(new URL(request.url).searchParams.entries()), {
request,
params,
next,
});
if (resp instanceof Response) return resp;
if (resp == undefined) return next();
return Response.json(resp);
});
};
}
export function post(path: string) {
return (target: (body: any, ctx: Context) => unknown, _context: ClassMethodDecoratorContext) => {
_registered.push({ method: "POST", path });
app.post(path, async (request: Request, { params, next }) => {
const resp = await target(await parseRequestBody(request, "application/json"), { request, params, next });
if (resp instanceof Response) return resp;
if (resp == undefined) return next();
return Response.json(resp);
});
};
}
export function all(path: string) {
return (target: (ctx: Context) => unknown, _context: ClassMethodDecoratorContext) => {
_registered.push({ method: "*", path });
app.all(path, async (request: Request, { params, next }) => {
const resp = await target({ request, params, next });
if (resp instanceof Response) return resp;
if (resp == undefined) return next();
return Response.json(resp);
});
};
}
export function use(path: string) {
return (target: (ctx: Context) => unknown, _context: ClassMethodDecoratorContext) => {
app.use(path, async (request: Request, { params, next }) => {
const resp = await target({ request, params, next });
if (resp instanceof Response) return resp;
if (resp == undefined) return next();
return Response.json(resp);
});
};
}
Val Town is a social website to write and deploy JavaScript.
Build APIs and schedule functions from your browser.
Comments
2
stevekrouse avatar
yieldray avatar

Thanks @stevekrouse! However, I've noticed that currently, decorators can only be applied to methods. If we had parameter decorators and function decorators, we could do much more!

v1
May 16, 2024