Using Node File Router for File-Based Routing in Node.js

Transform your Node.js API development with file-based routing that maps directory structure directly to URL paths.

Introduction

Traditional Node.js routing requires manual route registration, creating a disconnect between file organization and URL structure. File-based routing solves this by mapping your directory structure directly to URL paths, eliminating boilerplate configuration and ensuring your routes stay synchronized with your code. Node File Router brings this modern paradigm to pure Node.js applications, offering a lightweight alternative to full-stack frameworks while maintaining the developer experience that makes tools like Next.js so productive.

Whether you're building a new API from scratch or refactoring an existing Express application, file-based routing transforms how you think about URL organization. Instead of maintaining separate router configuration files that must be kept in sync with your code, the directory hierarchy itself becomes the source of truth. This approach reduces cognitive overhead, eliminates a common source of bugs, and makes your codebase immediately navigable for new team members. For teams focused on modern web development practices, adopting file-based routing is a significant step toward maintainable, scalable APIs.

Need Help Building Your API?

Our team specializes in modern Node.js architectures and API development.

Why File-Based Routing Matters

The file-based routing approach has transformed how developers build web applications. Rather than maintaining a separate routing configuration that must be kept in sync with your file structure, file-based routing uses the directory hierarchy itself as the source of truth. This means adding a new route is as simple as creating a new file--no router configuration updates required, no risk of misaligned routes, and no cognitive overhead tracking which URLs map to which handlers.

Node File Router brings these benefits to standalone Node.js applications without requiring a full framework stack. The library scans your routes directory at startup, automatically registering all endpoints based on their file paths. This automatic registration eliminates one of the most common sources of bugs in traditional routing setups: forgotten or misconfigured route handlers.

The productivity gains compound over time. On larger projects with dozens or hundreds of routes, the time saved not updating router files adds up. More importantly, new team members can understand the URL structure immediately by looking at the project directories--no routing table documentation required, no hunting through configuration files to understand which handler handles /api/users/:id.

Traditional Express Routing Setup
1// routes/users/index.js2const express = require('express');3const router = express.Router();4 5router.get('/users', getUsers);6router.get('/users/:id', getUserById);7router.post('/users', createUser);8router.put('/users/:id', updateUser);9router.delete('/users/:id', deleteUser);10 11module.exports = router;

Getting Started with Node File Router

Installing Node File Router is straightforward--it's available as an npm package and works with Node.js version 14 and above. The library supports both CommonJS and ES modules, giving you flexibility in your project setup. With no required dependencies beyond Node.js itself, your application's dependency tree stays clean and free of potential version conflicts.

The initialization process is asynchronous, allowing the library to scan your routes directory and build its internal route registry before handling requests. This startup scan happens once when your application starts, building an efficient lookup structure that enables fast request matching throughout your application's lifetime.

Installation
npm install node-file-router
Basic Server Setup
1const http = require('node:http');2const { initFileRouter } = require('node-file-router');3 4async function run() {5 const useFileRouter = await initFileRouter();6 7 const server = http.createServer((req, res) => {8 useFileRouter(req, res);9 });10 11 server.listen(4000, () => {12 console.log('Server running at http://localhost:4000/');13 });14}15 16run();
Project Structure Example
├── server.js
├── routes/
│ ├── index.js // Handles / route
│ ├── users/
│ │ ├── index.js // Handles /users route
│ │ └── [id].js // Handles /users/:id route
│ └── products/
│ └── index.js // Handles /products route
└── package.json

Creating Your First Routes

The simplest routes are static paths created by naming your files after the URL segments they should handle. The index.js file in any directory handles the base path for that directory, making it easy to create root-level and nested endpoints. Creating nested routes is as simple as creating subdirectories--a file at routes/users/index.js automatically handles the /users path, while routes/users/profile.js handles /users/profile.

This approach keeps related endpoints grouped together in the file system, making it easier to maintain and navigate your codebase. When you need to modify user-related functionality, you know exactly where to look. The export structure uses HTTP method names as keys, allowing you to define handlers for different request types in the same file, keeping all operations on a resource in one centralized location.

Root Route Handler - routes/index.js
1module.exports = {2 get: (req, res) => {3 res.end('Welcome to our API!');4 }5};
Nested Routes - routes/users/index.js
1module.exports = {2 get: (req, res) => {3 res.end('List of users');4 },5 post: (req, res) => {6 res.end('Create a new user');7 }8};
File-Based Routing Benefits

Key advantages of using file-based routing in your Node.js projects

Automatic Route Registration

Routes are automatically discovered and registered based on file structure, eliminating manual configuration.

Intuitive Structure

Directory hierarchy directly mirrors URL paths, making API organization immediately clear.

Reduced Boilerplate

No need for separate router configuration files--routes live alongside their handlers.

Easy Refactoring

Moving routes is as simple as moving files--no router updates required.

Dynamic Route Parameters

