File-Based Routing in Node.js
Tags: javascript, webdev, node, routing
Published: 2023-08-31 18:45:16

About a year ago, I tried Next.js for the first time and was so impressed by their file-based routing that I decided to implement a similar system on the backend. I couldn't find anything suitable, so I took it upon myself to create my own solution. It turned out to be technology-agnostic and works with both pure Node.js, Bun and the popular Express.js.

In this brief guide, we will attempt to partially implement a shop's API. This tutorial uses JavaScript and CommonJS module types to keep things simple and quick to get started. 

## 1. Installation

💿 Install `node-file-router`: 
```console
npm install node-file-router
```

## 2. Initialization

Create an `server.js` file and do the following

If you use pure Node.js:
```js
// 1. Import default http module and node-file-router
const http = require('node:http');
const { initFileRouter } = require('node-file-router');

// 2. Create an entry-point function
async function run() {
  // 3. Initialize node-file-router and the handler function
  const useFileRouter = await initFileRouter();

  const server = http.createServer((req, res) => {
    // 4. Create a server and invoke created function on each request
    useFileRouter(req, res);
  });

  // 5. Start listening a server on 4000 port
  const port = 4000;
  server.listen(port, () =>
    console.log(`Server running at http://localhost:${port}/`)
  );
}

// 6. Run entry-point function
run();
```

If you use Express.js:
```js
const { initFileRouter } = require('node-file-router');
const express = require('express');

async function run() {
  const fileRouter = await initFileRouter();

  const app = express();

  app.listen(4004);
  app.use(fileRouter);
}

run();
```


## 2. A first route

🏡 We will create a home route that displays welcome content. 

1\. Create an `api` folder at the root of your project.
```
├── api/ <-
├── server.js
└── package.json
```
2\. Create a file named `index.js` inside this folder:
```
├── api/
│  └── index.js <-
├── server.js
└── package.json
```

```js
module.exports = function index(req, res) {
  res.end("Welcome to our shop!");
}
```
3\. Run a server using: `node server.js` command
4\. Open a browser and navigate to http://localhost:4000.<br/>
You should see the message `Welcome to our shop!` displayed.

**Congratulations! 🎉 You've created a first file route**

## 3. Add http methods

1\. Before we start, we need a small utility function which will parse json from a request.
Create a folder `utils` and put `http.utils.js` file inside.
```
├── api/
├── ...
├── utils/
│  └── http.utils.js <-
...
```

```js
module.exports = {
  parseJson(request) {
    return new Promise((resolve, reject) => {
      let data = '';

      request.on('data', (chunk) => {
        data += chunk;
      });

      request.on('end', () => {
        try {
          const parsedData = JSON.parse(data);
          resolve(parsedData);
        } catch (e) {
          reject(e);
        }
      });
    });
  }
}
```

2\. Create a folder called `products` and an `index.js` file inside it.
```
├── api/
│  ├── products/ <-
│  │  └── index.js <-
│  └── ...
...
```

3\. Implement `get` and `post` methods:
```js
const { parseJson } = require('../../utils/http.utils');

module.exports = {
  get: (req, res) => {
    res.end('list of products');
  },
  post: async (req, res) => {
    const newProduct = await parseJson(req);
    res.end(`a product will be created: ${JSON.stringify(newProduct)}`);
  }
}
```

3\. Open a browser and go to http://localhost:4000/products. <br/>
You should see a `list of products` message displayed.

4\. Make a POST request using `curl`, `Postman` or any tool you like on http://localhost:4000/products <br/>
The response should display `a product will be created` along with your content.

**Perfect! 🎉 Let's move on**

## 4. Dynamic routes

1\. Create a new file with the name `[id]` inside the `product` folder.
```
├── api/
│  ├── products/
│  │  ├── ...
│  │  └── [id].js <-
│  └── index.js
...
```

2\. Fill it the same way as you did before:
```js
module.exports = {
  // Add the `routeParams` argument as the final argument to the function. This argument will contain
  // all the taken route parameters.
  get: (req, res, routeParams) => {
    const { id } = routeParams;
    res.end(`product ${id} info`);
  }
};
```

3\. Open a browser and go to http://localhost:4000/products/123. <br/>
The page should display a message: `product 123`.

Alright, let's make it more sophisticated.

Say, we want to address the following case:
`/catalog/tag-1/tag-2/tag-n`

4\. Create a `catalog` folder with `[[...categories]].js` inside.

```
├── api/
│  ├── catalog/
│  │  ├── ...
│  │  └── [[...categories]].js <-
│  ├── index.js
...
```

And add a single `get` method:
```js
module.exports = {
  get: (req, res, routeParams) => {
    const { categories } = routeParams;

    // This type of route also covers just "/catalog"
    if (!categories) {
      return res.end('all products');
    }

    res.end(`get products that have such tags: ${categories}`);
  },
};
```

5\. Open a browser and go to http://localhost:4000/catalog/men/sneakers/nike. <br/>
The page should display a list of categories: men, sneakers, nike

**🥁 That's it!**

## What's next?

1. [Discover more capabilities of the routing system](https://danilqa.github.io/node-file-router/docs/route-matching)
2. [Configuration and More Details](https://danilqa.github.io/node-file-router/docs/route-matching)
3. [Examples with Express, WebSockets, ECMAScript, and more](https://github.com/Danilqa/node-file-router/tree/main/examples)
4. [How to create your own custom adapter and use it with your favourite framework](https://danilqa.github.io/node-file-router/docs/custom-adapter)
5. [Usage with Bun](https://danilqa.github.io/node-file-router/docs/use-with-bun/)