Created
July 28, 2020 04:30
-
-
Save piyushchauhan2011/82d735ded45f670e858b68cd11bf0f0e to your computer and use it in GitHub Desktop.
Deno Server example with routes
This file contains hidden or 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
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