Real-world applications require routes that can accept variable segments--user IDs, product slugs, and other dynamic data. Node File Router supports dynamic parameters through bracket notation in filenames, similar to conventions used in Next.js and other modern frameworks. Create a file named [paramName].js to capture a single URL segment, with the routeParams object containing all captured parameters, where the parameter name becomes the key and the URL segment becomes the value.

For routes that should capture any number of path segments, use double brackets: [[...slug]].js. This pattern matches the base path and all subsequent segments, useful for hierarchical data structures like product categories or wildcard routes. The captured segments are available as an array, giving you full flexibility to handle complex URL patterns while keeping your route organization clean and predictable.

Single Parameter Route - routes/users/[id].js
1module.exports = {2 get: (req, res, routeParams) => {3 const { id } = routeParams;4 res.end(`User profile for user ID: ${id}`);5 }6};
Catch-All Route - routes/products/[[...categories]].js
1module.exports = {2 get: (req, res, routeParams) => {3 const { categories } = routeParams;4 5 if (!categories) {6 return res.end('All products');7 }8 9 const categoryPath = categories.join(' / ');10 res.end(`Products in category: ${categoryPath}`);11 }12};
Dynamic Route Structure Example
routes/
 ├── products/
 │ ├── index.js // /products
 │ ├── [category].js // /products/:category
 │ └── [category]/
 │ └── [productId].js // /products/:category/:productId

Handling All HTTP Methods

Node File Router exports each HTTP method as a property on the route handler object, providing a clear, centralized location for all operations on a resource. This pattern mirrors the structure used by Express Router but removes the need for explicit registration--developers can immediately see what actions are supported for each endpoint without navigating between different handler files.

Handlers can be asynchronous, enabling clean integration with databases and external APIs. The async/await syntax keeps your code readable while handling asynchronous operations naturally, making it straightforward to fetch data from databases, call external services, or perform any other async operations within your route handlers.

Complete REST Handler - routes/users/[id].js
1module.exports = {2 get: (req, res, routeParams) => {3 const { id } = routeParams;4 res.end(`User ${id} data`);5 },6 7 put: (req, res, routeParams) => {8 const { id } = routeParams;9 res.end(`User ${id} updated`);10 },11 12 patch: (req, res, routeParams) => {13 const { id } = routeParams;14 res.end(`User ${id} partially updated`);15 },16 17 delete: (req, res, routeParams) => {18 const { id } = routeParams;19 res.end(`User ${id} deleted`);20 }21};
Async Route Handler with Database
1const { getProductById } = require('../services/products');2 3module.exports = {4 get: async (req, res, routeParams) => {5 const { id } = routeParams;6 const product = await getProductById(id);7 8 if (!product) {9 res.statusCode = 404;10 return res.end('Product not found');11 }12 13 res.end(JSON.stringify(product));14 }15};

Integrating with Express.js

While Node File Router works with the native http module, many projects use Express.js for its extensive middleware ecosystem. The library integrates seamlessly with Express through custom adapters, allowing you to use Node File Router for your route definitions while leveraging Express middleware for authentication, logging, body parsing, and other cross-cutting concerns. Teams migrating from traditional Express patterns can adopt file-based routing incrementally without rewriting their entire middleware stack or abandoning the Express patterns they already know.

This integration pattern is particularly valuable for organizations looking to modernize their API architecture--they can gradually adopt file-based routing for new endpoints while maintaining existing Express routes, eventually consolidating to a unified routing approach.

Express Integration
1const express = require('express');2const { createExpressAdapter } = require('node-file-router/express');3const { initFileRouter } = require('node-file-router');4 5async function createApp() {6 const app = express();7 app.use(express.json());8 9 const useFileRouter = await initFileRouter();10 const router = createExpressAdapter(useFileRouter);11 app.use(router);12 13 return app;14}
Middleware Pattern
1// Global middleware2app.use(express.json());3app.use(logger);4 5// File router for API6const router = createExpressAdapter(await initFileRouter());7app.use('/api', router);8 9// Static files10app.use(express.static('public'));

Best Practices for Route Organization

Organizing routes effectively becomes increasingly important as your application grows. Keep routes for related resources together in the same directory--group all product-related endpoints in routes/products/, all user-related routes in routes/users/. This structure groups related functionality together while allowing nested relationships like product reviews through subdirectories.

Consistent naming for dynamic routes improves maintainability: use descriptive parameter names like [userId].js rather than [id].js, place dynamic segments at the end of filenames, and follow REST conventions with plural resource names. Perhaps most importantly, avoid putting business logic directly in route handlers--use a service layer to keep routes clean and testable.

Recommended Route Organization
routes/
 ├── index.js
 ├── auth/
 │ ├── index.js // POST /auth/login
 │ ├── register.js // POST /auth/register
 │ └── [token].js // GET /auth/:token
 ├── products/
 │ ├── index.js // GET /products
 │ ├── [id].js // GET /products/:id
 │ └── [id]/reviews.js // GET /products/:id/reviews
 └── orders/
 ├── index.js // GET/POST /orders
 └── [id].js // GET/PUT/DELETE /orders/:id
