5

Demystifying Routing: Creating Your Own file-path-based Router

 1 year ago
source link: https://blog.bitsrc.io/demystifying-routing-creating-your-own-file-path-based-router-e5f7245a4925
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

Demystifying Routing: Creating Your Own file-path-based Router

A feature that all modern frameworks use, why should it remain a mystery?

1*2Ev5AmMZiiwfW006FWMWIw.png
Understanding routing, Dalle-2

One trend that most JavaScript frameworks are adopting is to provide path-based routing. This means that there is a 1:1 relationship between whatever URL you want to visit and a special folder within your project, containing the route handler functions.

That is to say, if you visit http://<your host>/users/list you’ll have the handler function inside the users/list.js file (or perhaps within the users/list/index.js depending on your preferences).

This is a very nice feature, and one that we take for granted with frameworks like Next, Fresh or others.

That said, I hate taking things for granted, so I’ll try to reverse-engineer how that works in this article.

Let’s take a look at how to implement your own version of path-based routing using Express.

Why work on this?

I mean, other than trying to demystify a mechanic that’s deep within almost all modern frameworks, this is a very nice feature that helps improve the Development Experience by simplifying the way you set up your route handlers.

Before this, we used to use different sub-optimal ways, like having a single routes mapping file, which contained all routes and the files that were meant to handle them.

Something like:

That was a fine solution if you had a small app and just a few routes. You even had the flexibility of storing your route handlers in different places. But if on the other hand, you were working on a big enterprise application, you could be looking at hundreds of lines inside this file. As you can probably guess, maintaining such a file was something that no one wanted to do and adding bugs to it was too easy.

So really, using the good old “convention over configuration” here, we have a “simpler” way to define what parts of our code handle which routes without having to maintain yet another file. I call that a win-win!

I love these kinds of features because of exactly that, the improvement in DX.

Of course, doing this will probably determine a lot about how you structure your web project, but if you’re building the router yourself, you can also make the required modifications to suit exactly what your needs are.

So let’s see what that implementation could look like.

Implementing our own router

For this example, I’m going to be using ExpressJS, not to use their own router, because that would make no sense, however, it provides a simpler abstraction to the process of creating a web server, which is not really the focus here. So it should make things easier.

The aim is to provide a router that can handle a structure like this:

1*LMpjk4KHBJbepDkUi6C5HA.png

Notice the content of the routes folder:

  • We have an index.js file that should correspond to the main route of the app.
  • We can have handlers using the file name, like users.js which would handle requests to /users and the books/index.js file which would handle requests to /books .
  • We have further nested routes with books/addresses/index.js which handles the /books/addresses URL.
  • Finally, the cherry on top, we even have dynamic routes thanks to the /books/[book].js file. That file will handle all routes inside /books that are not the root or /books/addresses

So it’s quite complete all in all, and as you’re about the see, the only really complex logic is the one that handles the dynamic route, the rest is quite straightforward.

Adding file-path-based routing

Let’s first look at the easy logic: anything BUT the dynamic routes.

After installing ExpressJS, let’s set up a catch-all handler with the all method:

This code is catching all routes (thanks to the /* route) and all HTTP verbs (thanks to the use of the all method).

Every time a request is caught by this handler, we’ll take the URL and check if there is a file with a .js extension inside the routes folder. If there isn’t, we’ll assume it’s a folder and we’ll look for an index.js file inside it.

With that settled, we’ll just call the executeRoute function and we’ll get the resulting value. If it’s false , then we’ll assume the execution failed because it couldn’t find the files. So we’ll translate that error into a “404 — Not found response”.

The missing piece to this puzzle, the executeRoute function looks like this:

We’re using the dynamic import function, if the file we’re looking for exists, then we keep moving and get the HTTP verb used for the request. This way, you have the ability to define a catch-all handler called handler (I know, great naming!) or use the verb’s name as the method’s name. If you define it, the code will use that one instead.-

And if the import fails (by throwing an exception) that’s because there is no file to import, which means this particular route is not mapped. Which in turn means we have to return a 404 error.

Believe it or not, that’s pretty much all you need to achieve file-path routing.

Of course, if we also want to add support for dynamic routing as I showed before, we need “a few” more lines.

Adding support for dynamic routing

Now that we know how to import the route handler based on the URL the user is requesting, we need to add some logic to the scenario where the executeRoute function returns false . After all, that means the route isn’t directly mapped to a file, but we’re also looking to add support for “wildcard” files.

The logic to add looks something like this:

  1. If there is no direct file mapped, then let’s first extract the name of the dynamic parameter, and its value.
  2. Let’s remove the name from the URL to understand the folder where the special file should reside.
  3. Let’s create the new name of the file to import by wrapping the name of the dynamic parameter in square brackets.
  4. Let’s try to import the new file and call executeRoute on it.

Imagine the following example:

  • The path /api/users/donald is getting hit.
  • We look for the file /routes/api/users/donald.js or /routes/api/users/donald/index.js but there is nothing there.
  • So we take to “donald” out of the URL and look inside the /routes/api/users folder for a file with the name inside square brackets. And we find the [user].js file.
  • Then we now know the dynamic parameter is called “user” and its value is “donald”.
  • We can now import the dynamic file, and add the parameter into the Request object so the dynamic handler can make use of it.

That translates into this:

The above is the full code of our server. You can ignore the bits you already know, but look at the logic inside the false clause I added on line 58. It follows what I already explained.

I’m calling the combo of filename and parameter name as “dynamic handler” and I wrote a function to get it from the URL.

The getDynamicHandler function, which can be found at the top of the above code, explores the folder where the “special” file should be. It reads all files inside it, and looks for one with brackets on its name. Since it only makes sense to have a single dynamic handler per route, once we find it, we stop looking.

With a neat regular expression I also capture the param’s name (which is what’s wrapped in brackets on the file name).

With that I can now return both, the name of the parameter, and the file that contains the handler code. Remember, the actual value of the parameter is taken directly from the URL.

And that’s it, the dynamic router now can handle simple routes, multiple HTTP verbs, and dynamic routes using specially named handler files.

And all of that inside a single file that’s less than 100 lines of code.

Of course, as is the case with all my similar experiments, this is a learning exercise, if you’re looking to take this code into production, I’d recommend cleaning it up a bit, probably adding some sort of caching to avoid reading from disk on every dynamic request, and finally, adding unit tests.

That said, I hope you found the article useful and if you’ve had the experience of writing your own file-path router in the past, let me know in the comments how you did it, I’d love to compare notes!

Build apps with reusable components like Lego

1*mutURvkHDCCgCzhHe-lC5Q.png

Bit’s open-source tool help 250,000+ devs to build apps with components.

Turn any UI, feature, or page into a reusable component — and share it across your applications. It’s easier to collaborate and build faster.

Learn more

Split apps into components to make app development easier, and enjoy the best experience for the workflows you want:

Micro-Frontends

Design System

Code-Sharing and reuse

Monorepo


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK