Node File Router

website·Indexed 29 days ago

https://node-file-router.js.org

A lightweight and fast file-based routing library for Node.js that works with any framework, pure Node.js HTTP servers, Express.js, or WebSockets. It supports dynamic routes using bracket notation (e.g., [id]) and provides configuration options for file sources, pattern ignoring, and import cache management.

Tokens
3.1K
Snippets
22
Records
23
Agent score
58%

What's inside Node File Router

  1. Create a dynamic route with a single parameter

    To create a dynamic route with a single parameter, create a file using the [parameterName].js naming convention (e.g., [id].js) within the desired route folder. The router passes the captured parameters as the final argument (routeParams) to the handler function.
    // File: api/products/[id].js
    module.exports = {
      get: (req, res, routeParams) => {
        const { id } = routeParams;
        res.end(`product ${id} info`);
      }
    };
  2. Share state across middlewares using Request Context

    You can establish a shared state (context) by passing an object as an additional argument to the useFileRouter function. This object is then accessible in all middlewares and the final request handler.
    // 1. Initialize server with context
    const server = http.createServer((req, res) => {
      const ctx = {};
      useFileRouter(req, res, ctx);
    });
    
    // 2. Access/Modify context in middleware
    export default async function useMyMiddleware(req, res, ctx, next) {
      ctx.myState = 'some value';
      await next();
    }
    
    // 3. Access context in request handler
    export default function createUser(req, res, ctx) {
      console.log(ctx.myState); // 'some value'
    }
  3. Interrupt the middleware chain

    To stop subsequent middlewares and the request handler from executing, call a return statement before calling await next() or throw an error. This is typically used for authentication or validation checks.
    // Example for Express.js
    export async function useAuth(req, res, next) {
      const auth = req.headers.authorization;
      if (!auth) {
        return res.status(401).send('Unauthorized');
      }
      await next();
    }
  4. Initialize node-file-router for Node.js and Express.js

    To initialize the routing system, call the asynchronous initFileRouter function. This constructs a URL pattern table and returns a handler function. This handler should be called on each request, passing in the request and response objects. It supports .ts, .js, .cjs, and .mjs files.
    // Pure Node.js
    const useFileRouter = await initFileRouter();
    const server = http.createServer((req, res) => {
      useFileRouter(req, res);
    });
    
    // Express.js
    const app = express();
    const fileRouter = await initFileRouter();
    app.use(fileRouter);
    app.listen(4004);
  5. Create a catch-all dynamic route for multiple parameters

    To handle routes with a variable number of segments (e.g., /catalog/tag-1/tag-2/tag-n), use the [[...parameterName]].js naming convention. This pattern also matches the base route (where the parameter is undefined). The captured segments are provided in the routeParams object.
    // File: api/catalog/[[...categories]].js
    module.exports = {
      get: (req, res, routeParams) => {
        const { categories } = routeParams;
    
        // This covers the base route (e.g., /catalog)
        if (!categories) {
          return res.end('all products');
        }
    
        res.end(`get products that have such tags: ${categories}`);
      },
    };
  6. Implement global error handling in middleware

    To capture errors from subsequent middlewares or the request handler, wrap the await next() call in a try/catch block. This middleware should be placed early in the chain to ensure it catches all downstream errors.
    // Example for Pure Node.js
    export async function useErrorHandler(req, res, next) {
      try {
        await next();
      } catch (error) {
        return res
          .writeHead(500, { 'Content-Type': 'text/html' })
          .end(error.message);
      }
    }
  7. Create a basic file-based route

    Node File Router maps URLs to files within an api folder. To create a root route (/), create an index.js file inside the api directory. The file must export a function that handles the request and response.
    // api/index.js
    module.exports = function index(req, res) {
      res.end("Welcome to our shop!");
    }
  8. Implement route handlers using different patterns

    The router supports multiple ways to define handlers. Route parameters from dynamic paths are always passed as the last argument to the handler function.
    // 1. Any Method: Single function for all HTTP methods
    export default function(req, res, routeParams) {
      // routeParams contains dynamic path segments
    }
    
    // 2. Object with Methods: Specific functions for GET, POST, etc.
    export default {
      get(req, res, routeParams) { /* ... */ },
      post(req, res, routeParams) { /* ... */ },
    };
    
    // 3. Class with Methods: Default export of a class or class instance
    export default class SomeResource {
      get(req, res, routeParams) { /* ... */ }
    }
  9. Define HTTP methods using filename suffixes

    You can specify the HTTP method directly in the filename using the .[method]. suffix. This works for plain routes, dynamic slugs, catch-all routes, and index files. The file content must be a default-exported function.
    ├── user/
    │  ├── login.[post].js
    │  └── logout.[post].js
    ├── drafts/
    │  ├── [id].[post].js
    │  └── [id].[get].js
    ├── catalog/
    │  └── [...categories].[get].js
    └── auth/
       └── index.[post].js

    // Content of the above files: export default function(req, res, routeParams) { // handler logic }

  10. Create a custom adapter for non-HTTP protocols

    To use node-file-router with protocols other than HTTP (e.g., WebSockets), provide a custom adapter object during initialization. The adapter must define how to extract the path and method from the incoming message and how to handle 404s.
    const useFileRouter = await initFileRouter({
      baseDir: `${__dirname}/api`,
      adapter: {
        // Extract path for file matching
        getPathname: incomeMessage => incomeMessage.path,
        // Extract action for object-type file methods
        getMethod: incomeMessage => incomeMessage.action,
        // Handle cases where no file matches
        defaultNotFoundHandler: (incomeMessage, ws) => 
          ws.send(`${incomeMessage.path} is not found.`)
      }
    });
  11. Handle requests with route parameters

    When a route uses dynamic parameters (e.g., [id].js), the routeParams object is passed as the second argument to the handler function if a response is returned.
    interface RouteParams { id: string; }
    
    export default async function CreateOrUpdateUser(req: Request, routeParams: RouteParams) {
      const maybeUser = await req.json();
      // ... logic using routeParams.id
      return new Response(JSON.stringify(db[routeParams.id]), {
        headers: { 'Content-Type': 'application/json' }
      });
    }