Separation of Concerns Pattern
1// routes/users/[id].js - Keep routes clean2const { getUserById, updateUser } = require('../services/users');3 4module.exports = {5 get: async (req, res, routeParams) => {6 const user = await getUserById(routeParams.id);7 res.end(JSON.stringify(user));8 },9 patch: async (req, res, routeParams) => {10 const user = await updateUser(routeParams.id, req.body);11 res.end(JSON.stringify(user));12 }13};

Group Related Routes

Keep routes for the same resource in the same directory for easy navigation.

Descriptive Parameter Names

Use [userId].js rather than [id].js for clarity when reading file names.

Use Plural Resource Names

Follow REST conventions with plural names: users/, products/, not user/, product/.

Separate Business Logic

Keep route handlers thin and delegate to service modules for business logic.

Performance Considerations

Node File Router is designed for minimal runtime overhead. The route scanning happens at startup, building an internal lookup structure that enables fast request matching. For applications with thousands of routes, this scan might take a noticeable moment during development restarts. However, production deployments rarely restart frequently, making this a negligible concern for most applications.

Once initialized, Node File Router uses a prefix-tree (trie) based lookup for fast route matching. This means request routing time scales logarithmically with the number of routes rather than linearly--adding more routes doesn't slow down existing routes. The library uses efficient file system traversal and caches the resulting route registry, ensuring that subsequent requests route instantly without repeated scanning.

Custom Configuration Options
1const useFileRouter = await initFileRouter({2 routesDir: './api/routes', // Custom routes directory3 extensions: ['.ts', '.js', '.mjs'] // File extensions to process4});

Performance Metrics

14

Minimum Node.js Version

0

Required Dependencies

O(log n)

Route Lookup Complexity

Comparison with Next.js File-Based Routing

Next.js popularized file-based routing in the React ecosystem, and Node File Router brings similar concepts to standalone Node.js applications. Both use identical file naming conventions: index.js for base paths, [param].js for dynamic segments, and [[...slug]].js for catch-all routes. This convention similarity means developers familiar with Next.js can immediately understand Node File Router projects, reducing the learning curve when switching between them.

The primary difference is scope. Next.js provides a complete framework including server-side rendering, static generation, API routes, and routing for React applications. Node File Router focuses specifically on routing, offering a lightweight solution that works with any application architecture. For projects that need only API routing without the full Next.js framework stack, Node File Router provides a simpler, more focused solution.

Node File Router vs Next.js API Routes
FeatureNode File RouterNext.js API Routes
File-based routingYesYes
Framework scopeRouting onlyFull-stack framework
Server renderingNot includedBuilt-in SSR/SSG
Setup complexitySimpleRequires Next.js setup
Framework dependenciesNoneReact, Next.js
Custom frameworksYes (adapters)No
  • Building a pure Node.js API without React frontend requirements
  • Wanting file-based routing without a full framework
  • Migrating from Express and wanting improved route organization
  • Needing a lightweight solution that integrates with existing Express apps

Advanced Configuration and Examples

Node File Router accepts configuration options to customize its behavior for specific project requirements. Control which file types are processed, specify custom routes directories, and create custom adapters for frameworks beyond the built-in http and Express support. A common pattern for API evolution is including version numbers in the URL path--accessing routes through /v1/users/123 clearly indicates which API version is in use, making it easier to maintain backward compatibility while evolving your API.

Model nested relationships through corresponding directory structures, handling URLs like /users/123/posts/456 to cleanly represent relationships between resources. Create dedicated error handlers using catch-all routes like routes/[...404].js for consistent error responses across your API.

API Versioning Pattern
routes/
 └── v1/
 └── users/
 └── [id].js // /v1/users/:id
404 Error Handler
1// routes/[...404].js2module.exports = {3 get: (req, res) => {4 res.statusCode = 404;5 res.end(JSON.stringify({ error: 'Not found' }));6 }7};
Custom Adapter Example
1const { createAdapter } = require('node-file-router');2 3function createCustomAdapter(router) {4 return (req, res) => {5 router(req, res).then((result) => {6 if (result) {7 // Handle matched route8 }9 });10 };11}

Conclusion

File-based routing transforms Node.js API development by eliminating the separation between file organization and URL structure. Node File Router brings this modern approach to standalone Node.js applications, providing a lightweight alternative to full-stack frameworks while maintaining the developer experience benefits that make file-based routing so productive.

The library's simplicity--install, create files, get automatic routing--enables rapid development without configuration overhead. Its flexibility with frameworks and customization options ensures it can adapt to projects of any size or complexity. For teams building Node.js APIs who want the organization benefits of file-based routing without the weight of a full framework, Node File Router offers a compelling solution that aligns your codebase structure with your API's URL hierarchy.

Whether you're starting a new API project or looking to refactor an existing Express application, file-based routing with Node File Router provides a clean, maintainable approach to route organization that scales with your project and sets the foundation for future growth.

Frequently Asked Questions

Ready to Modernize Your Node.js API?

Our team has extensive experience building scalable APIs with Node.js. Let's discuss how file-based routing can improve your development workflow.