Gathering detailed insights and metrics for express-fs-routes
Gathering detailed insights and metrics for express-fs-routes
Gathering detailed insights and metrics for express-fs-routes
Gathering detailed insights and metrics for express-fs-routes
@dcefram/fs-routes
Polka/Express routing based on the folder structure. Inspired by ZEIT Now's Serverless Functions structure/workflow.
express-router-fs
Simplify your Express routing. Organize routes using file and directory structures, and let this tool auto-register them.
express-create-api-endpoint
Membuat folder api beserta file controller, routes dan services
express-route-fs
File system-based approach for handling routes in Express.
Automatically create Express routes with one line of code using the file system. No more hard coding routes into your project's main file.
npm install express-fs-routes
Typescript
Module System
Node Version
NPM Version
TypeScript (98.32%)
JavaScript (1.52%)
Shell (0.17%)
Total Downloads
0
Last Day
0
Last Week
0
Last Month
0
Last Year
0
MIT License
2 Stars
167 Commits
1 Branches
1 Contributors
Updated on Nov 30, 2022
Latest Version
3.0.0
Package Id
express-fs-routes@3.0.0
Unpacked Size
200.03 kB
Size
38.41 kB
File Count
108
NPM Version
9.5.1
Node Version
18.16.0
Published on
Jun 18, 2023
Cumulative downloads
Total Downloads
Last Day
0%
NaN
Compared to previous day
Last Week
0%
NaN
Compared to previous week
Last Month
0%
NaN
Compared to previous month
Last Year
0%
NaN
Compared to previous year
2
1
32
An intuitive way of defining and registering your routes for your Express app. Avoid the clutter and cumbersome process of importing your routes and manually registering them. Easily create and manage Express routes with a simple directory structure. No more hard coding routes into your projects main file. Specify a directory to scan, and all routes will be automatically registered.
New to Express? Check out the Express documentation to get started.
1$ npm install express-fs-routes
1$ yarn add express-fs-routes
Ensure you have express installed in your project.
It is recommended to view the examples before continuing.
Existing projects should have little to no effort when migrating to this package. This aims to eliminate as much overheard as possible when creating and managing routes. Middleware can be used as well, and will be registered in the order they are defined in the route file.
It is important to note that the relative path is the url path of a route. This is similiar to how Next.js handles routing.
There is no limitation on the amount of times you decide to register routes. You can register routes from multiple directories, and even multiple times from the same directory. Using the same instance of the RouteEngine
class, you can register routes from multiple directories. This is useful if you have a directory for your public routes, and another for your private routes. You can register both directories and have them both be accessible from the same Express app.
Example directory structure:
1├── routes 2│ ├── users 3│ │ ├── login.ts 4│ │ ├── register.ts 5│ │ ├── fetch.ts 6│ │ ├── create.ts 7│ │ └── delete.ts 8│ └── index.ts 9└── server.ts
Then turns into:
1GET /users/login 2POST /users/register 3GET /users/fetch 4POST /users/create 5DELETE /users/delete 6GET /
Example: server.ts
The initial directory you choose to scan will NOT be included in the route path. For example, if you choose to scan the routes
directory, the route path will be /users
and not /routes/users
. If you wish to provide a one-time prefix for all routes, see the Engine Options section.
1import express from "express"; 2 3import { RouteEngine } from "express-fs-routes"; 4 5const app = express(); 6const routeEngine = new RouteEngine(app, "module"); // or "commonjs" 7 8routeEngine.setOptions({ 9 directory: "routes" // or path.join(__dirname, "routes") 10}); 11 12// middleware still works as normal 13 14app.use(express.json()); 15app.use(express.urlencoded({ extended: true })); 16app.disable("x-powered-by"); 17 18// here you would normally do 19// app.use("/users", usersRouter); 20// app.use("/posts", postsRouter); 21// app.use("/comments", commentsRouter); 22// ... 23 24// but now you can do 25const registry = await routeEngine.run(); 26 27// provide a catch all route 28app.use((req, res) => { 29 res.status(404).send("Not Found"); 30}); 31 32app.listen(3000, () => { 33 console.log("Server listening on port 3000"); 34});
Example: routes/users/fetch.ts
Whether you are using CommonJS, ES6, or TypeScript, all are supported and will be registered as expected. It is important when you define the route, that it is exported as default. This is the expected behaviour designed by Express. See more information about Express routers here.
An extra feature that is built into this package is the option to export a custom named object that will be used to control the registration behaviour of the route. See the Route Options section for more information.
It is important to remember that the folder/file structure that you defined will be the base url of the route that is defined within the file at hand. You may be confused to why each file has a path of /
and that is because the file"s relative path is used as the url. You are still free to define additional url paths within the route file. These are referred to as extended url paths and are explained in more detail here.
1import express from "express"; 2 3import type { RouterOptions } from "express-fs-routes"; 4 5const router = express.Router(); 6 7router.get("/", async (req, res) => { 8 const users = await User.find(); 9 10 res.json(users); 11}); 12 13export default router; 14export const routeOptions: RouterOptions = { 15 // options here 16};
1import express from "express"; 2 3import { RouteEngine } from "express-fs-routes"; 4 5const app = express(); 6const routeEngine = new RouteEngine(app, "module"); 7 8app.use(express.json()); 9app.use(express.urlencoded({ extended: true })); 10 11await routeEngine.run(app); 12 13app.listen(3000, () => { 14 console.log("Server listening on port 3000"); 15});
By default, the directory that is scanned is routes
. This is the same directory structure that is used in the quick start example.
If you are confused on the module
reference as the 2nd argument to the constructor, have a look at the Route Engine section.
1import express from "express"; 2 3import { RouteEngine } from "express-fs-routes"; 4 5const app = express(); 6const routeEngine = new RouteEngine(app, "module"); 7 8routeEngine.setOptions({ 9 directory: "my_custom_path" // or path.join(__dirname, "my_custom_path") 10}); 11 12app.use(express.json()); 13app.use(express.urlencoded({ extended: true })); 14 15await routeEngine.run(app); 16 17app.listen(3000, () => { 18 console.log("Server listening on port 3000"); 19});
If you are having trouble supplying a custom directory, ensure you are using the absolute path. You can use the path
module to help with this. See the Engine Options section for more information or the examples.
When calling setOptions
, this can be called at any time before run
is called. This means you can change the directory at any time.
1// routes/users/fetch.ts 2 3import express from "express"; 4 5import type { RouterOptions } from "express-fs-routes"; 6 7const router = express.Router(); 8 9router.get("/", async (req, res) => { 10 const users = await User.find(); 11 12 res.json(users); 13}); 14 15export default router; 16 17export const routeOptions: RouterOptions = { 18 environments: ["development", "staging"] 19};
Environments are not standardised, so you can use whatever you want. The default environment is development
. You can change this by setting the NODE_ENV
environment variable. As long as the environment matches, the route will be registered. If you want to register a route for all environments, either omit this value or supply a wildcard *
.
In this case, the above route will only be registered in the development
and staging
environments. Any other environment will not register the route.
See the examples for more information.
Also known as an application mount, you can specify a prefix that will be appended to all registered routes. If your server sits behind /api
, you can specify this as the global route prefix.
This saves the hassle of having to create a directory just for the prefix.
1import express from "express"; 2 3import { RouteEngine } from "express-fs-routes"; 4 5const app = express(); 6const routeEngine = new RouteEngine(app, "module"); 7 8routeEngine.setOptions({ 9 appMount: "/api" 10}); 11 12app.listen(3000, () => { 13 console.log("Server listening on port 3000"); 14}); 15 16// GET /api/users 17// GET /api/posts 18// GET /api/comments
Dynamic parameters are supported and will be parsed to treat them as such. The slug pattern is used to denote a parameter.
1// routes/users/[user_id] 2 3import express from "express"; 4 5import type { RouterOptions } from "express-fs-routes"; 6 7const router = express.Router({ mergeParams: true }); 8 9router.delete("/", (req, res) => { 10 const { user_id } = req.params; 11 12 await User.delete(user_id); 13 14 res.json({ message: `User ${user_id} deleted` }); 15}); 16 17export default router; 18 19// DELETE /users/:user_id
Ensure you set { mergeParams: true }
when using parameters. This is required by express to ensure the parameters are parsed correctly. By default, a parameter will be parsed into :param
format.
Additionally, you can provide a regex pattern that will be used to replace the slug. This is useful if you want to use a different pattern for your parameters.
1export const routeOptions: RouterOptions = { 2 paramsRegex: { 3 user_id: /user_id_[a-zA-Z0-9]+/ // will match user_id_1234 4 } 5}; 6 7// DELETE /users/:user_id(user_id_[a-zA-Z0-9]+)
Parameters can be nested as deep as you need. Just ensure that if you wish to use custom patterns, you provide a
pattern for each level. This is not required by default as any missing patterns will be parsed into the default :param
format.
See the examples for more information.
You may think that you are limited to utilizing the directory structure to define your routes, but you are not. You can define additional url paths within the route file. This will be appended at runtime to the base url path and will work as expected.
1// routes/users/create.ts 2 3import express from "express"; 4 5import type { RouterOptions } from "express-fs-routes"; 6 7const router = express.Router(); 8 9router.post("/admin", async (req, res) => { 10 const user = await User.create(req.body); 11 12 res.json(user); 13}); 14 15export default router; 16 17// POST /users/create/admin
When defining a route, you can specify multiple HTTP methods on the same router. All routes are handled as expected and will be registered as expected.
There is one condition to this, read the Caveats section for more information.
1// routes/users 2 3import express from "express"; 4 5const router = express.Router(); 6 7router.get("/", async (req, res) => { 8 const users = await User.find(); 9 10 res.json(users); 11}); 12 13router.post("/", async (req, res) => { 14 const user = await User.create(req.body); 15 16 res.json(user); 17}); 18 19router.delete("/:user_id", async (req, res) => { 20 const { user_id } = req.params; 21 22 await User.delete(user_id); 23 24 res.json({ message: `User ${user_id} deleted` }); 25}); 26 27// extended url paths also work 28router.put("/:user_id/avatar", async (req, res) => { 29 const { user_id } = req.params; 30 31 await User.updateAvatar(user_id, req.body); 32 33 res.json({ message: `User ${user_id} avatar updated` }); 34}); 35 36export default router;
The RouteEngine
class is the main class that is used to register routes. Start by instantiating the class with the express application and context type. The context is used to indicate how the internal engine should require the route files. This can be either commonjs
or module
. Each context type also performs its own validation on the route files.
Normally, you will most likely only need one instance of the RouteEngine
class. However, if you wish to have multiple instances, you can do so. This is useful if you wish to have different contexts for different directories. Such as when using TypeScript, one directory may be compiled to commonjs
and another to module
.
When a class is instantiated, it is best to then call setOptions
to set the options for the engine. This is required before calling run
. All options are optional except for the directory
option. When setting the options, it will override any previously set options.
1import express from "express"; 2 3import { RouteEngine } from "express-fs-routes"; 4 5const app = express(); 6const routeEngine = new RouteEngine(app, "module"); // or "commonjs" 7 8routeEngine.setOptions({ 9 directory: "routes", 10 appMount: "/api" 11}); 12 13// or 14 15routeEngine.setOptions( 16 Object.assign({}, routeEngine.options, { 17 directory: "routes", 18 appMount: "/api" 19 }) 20); // this will merge the options instead of overriding them 21 22const registry = await routeEngine.run();
After calling run
, the engine will return a RouteRegistry
object. This object contains all the registered routes and their corresponding metadata. This is useful if you wish to perform any additional actions on the routes.
1export interface RegistrationOptions<T extends MetaData = any> { 2 /** 3 * The root directory that contains all routes you wish to register. 4 * You may pass a relative path, or an absolute path. If you pass a relative path, 5 * it will be resolved relative to `process.cwd()`. 6 * 7 * @default "routes" 8 */ 9 directory: FilePath; 10 11 /** 12 * An optional app mount that is appended to the start of each route. 13 * 14 * For example, if you are building an application that will be hosted at 15 * `https://example.com/api`, you would set this to `/api` to indicate that 16 * all routes should be mounted at `/api`. 17 * 18 * This is designed to eliminate the need to specify a directory for app mounts. 19 * 20 * @default "" 21 */ 22 appMount?: string | null; 23 24 /** 25 * Specify default route metadata that will be passed to all 26 * routes. Existing route metadata will be merged with this value. 27 * 28 * @default {} 29 */ 30 routeMetadata?: T; 31 32 /** 33 * Define any routes that are specific to a certain environment. This 34 * is resolved relative to the `directory` option. 35 * 36 * ``` 37 * { 38 * environmentRoutes: { 39 * development: ["users", "posts"], 40 * production: ["users"], 41 * test: ["users", "posts", "comments"], 42 * staging: ["users", "posts"], 43 * custom_env: ["foo", "bar"] 44 * } 45 * } 46 * ``` 47 * 48 * If you instead wish to use the root directory as the environment, you must 49 * instead pass an absolute path. E.g. `path.join(__dirname, "routes")`. 50 * 51 * Note: Only accepts directories. 52 * 53 * @default undefined 54 */ 55 environmentRoutes?: EnvironmentRoutes; 56 57 /** 58 * Sometimes you may want to specify routes that act upon the root 59 * of a directory. 60 * 61 * For example, if you have a directory structure like this: 62 * 63 * ``` 64 * routes/ 65 * users/ 66 * index.js 67 * retrieve.js 68 * ``` 69 * 70 * You can tell `registerRoutes` to treat `index.js` as the root of the 71 * `users` directory. 72 * 73 * Note: Only accepts filenames. 74 * 75 * @default ["index.js"] 76 */ 77 indexNames?: string[]; 78 79 /** 80 * Specify a directory to save a JSON file that contains a tree of all 81 * registered routes, and a registry of all route handlers. This is useful 82 * for debugging purposes. 83 * 84 * Set this to `false` to disable this feature. 85 * 86 * @default ".fs-routes" 87 */ 88 output?: string | false | null; 89 90 /** 91 * Specifies whether the route registration process should run in strict mode. 92 * When strict mode is enabled, additional checks and validations can be performed 93 * to ensure that the routes being registered meet certain criteria or follow specific 94 * guidelines. 95 * 96 * - The directory must exist. 97 * - The required route must return a function. 98 * 99 * When strict mode is enabled, any errors that occur will be thrown and the registration 100 * process will be aborted. 101 * 102 * @default false 103 */ 104 strictMode?: boolean; 105 106 /** 107 * Whether errors should be thrown. If this is set to `false`, operations will 108 * continue as normal. 109 * 110 * @default false 111 * 112 * @deprecated Use `strictMode` instead. 113 */ 114 silent?: boolean; 115 116 /** 117 * Choose if you wish to redact the file output paths for security reasons. 118 * 119 * @default false 120 */ 121 redactOutputFilePaths?: boolean; 122 /** 123 * A function that is called before a route undergoes registration. This 124 * is called before environment based checks are performed, and before the route 125 * is conditionally checked for registration. Any changes made to the route 126 * object will be reflected in the registration process and the file output. 127 * 128 * **This is not middleware**. This will only be called once per route and won't 129 * be called for each request. 130 * 131 * @param route The route schema object. 132 * @returns The route schema object. 133 * 134 * @default (route) => route 135 */ 136 beforeRegistration?(route: RouteSchema<T>): RouteSchema<T>; 137 138 /** 139 * Intercept the layer stack that is registered to the Express app and provided 140 * your own custom handler for a given path. You can either return a 141 * new handler, or the original handler. 142 * 143 * Note: The `layer` that is passed is a clone of the original layer, and will not 144 * affect the original layer stack. 145 * 146 * @param layer The layer that is registered to the Express app. 147 * @param handle The handle that is registered to the Express app. 148 * @param currentIdx The current index of the layer stack. 149 * @param stackSize The total size of the layer stack. 150 * 151 * @returns The middleware that will be registered to the Express app. 152 * 153 * @default null 154 */ 155 interceptLayerStack?( 156 layer: RouteLayer, 157 handle: ExpressMiddleware, 158 currentIdx: number, 159 stackSize: number 160 ): ExpressMiddleware; 161 162 /** 163 * Manage the middleware that is responsible for calling the route handler. By 164 * providing this value, you are required to call the route handler yourself 165 * and assign the route metadata to the request object. 166 * 167 * Note: The `route` object is a clone of the original route object, and will not 168 * affect the original route object. 169 * 170 * @param route The route schema object. 171 * @param handler The route handler that is registered to the Express app. 172 * @returns An Express middleware function. 173 * 174 * @example 175 * ```typescript 176 * const routeEngine = new RouteEngine(app, "module"); 177 * 178 * routeEngine.setOptions({ 179 * customMiddleware: (route, handler) => { 180 * return (req, res, next) => { 181 * req.routeMetadata = route.route_options.metadata ?? {}; 182 * 183 * return handler.call(app, req, res, next); 184 * } 185 * } 186 * }) 187 * ``` 188 * 189 * @default null 190 */ 191 customMiddleware?(route: RouteSchema<T>, handler: RouteHandler): ExpressMiddleware; 192}
Routing works similiar to how Next.js handles routing. Each file in the directory
is treated as a route. The file name is used as the route path. For example, if you have a file called users.ts
in the directory
, it will be registered as /users
.
Dynamic routes are also supported and this is denoted using square brackets. For example, if you have a file called [id].ts
in the directory
, it will be registered as /:id
and the id
parameter will be available in the req.params
object.
Only TypeScript and JavaScript files are supported. Everything else will be ignored.
Each file can have a routeOptions
export. This is an optional export that allows you to specify additional options for the route. As of version 2.0.0, all options are purely for registration purposes. There are plans to include support for dynamic route options in the future.
1// routes/users/login.ts 2 3import express from "express"; 4 5import type { RouterOptions } from "express-fs-routes"; 6 7const router = express.Router(); 8 9router.post("/", async (req, res) => { 10 const { email, password } = req.body; 11 12 const newUser = await User.create({ email, password }); 13 14 res.json(newUser); 15}); 16 17export default router; 18export const routeOptions: RouterOptions = { 19 environments: ["production"] // only available in these environments 20};
The RouterOptions
interface accepts a generic type which is used to specify the metadata
property. See below for more information.
1export interface RouterOptions<T extends MetaData = MetaData> { 2 /** 3 * Specify certain environments you want this route to be registered in. If 4 * you wish to register a route in all environments, you can omit this property 5 * or provide a wild card token `*`. 6 * 7 * This value takes precedence over `environmentRoutes` when both are present. 8 * 9 * @default null 10 */ 11 environments?: string | string[]; 12 13 /** 14 * Whether this route should be treated as an index route. This route 15 * will be instead mounted at the parent directory. 16 * 17 * This value takes precedence over `indexNames`. 18 * 19 * @default null 20 */ 21 isIndex?: boolean; 22 23 /** 24 * Control whether the route should be registered. The route will still be scanned and under go 25 * all the same checks, but will bypass express registration. 26 * 27 * @default false 28 */ 29 skip?: boolean; 30 31 /** 32 * Specify a custom parameter regex that will be used when 33 * registering the route to the express app. 34 * 35 * It supports nested parameters, and will be used to replace 36 * the default regex. 37 * 38 * ```ts 39 * export const routeOptions: RouterOptions = { 40 * paramsRegex: { 41 * post_id: /post_id_[a-z]+/, 42 * user_id: /user_id_[a-z]+/ 43 * } 44 * } 45 * ``` 46 * 47 * Accepts either a string or a RegExp. If a RegExp is provided, 48 * it will be converted to a string using `.source`. 49 * 50 * @default {} 51 */ 52 paramsRegex?: ParamsRegex; 53 54 /** 55 * Metadata that is passed to the route and is available 56 * in the `req` object as `req.routeMetadata`. 57 * 58 * This is useful for passing data to middleware that is 59 * specific to a given route where you want to have request 60 * based context or conditional logic. 61 * 62 * @default {} 63 */ 64 metadata?: T; 65}
environments
Controls which environments this route should be registered in. This is not standardised, so you can specify any environment you want. As long as NODE_ENV
is set to one of the values, the route will be registered.
This value coincides with the environmentRoutes
option. If this option is set, it will take precedence over environmentRoutes
.
Rules this property follows:
environmentRoutes
option.environmentRoutes
.*
will register the route in all environments, regardless of environmentRoutes
.Default: undefined
isIndex
Whether this route should be treated as an index route. This route will be instead mounted at the parent directory, or will "navigate up" a directory.
This value takes precedence over indexNames
.
Default: false
skip
Whether to skip this route entirely.
Default: false
paramsRegex
Specify a custom regex pattern to use when a known parameter is found. This is useful if you want to use a different regex pattern for a specific parameter.
1// routes/users/[id].ts 2 3export const routeOptions: RouterOptions = { 4 paramsRegex: { 5 id: /user_[a-z]+/ 6 } 7};
metadata
Metadata can be defined per route file that will be passed onto the request object. This value will be available on the req.routeMetadata
property.
Default: {}
1// routes/account/register.ts 2 3interface RegisterMetadata { 4 title: string; 5 description: string; 6} 7 8app.get("/", (req, res) => { 9 res.send(req.routeMetadata.title); // Register 10}); 11 12export const routeOptions: RouterOptions<RegisterMetadata> = { 13 metadata: { 14 title: "Register", 15 description: "Register a new account" 16 } 17};
See the examples for more information.
Currently, when exporting the routeOptions
object, if your file contains multiple http methods, all routes will be affected by the options. This is a limitation of the current implementation and will be addressed in a future release.
1import express from "express"; 2 3import type { RouterOptions } from "express-fs-routes"; 4 5const router = express.Router(); 6 7router.get("/foo", (req, res) => { 8 res.json({ message: "foo" }); 9}); 10 11router.get("/bar", (req, res) => { 12 res.json({ message: "bar" }); 13}); 14 15export default router; 16 17export const routeOptions: RouterOptions = { 18 environments: ["production"], 19 skip: true 20}; 21 22// all routes, both GET /foo and GET /bar will be affected by the options
Coming soon...
This package is written in TypeScript and provides type definitions for the exported functions.
No vulnerabilities found.
No security vulnerabilities found.