Good ol’ route hanlder

Background

All of this began after trying to make my backend files more organized and to be more specific, the API endpoints that I have built so far. The folder in which I have them is located within two folder routes/api/xD.js and the way to call them is like this /api/v1/xD something which is not bad and is considered normal to some extent. However, I want to have routes that will work as tools and for that some of them needs to look like this when call them via HTTP requests:

app.use('/api/v1/extras/xD', require('./routes/api/extras/xD));

A good example of this type of API endpoints would be functions to convert videos, download videos, download audios, image manipulation and so on. At the same time I did not want to have several lines of code as I did prior to doing this trick, I mean take a look in these lines:

// What I had
app.use('/api/v1/xD', require('./routes/api/xD'));
app.use('/api/v1/extras/xD', require('./routes/api/extras/xD'));

This in a sense looks good but takes many-many lines as your endpoints keep growing up. However, what if you wanted to have a main project to handle others projetcs, something that will require its own set of API endpoints?. Something to which I refer as a centralized project?….that’s exactly what I’m doing which is a whole different post and a big ass explanation for another time.

Furthermore, the way I found out to tackle this structure was to use the Node module of fs and to be more crystal clear, what I’m talking about are the methods of readdirSync() and statSync().

Let’s begin

This is what I ended up with:

// Define routes
const dir = './routes/api';

function getFiles(dir) {
  return fs.readdirSync(dir).flatMap((item) => {
    const routePath = `${dir}/${item}`;
    if (fs.statSync(routePath).isDirectory()) {
      return getFiles(routePath);
    }
    return { item, routePath };
  });
}

const routes = getFiles(dir);

routes.map((result) => {
  app.use(
    `/api/v1/${result.item.split('.')[0]}`,
    require(`.${result.routePath.split('.')[1]}`)
  );
});

That was my first try and it was good but it was only letting me call my API endpoints up to one level only /api/v1/xD something that I have previously mentioned, I did not really want; at the same time I did not want to keep thousands of lines as shown in the second block of code. Instead, I kept doing tweaks here and there and ended up with somewhat a good solution.

What I modified was the loop in which I iterate through the endpoints, instead of using both values, item and routePath, I only used routePath which was returning the whole path of the file and that led me to play with the string by using two JavaScript methods and these are split(), replace() and split() again; if you guys want to read more about JavaScript manipulation on strings, click here. Take a look on the final code:

routes.map((result) => {
  const firstRoute = result.routePath
    .split(`./routes`)[1]
    .replace(`/api/`, `/api/v1/`)
    .split('.')[0];
  app.use(`${firstRoute}`, require(`.${result.routePath.split('.')[1]}`));
});

The explanation is simple, using result.routePath.split('./routes')[1] takes the ./routes string out the whole path returned; secondly, I wanted to add /v1/ after the /api/ word so in order to add a string after X string, I needed to use replace() which takes two parameters, the word to find and what you want to replace it with; lastly, ExpressJS can not read files that have extension on them. As of now, the string returned looks like this /api/v1/routes/folder/file.js which throws an error; what I need is to get rid off of the . character and everything after that; I can only see one dot; I simply used split('.')[0] – zero represents the position of the character and that was about everything needed to actually have http requests up to many levels deep.

Bye bye 🙂

Leave a Reply

Back to Top