Building a nested routes function - a fun exercise
I was reading the Remix documentation and saw a function that they use to programatically define routes. It's pretty cool in that you are given a single function to build your routes, but you can nest these function calls to build and group relative paths together. It got me thinking about how I might implement something like that, so I gave it a shot.
For context, here's what an example of calling this function might look like:
const routes = defineRoutes((route) => {
route("users", "user.js");
route("users/:id", "userShow.js");
route("companies/:id", "companies.js", () => {
route("users", "companyUsers.js");
route("stores", "companyStores.js");
route("stores/:id", "companyStore.js", () => {
route("products", "companyStoreProduct.js");
});
});
route("roles/:id", "roles.js", () => {
route("users", "usersForRole.js");
});
});
console.log(routes);
// output
[
{ path: "users", file: "user.js" },
{ path: "users/:id", file: "userShow.js" },
{ path: "companies/:id", file: "companies.js" },
{ path: "companies/:id/users", file: "companyUsers.js" },
{ path: "companies/:id/stores", file: "companyStores.js" },
{ path: "companies/:id/stores/:id", file: "companyStore.js" },
{ path: "companies/:id/stores/:id/products", file: "companyStoreProduct.js" },
{ path: "roles/:id", file: "roles.js" },
{ path: "roles/:id/users", file: "usersForRole.js" },
];
As you can see, top level routes are stored as-is but as you nest routes deeper and deeper within the route callback function you can build off of the parent path without having to type it every time.
There's a few different concepts you have to deal with while implementing this which makes me think it would make a great interview question to pair on with a candidate.
I'll post my solution below here, but try it out yourself before looking at mine and let me know how it went! Make sure to test it against multiple side-by-side nestings like I did above to make sure your paths are building properly.
function defineRoutes(cb) {
const routes = [];
let parent = "";
function pathWithParent(path) {
if (parent) {
return `${parent}/${path}`;
}
return path;
}
function route(path, file, nestedCb) {
routes.push({ path: pathWithParent(path), file });
if (nestedCb) {
parent = pathWithParent(path);
nestedCb();
parent = parent.slice(0, -(path.length + 1));
}
}
cb(route);
return routes;
}