UNPKG

@psenger/express-auto-router

Version:

A dynamic route composition system for Express.js applications that automatically discovers and mount routes and middleware based on your file system structure. Inspired by Next.js routing conventions.

1 lines 25.1 kB
{"version":3,"file":"index.mjs","sources":["../src/index.js"],"sourcesContent":["import path from 'path'\nimport { readdirSync, statSync } from 'fs'\n\nconst PLACEHOLDER_REGEX = /\\[(.+?)\\]/\nconst PLACEHOLDER_GLOBAL_REGEX = /\\[(.+?)\\]/g\n\n/**\n * Checks if a directory entry is a middleware file\n *\n * @param {Object} entry - The directory entry to check (fs.Dirent object)\n * @returns {boolean} - True if the entry is a file named '_middleware.js'\n *\n * @example\n * // With a file entry for '_middleware.js'\n * const middlewareEntry = { isFile: () => true, name: '_middleware.js' };\n * isMiddlewareFile(middlewareEntry); // Returns: true\n *\n * @example\n * // With a directory entry\n * const dirEntry = { isFile: () => false, name: '_middleware.js' };\n * isMiddlewareFile(dirEntry); // Returns: false\n *\n * @example\n * // With a different file\n * const otherFileEntry = { isFile: () => true, name: 'index.js' };\n * isMiddlewareFile(otherFileEntry); // Returns: false\n */\nexport function isMiddlewareFile(entry) {\n return entry.isFile() && entry.name === '_middleware.js'\n}\n\n/**\n * Ensures a value is always an array by wrapping non-array values\n *\n * @param {*} ary - The value to convert to an array\n * @returns {Array} - Wraps the value in an array, or if the input was an array already it will return it as is.\n *\n * @example\n * // With a non-array value\n * autoBox(5); // Returns: [5]\n *\n * @example\n * // With an array value\n * autoBox([1, 2, 3]); // Returns: [1, 2, 3]\n *\n * @example\n * // With null or undefined\n * autoBox(null); // Returns: [null]\n * autoBox(undefined); // Returns: [undefined]\n *\n * @example\n * // With an object\n * autoBox({ key: 'value' }); // Returns: [{ key: 'value' }]\n */\nexport function autoBox(ary) {\n return Array.isArray(ary) ? ary : [ary]\n}\n\n/**\n * Converts URL placeholder syntax [param] to Express parameter syntax :param\n *\n * @param {string} urlPath - The URL path containing placeholders\n * @returns {string} - The URL path with Express-style parameters\n *\n * @example\n * // With single placeholder\n * replaceUrlPlaceholders('/users/[id]'); // Returns: '/users/:id'\n *\n * @example\n * // With multiple placeholders\n * replaceUrlPlaceholders('/users/[id]/posts/[postId]'); // Returns: '/users/:id/posts/:postId'\n *\n * @example\n * // With no placeholders\n * replaceUrlPlaceholders('/users/list'); // Returns: '/users/list'\n *\n * @example\n * // With nested/complex placeholders\n * replaceUrlPlaceholders('/products/[category]/[id]/reviews/[reviewId]');\n * // Returns: '/products/:category/:id/reviews/:reviewId'\n */\nexport function replaceUrlPlaceholders(urlPath) {\n return urlPath.replace(\n PLACEHOLDER_GLOBAL_REGEX,\n (match, variable) => `:${variable}`\n )\n}\n\n/**\n * Checks if a URL path contains a placeholder\n *\n * @param {string} urlPath - The URL path to check\n * @returns {boolean} - True if the path contains a placeholder\n *\n * @example\n * // With placeholder\n * isPlaceholder('/users/[id]'); // Returns: true\n *\n * @example\n * // With multiple placeholders\n * isPlaceholder('/users/[id]/posts/[postId]'); // Returns: true\n *\n * @example\n * // Without placeholder\n * isPlaceholder('/users/list'); // Returns: false\n *\n * @example\n * // With square brackets in a different context (not a placeholder)\n * isPlaceholder('/users/list[all]'); // Returns: true (matches the regex pattern)\n */\nexport function isPlaceholder(urlPath) {\n return PLACEHOLDER_REGEX.test(urlPath)\n}\n\n/**\n * Validates if a path is a non-empty string\n *\n * @param {string} path - The path to validate\n * @throws {Error} If path is not a string or is empty\n *\n * @example\n * // With valid path\n * validatePath('/api/users'); // No error thrown\n *\n * @example\n * // With empty string\n * try {\n * validatePath('');\n * } catch (error) {\n * console.error(error.message); // Outputs: 'Invalid path provided'\n * }\n *\n * @example\n * // With null value\n * try {\n * validatePath(null);\n * } catch (error) {\n * console.error(error.message); // Outputs: 'Invalid path provided'\n * }\n *\n * @example\n * // With non-string value\n * try {\n * validatePath(123);\n * } catch (error) {\n * console.error(error.message); // Outputs: 'Invalid path provided'\n * }\n */\nexport function validatePath(path) {\n if (typeof path !== 'string' || !path) {\n throw new Error('Invalid path provided')\n }\n}\n\n/**\n * Retrieves and sorts middleware functions that match a given path\n * Finds all entries in the dictionary where the given path starts with the dictionary key,\n * sorts them by key length (shortest first), and returns the flattened array of middleware functions\n *\n * @param {Object<string, Function|Array<Function>>} dictionary - Dictionary of paths to middleware functions\n * @param {string} path - The path to match\n * @returns {Array<Function>} - Array of middleware functions that apply to the path, ordered by path specificity\n *\n * @example\n * // With matching paths\n * const dict = {\n * '/api/': [authMiddleware],\n * '/api/users/': [userMiddleware]\n * };\n * dictionaryKeyStartsWithPath(dict, '/api/users/profile');\n * // Returns: [authMiddleware, userMiddleware] (in order from least to most specific)\n *\n * @example\n * // With no matching paths\n * const dict = {\n * '/api/': [authMiddleware],\n * '/api/users/': [userMiddleware]\n * };\n * dictionaryKeyStartsWithPath(dict, '/admin/');\n * // Returns: []\n *\n * @example\n * // With mixed array and single function values\n * const dict = {\n * '/api/': [authMiddleware, logMiddleware],\n * '/api/users/': userMiddleware\n * };\n * dictionaryKeyStartsWithPath(dict, '/api/users/');\n * // Returns: [authMiddleware, logMiddleware, userMiddleware]\n *\n * @example\n * // With null or undefined values in the dictionary (they are filtered out)\n * const dict = {\n * '/api/': [authMiddleware, null],\n * '/api/users/': undefined\n * };\n * dictionaryKeyStartsWithPath(dict, '/api/users/');\n * // Returns: [authMiddleware]\n */\nexport function dictionaryKeyStartsWithPath(dictionary, path) {\n return Object.entries(dictionary)\n .filter(([key]) => path.startsWith(key))\n .sort(([aKey], [bKey]) => aKey.length - bKey.length)\n .flatMap(([, value]) => (Array.isArray(value) ? value : [value]))\n .filter(Boolean)\n}\n\n/**\n * Creates a curried router object with pre-configured URL path and middleware\n * Returns a proxy to the original router that applies the given URL path and middleware functions\n * to all HTTP method calls (get, post, put, etc.) automatically\n *\n * @param {Object} router - Express router instance\n * @param {string} urlPath - The URL path to be curried\n * @param {...Function} initialMiddleWareFunctions - Initial middleware functions to be applied (rest parameter, accepts multiple functions)\n * @returns {Object} - Curried router proxy with pre-configured path and middleware\n *\n * @example\n * // Basic usage with a single middleware function\n * const router = express.Router();\n * const curriedRouter = curryObjectMethods(router, '/users', authMiddleware);\n * curriedRouter.get((req, res) => res.json({}));\n * // Equivalent to: router.get('/users', authMiddleware, (req, res) => res.json({}));\n *\n * @example\n * // With multiple middleware functions\n * const curriedRouter = curryObjectMethods(router, '/posts', authMiddleware, logMiddleware);\n * curriedRouter.post((req, res) => res.status(201).json({}));\n * // Equivalent to: router.post('/posts', authMiddleware, logMiddleware, (req, res) => res.status(201).json({}));\n *\n * @example\n * // With no middleware\n * const curriedRouter = curryObjectMethods(router, '/public');\n * curriedRouter.get((req, res) => res.send('Hello'));\n * // Equivalent to: router.get('/public', (req, res) => res.send('Hello'));\n *\n * @example\n * // Accessing the original router object\n * const curriedRouter = curryObjectMethods(router, '/api');\n * const originalRouter = curriedRouter._getOriginalObject();\n * // originalRouter is the router instance passed in the first parameter\n */\nexport function curryObjectMethods(\n router,\n urlPath,\n ...initialMiddleWareFunctions\n) {\n const originalRouter = router\n const handler = {\n get(target, prop) {\n const originalHttpMethod = target[prop]\n if (typeof originalHttpMethod === 'function') {\n return (...remainingFns) => {\n const allFns = [...initialMiddleWareFunctions, ...remainingFns]\n return originalHttpMethod.call(target, urlPath, ...allFns)\n }\n }\n return originalHttpMethod\n }\n }\n const curriedRouterObject = new Proxy(router, handler)\n curriedRouterObject._getOriginalObject = () => originalRouter\n return curriedRouterObject\n}\n\n/**\n * Builds a dictionary of middleware functions from a directory structure\n * Recursively scans the given directory for '_middleware.js' files and builds a dictionary\n * mapping URL paths to their corresponding middleware functions\n *\n * @param {string} basePath - Base filesystem path to start scanning\n * @param {string} baseURL - Base URL path for the routes\n * @param {Object} [options=undefined] - Options that can be passed to all controllers when they are executed.\n * @returns {Object<string, Array<Function>>} Dictionary where keys are URL paths and values are arrays of middleware functions\n *\n * @example\n * // Basic directory structure with middleware\n * // ./src/routes/_middleware.js -> exports a global middleware\n * // ./src/routes/users/_middleware.js -> exports a users-specific middleware\n * const middlewares = buildMiddlewareDictionary('./src/routes', '/api');\n * // Returns: {\n * // '/api/': [globalMiddleware],\n * // '/api/users/': [usersMiddleware]\n * // }\n *\n * @example\n * // With dynamic route parameters\n * // ./src/routes/users/[id]/_middleware.js -> exports a user-specific middleware\n * const middlewares = buildMiddlewareDictionary('./src/routes', '/api');\n * // Returns: {\n * // '/api/': [globalMiddleware],\n * // '/api/users/': [usersMiddleware],\n * // '/api/users/:id/': [userSpecificMiddleware]\n * // }\n *\n * @example\n * // With middleware exporting multiple functions\n * // ./src/routes/_middleware.js -> exports [authMiddleware, logMiddleware]\n * const middlewares = buildMiddlewareDictionary('./src/routes', '/api');\n * // Returns: {\n * // '/api/': [authMiddleware, logMiddleware]\n * // }\n *\n * @example\n * // With middleware exporting a single function\n * // ./src/routes/_middleware.js -> exports singleMiddleware (not in an array)\n * const middlewares = buildMiddlewareDictionary('./src/routes', '/api');\n * // Returns: {\n * // '/api/': [singleMiddleware]\n * // }\n */\nexport function buildMiddlewareDictionary(basePath, baseURL, options) {\n const dictionary = {}\n const traverseDirectory = (currentPath, currentURL) => {\n const dirEntries = readdirSync(currentPath, { withFileTypes: true })\n dirEntries.forEach((entry) => {\n const entryPath = path.join(currentPath, entry.name)\n let entryURL = `${currentURL}/${entry.name}`\n if (entry.isDirectory()) {\n const match = entry.name.match(/\\[(.+?)\\]/)\n if (match) {\n const pathVariable = `:${match[1]}`\n entryURL = `${currentURL}/${entry.name.replace(/\\[(.+?)\\]/, pathVariable)}`\n }\n traverseDirectory(entryPath, entryURL)\n } else if (isMiddlewareFile(entry)) {\n try {\n const middleware = require(entryPath)(options)\n const middlewareURL = entryURL.replace('_middleware.js', '')\n dictionary[middlewareURL] = autoBox(middleware)\n } catch (e) {\n console.error(e)\n console.error(`Failed to load ${entryPath}`)\n }\n }\n })\n }\n traverseDirectory(basePath, baseURL)\n return dictionary\n}\n\n/**\n * Builds an array of route mappings from a directory structure\n * Recursively scans the given directory for 'index.js' files and builds an array of\n * URL paths and their corresponding file paths, converting directory placeholders to Express params\n *\n * @param {string} basePath - Base filesystem path to start scanning\n * @param {string} baseURL - Base URL path for the routes\n * @returns {Array<Array<string>>} Array of tuples where first element is URL path and second is file path\n *\n * @example\n * // Basic directory structure\n * // ./src/routes/users/index.js\n * // ./src/routes/posts/index.js\n * const routes = buildRoutes('./src/routes', '/api');\n * // Returns: [\n * // ['/api/users/', './src/routes/users/index.js'],\n * // ['/api/posts/', './src/routes/posts/index.js']\n * // ]\n *\n * @example\n * // With dynamic route parameters\n * // ./src/routes/users/[id]/index.js\n * const routes = buildRoutes('./src/routes', '/api');\n * // Returns: [\n * // ['/api/users/:id/', './src/routes/users/[id]/index.js']\n * // ]\n *\n * @example\n * // With nested dynamic routes\n * // ./src/routes/users/[userId]/posts/[postId]/index.js\n * const routes = buildRoutes('./src/routes', '/api');\n * // Returns: [\n * // ['/api/users/:userId/posts/:postId/', './src/routes/users/[userId]/posts/[postId]/index.js']\n * // ]\n *\n * @example\n * // With root route\n * // ./src/routes/index.js\n * const routes = buildRoutes('./src/routes', '/api');\n * // Returns: [\n * // ['/api/', './src/routes/index.js']\n * // ]\n */\nexport function buildRoutes(basePath, baseURL) {\n const result = []\n const queue = [[basePath, baseURL + '/']]\n while (queue.length > 0) {\n const [currentPath, currentURL] = queue.shift()\n const files = readdirSync(currentPath)\n const indexFile = files.find((file) => file === 'index.js')\n if (indexFile) {\n const indexFilePath = path.join(currentPath, indexFile)\n result.push([currentURL, indexFilePath])\n }\n const directories = files\n .filter((file) => statSync(path.join(currentPath, file)).isDirectory())\n .map((dir) => path.join(currentPath, dir))\n directories.forEach((dir) => {\n const dirName = path.basename(dir)\n let entryURL = `${currentURL}${dirName}/`\n if (isPlaceholder(dirName)) {\n entryURL = `${currentURL}${replaceUrlPlaceholders(dirName)}/`\n }\n queue.push([dir, entryURL])\n })\n }\n return result\n}\n\n/**\n * Composes Express routes from a directory structure with middleware support.\n * This is the main function that processes route mappings, builds middleware dictionaries,\n * and configures an Express router with all discovered routes and middleware.\n *\n * @param {Object} express - The Express module instance\n * @param {Array<Object>} routeMappings - Array of route mapping configurations\n * @param {string} routeMappings[].basePath - Base filesystem path to start scanning\n * @param {string} routeMappings[].baseURL - Base URL path for the routes\n * @param {Object} [options] - Configuration options\n * @param {Object} [options.routerOptions] - Options for the Express router (default: `{ strict: true }` stay with this for best results but be advised it makes paths require to be terminated with `/` )\n * @param {Object} [options.middlewareOptions=undefined] - Options passed to every middleware.\n * @param {Object} [options.controllerOptions=undefined] - Options passed to every controller.\n * @returns {Object} Configured Express router with applied routes\n *\n * @example\n * // Basic usage with a single route mapping\n * const express = require('express');\n * const app = express();\n *\n * const router = composeRoutes(express, [\n * {\n * basePath: './src/routes',\n * baseURL: '/api'\n * }\n * ]);\n *\n * app.use(router);\n * // This will set up all routes found in './src/routes' with their middleware\n *\n * @example\n * // With multiple route mappings\n * const router = composeRoutes(express, [\n * {\n * basePath: './src/api/routes',\n * baseURL: '/api'\n * },\n * {\n * basePath: './src/admin/routes',\n * baseURL: '/admin'\n * }\n * ]);\n *\n * @example\n * // With custom router options\n * const router = composeRoutes(express, [\n * {\n * basePath: './src/routes',\n * baseURL: '/api'\n * }\n * ], {\n * routerOptions: {\n * strict: true,\n * }\n * });\n *\n * @example\n * // With an existing router instance\n * const existingRouter = express.Router();\n * const router = composeRoutes(express, [\n * {\n * basePath: './src/routes',\n * baseURL: '/api'\n * }\n * ], {\n * router: existingRouter\n * });\n */\nconst composeRoutes = (\n express,\n routeMappings,\n options = { routerOptions: { strict: true }, middlewareOptions: undefined, controllerOptions: undefined }\n) => {\n if (!Array.isArray(routeMappings)) {\n throw new Error('Route mappings must be an array')\n }\n const routerOptions = options.routerOptions || { strict: true }\n const middlewareOptions = options.middlewareOptions || undefined\n const controllerOptions = options.controllerOptions || undefined\n const router = express.Router(routerOptions)\n return routeMappings.reduce((router, { basePath, baseURL }) => {\n validatePath(basePath)\n validatePath(baseURL)\n const middlewareFunctionDictionary = buildMiddlewareDictionary(\n basePath,\n baseURL,\n middlewareOptions\n )\n const routes = buildRoutes(basePath, baseURL)\n routes.forEach(([url, filepath]) => {\n // curry the Router, so that the URL is set to the route, and the Middleware is loaded.\n let curriedRouter = curryObjectMethods(\n router,\n url,\n ...dictionaryKeyStartsWithPath(middlewareFunctionDictionary, url)\n )\n const controllers = require(filepath)\n curriedRouter = controllers(curriedRouter, controllerOptions)\n if ( ! curriedRouter ) {\n throw new Error(`Controller at ${filepath} did not return the 'router'`)\n }\n router = curriedRouter._getOriginalObject()\n })\n return router\n }, router)\n}\nexport default composeRoutes\n"],"names":[],"mappings":";;;AAGA,MAAM,iBAAiB,GAAG;AAC1B,MAAM,wBAAwB,GAAG;;AAEjC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO,SAAS,gBAAgB,CAAC,KAAK,EAAE;AACxC,EAAE,OAAO,KAAK,CAAC,MAAM,EAAE,IAAI,KAAK,CAAC,IAAI,KAAK;AAC1C;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO,SAAS,OAAO,CAAC,GAAG,EAAE;AAC7B,EAAE,OAAO,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,GAAG,GAAG,CAAC,GAAG;AACxC;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO,SAAS,sBAAsB,CAAC,OAAO,EAAE;AAChD,EAAE,OAAO,OAAO,CAAC,OAAO;AACxB,IAAI,wBAAwB;AAC5B,IAAI,CAAC,KAAK,EAAE,QAAQ,KAAK,CAAC,CAAC,EAAE,QAAQ,CAAC;AACtC;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO,SAAS,aAAa,CAAC,OAAO,EAAE;AACvC,EAAE,OAAO,iBAAiB,CAAC,IAAI,CAAC,OAAO;AACvC;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO,SAAS,YAAY,CAAC,IAAI,EAAE;AACnC,EAAE,IAAI,OAAO,IAAI,KAAK,QAAQ,IAAI,CAAC,IAAI,EAAE;AACzC,I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"}