Skip to content

Instantly share code, notes, and snippets.

@piyushchauhan2011
Created July 28, 2020 04:30
Show Gist options
  • Save piyushchauhan2011/82d735ded45f670e858b68cd11bf0f0e to your computer and use it in GitHub Desktop.
Save piyushchauhan2011/82d735ded45f670e858b68cd11bf0f0e to your computer and use it in GitHub Desktop.
Deno Server example with routes
import {
serve,
ServerRequest,
} from "https://deno.land/[email protected]/http/server.ts";
import { Status } from "https://deno.land/[email protected]/http/http_status.ts";
class User {
constructor(public name: string, public age: number) {}
}
const user1 = new User("user 1", 23);
const user2 = new User("user 2", 21);
const users = [user1, user2];
async function getUsers(): Promise<User[]> {
return users;
}
async function getUser(id: number): Promise<User | undefined> {
return users[--id];
}
export const enum HttpMethod {
GET = "GET",
POST = "POST",
PUT = "PUT",
PATCH = "PATCH",
DELETE = "DELETE",
}
export type Route = {
method: HttpMethod;
route: string;
handler: (...params: any) => Promise<any>;
};
export type RouteParam = {
idx: number;
paramKey: string;
};
const routes: Route[] = [
{
method: HttpMethod.GET,
route: "/api/user",
handler: getUsers,
},
{
method: HttpMethod.GET,
route: "/api/user/:id",
handler: getUser,
},
];
/**
* Extract all the route params out of the route into an array of RouteParam.
* Example: /api/user/:id => [{ idx: 2, paramKey: 'id' }]
* @param route the configured route with params
*/
export const extractRouteParams: (route: string) => RouteParam[] = (route) =>
route.split("/").reduce((accum: RouteParam[], curr: string, idx: number) => {
if (/:[A-Za-z1-9]{1,}/.test(curr)) {
const paramKey: string = curr.replace(":", "");
const param: RouteParam = { idx, paramKey };
return [...accum, param];
}
return accum;
}, []);
export const routeParamPattern: (route: string) => string = (route) =>
route.replace(/\/\:[^/]{1,}/gi, "/[^/]{1,}").replace(/\//g, "\\/");
/**
* Basic route matcher. Check to see if the method and url match the route
* @param req the received request
* @param route the route being checked against
*/
export const basicRouteMatcher = (req: ServerRequest, route: Route): boolean =>
req.method === route.method && req.url === route.route;
/**
* Attempt to match the route if it has route params. For instance /api/user/12 to match with /api/user/:id.
* @param req the received HTTP request
* @param route the route being checked against
*/
export const routeWithParamsRouteMatcher = (
req: ServerRequest,
route: Route
): boolean => {
const routeMatcherRegEx = new RegExp(`^${routeParamPattern(route.route)}$`);
return (
req.method === route.method &&
route.route.includes("/:") &&
routeMatcherRegEx.test(req.url)
);
};
/**
* Match the received request to a route in the routing table. return the handler function for that route
* @param routes the routing table for the API
* @param req the received request
*/
export const matchRequestToRouteHandler = async (
routes: Route[],
req: ServerRequest
): Promise<void> => {
const headers = new Headers();
headers.append("content-type", "application/json");
let route: Route | undefined = routes.find((route: Route) =>
basicRouteMatcher(req, route)
);
if (route) {
const response: any = await route.handler();
return req.respond({
status: Status.OK,
body: JSON.stringify(response),
headers,
});
}
route = routes.find((route: Route) =>
routeWithParamsRouteMatcher(req, route)
);
if (route) {
// the received route has route params, extract the route params from the route
const routeParamsMap: RouteParam[] = extractRouteParams(route.route);
const routeSegments: string[] = req.url.split("/");
const routeParams: {
[key: string]: string | number;
} = routeParamsMap.reduce(
(accum: { [key: string]: string | number }, curr: RouteParam) => {
return {
...accum,
[curr.paramKey]: routeSegments[curr.idx],
};
},
{}
);
const response: any = await route.handler(...Object.values(routeParams));
return req.respond({
status: Status.OK,
body: JSON.stringify(response),
headers,
});
}
// route could not be found, return 404
return req.respond({
status: Status.NotFound,
body: "Route not found",
headers,
});
};
if (import.meta.main) {
const s = serve({ port: 8000 });
console.log(`server running on port 8000`);
for await (const req: ServerRequest of s) {
matchRequestToRouteHandler(routes, req);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment