hono-file-router
Version:
File-based router for easier route management for hono.
96 lines (90 loc) • 3.49 kB
JavaScript
;
var fs = require('fs');
var path = require('path');
var hono = require('hono');
var factory = require('hono/factory');
const httpMethods = ["GET", "POST", "PUT", "DELETE", "OPTIONS", "PATCH", "HEAD", "CONNECT", "TRACE", "index"];
function fileRouterScanner(cwd, initpath) {
const normalFolders = directoryScanner(`${cwd}/${initpath}`);
const resolvedPath = cleanupPath(path.resolve(initpath));
normalFolders.push({ name: ".", path: resolvedPath, directory: true });
const routes = [];
const errors = [];
normalFolders.forEach((folder) => {
if (!folder.directory) return;
const methodAssignment = {};
httpMethods.forEach((methodOrIndex) => {
const pathToFile = `${folder.path}/${folder.name}/${methodOrIndex}`;
const jsExists = fileExists(`${pathToFile}.js`);
const tsExists = fileExists(`${pathToFile}.ts`);
if (jsExists && tsExists) errors.push({ message: "Both js and ts files exist for that method. Only one must exist.", data: pathToFile });
if (jsExists || tsExists) {
const route = `${folder.path.replaceAll(resolvedPath, "")}/${folder.name === "." ? "" : folder.name}`;
methodAssignment[methodOrIndex] = {
file: cleanupPath(`${pathToFile}.${jsExists ? "js" : "ts"}`),
route: replaceParams(route),
method: methodOrIndex
};
}
});
if (methodAssignment.index && Object.keys(methodAssignment).length !== 1)
errors.push({ message: "An index file exists along with method files. Please remove either to avoid confusion.", data: methodAssignment });
routes.push(methodAssignment);
});
function replaceParams(path2) {
return path2.replace(/\[([^\]]+)\]/g, ":$1");
}
if (errors.length) throw Error(JSON.stringify(errors, null, " "));
return routes;
}
function fileExists(file) {
return fs.existsSync(file);
}
function directoryScanner(path2) {
const folders = fs.readdirSync(path2, { recursive: true, withFileTypes: true });
return folders.map((folders2) => ({ name: folders2.name, path: cleanupPath(folders2.parentPath), directory: folders2.isDirectory() }));
}
function cleanupPath(path2) {
return path2.replaceAll("/./", "/").replaceAll("\\", "/");
}
function getCwd() {
return process.cwd();
}
const createHandler = factory.createFactory().createHandlers;
async function createFolderRoute({ path } = {}) {
if (!path) throw Error("path is required");
const cwd = getCwd();
const routes = fileRouterScanner(cwd, path);
const flattenedRoutes = [];
routes.forEach(
(route) => httpMethods.forEach((method) => {
const routeMethod = route[method];
if (routeMethod) flattenedRoutes.push(routeMethod);
})
);
const promises = [];
const app = new hono.Hono();
flattenedRoutes.forEach((route) => {
if (route.method === "index") {
promises.push(
import(`file://${route.file}`).then((importedRoutes) => {
httpMethods.forEach((method) => {
if (method !== "index") {
if (importedRoutes[method]) app.on(method, route.route, ...importedRoutes[method]);
}
});
})
);
} else {
promises.push(
import(`file://${route.file}`).then(async (importedRoute) => {
app.on(route.method, route.route, ...importedRoute.default);
})
);
}
});
await Promise.all(promises);
return app;
}
exports.createFolderRoute = createFolderRoute;
exports.createHandler = createHandler;