-
-
Save hharzer/2d05281855f17e735ac4f764268e1b0c to your computer and use it in GitHub Desktop.
Micro router for Cloudflare Workers
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
const router = require('./router'); | |
router.setRoutes([ | |
{ | |
path: '/ping', | |
method: 'GET', | |
handler: ping | |
}, | |
{ | |
path: '/hello/:name', | |
method: 'GET', | |
handler: sayHello | |
}, | |
]); | |
addEventListener('fetch', event => { | |
event.respondWith(router.onRequest(event)) | |
}); | |
function ping() { | |
return new Response('PONG!', { | |
status: 200, | |
}); | |
} | |
function sayHello (request, params) { | |
return new Response('Hello ' + params.name, { | |
status: 200, | |
}); | |
} |
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
const router = { | |
routes: [] | |
}; | |
router.onRequest = async (event) => { | |
try { | |
const match = matchRoute(event.request, router.routes); | |
if (match) { | |
return await match.route.handler(event.request, match.params); | |
} else { | |
return new Response('No soup for you!', { | |
status: 404, | |
}); | |
} | |
} catch (error) { | |
return new Response(error.toString(), { | |
status: 500, | |
}); | |
} | |
} | |
// adds the routes config to the router | |
router.setRoutes = (routes) => { | |
router.routes = routes.map((route) => { | |
// we create the path components of the routes in advance so these are cached and doesn't have to be computed on each request | |
route.pathComponents = route.path.split('/').map((component) => { | |
const isParam = component.startsWith(':'); | |
const isOptional = component.startsWith('?'); | |
const paramName = isParam || isOptional ? component.substr(1) : null; | |
return { | |
string: component, | |
isParam, | |
isOptional, | |
paramName | |
} | |
}); | |
return route; | |
}); | |
} | |
// matches a request with a route and returns a match object | |
function matchRoute (request, routes) { | |
const url = new URL(request.url); | |
const requestPath = url.pathname; | |
for (const route of routes) { | |
if (route.method.toLowerCase() === request.method.toLowerCase()) { | |
const params = matchPath(route, requestPath); | |
if (params) { | |
return { | |
route, | |
params | |
}; | |
} | |
} | |
} | |
} | |
// matches paths and returns an object with the values of the parameters | |
function matchPath (route, requestPath) { | |
// If it's identical we simply return an empty object because there can be no params | |
if (route.path === requestPath) return {}; | |
// We compare components of the two paths. | |
// We've already created the components of the router path when the worker was initialized | |
const requestPathComponents = requestPath.split('/'); | |
const params = {}; | |
for (var i = 0; i < route.pathComponents.length; i++) { | |
const component = route.pathComponents[i]; | |
if (component.string !== requestPathComponents[i]) { | |
// TODO: implement optional parameters | |
if (component.isParam) { | |
params[component.paramName] = requestPathComponents[i]; | |
} else { | |
// if one component doesn't match and it's not a param then we return undefined and stop matching this path | |
return; | |
} | |
} | |
} | |
return params; | |
} | |
module.exports = router; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment