Last active
April 30, 2024 09:08
-
-
Save PierBover/4da745b601d30a6bb850c297b0c0c579 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
Super small and efficient. No Regex that consumes precious CPU time.
Needs a bit more work but if you only need simple routes with params should be fine.