Refactoring a Simple Node.js API
Refactoring from a student’s perspective
As an aspiring software engineer I built a pretty simple CRUD API that takes data from the PokeAPI and reformatted the information into more manageable components for future app development.
When I first created the API back in November 2018, I thought I did a pretty good job at keeping to conventions and the the code that I wrote made perfect sense to me. If it makes sense to me, then other people can surely make sense of it too.
Who needs comments? Just read the code.
- Me, 2018
Here lies the start of my frustrations in 2019. In fact the code base was bad enough that it was incredibly hard to find bugs in my code when they occurred. Here’s the general file structure for my project for some context.
Seems pretty simple right? Well, the problem is it’s hard to read the nested callbacks. I know it’s in the controllers… EVERYTHING is in the controllers! Additionally, even if I can narrow it down to which route in the controllers is throwing the error, it’s hard to read which line is throwing the error and why. This lead to me refactoring the entire application from scratch to increase legibility and modularity.
Throughout the process, I had three goals in mind:
- Keep the logic in tact. While, the syntax will change, the underlying logic that I used to implement the code needs no modification.
- Increase legibility. Mostly, that means add comments to areas that need it and keep functions minimal so that no function is doing too many things at once.
- Separate concerns. This means that I need to separate the logic, which I will refer to as controllers from now on, and routes into separate directories. Also, I made sure that each set of controllers is only concerned with one resource.
Here’s what I mean. The following block of code is generally how I structured all of routes:
Basically what’s happening is that when users hit the endpoint
As mentioned before, I really tried to simplify my code, but the reason is not just for legibility, but because it allows me to add more complexity to the function without going crazy reading endless nested callbacks. That said here’s the same function but with just a little bit more bells and whistles:
I swear it’s the same function! At least, it started off as the same function. Besides the comments, I also re-implemented the function as an async (short for asynchronous) function due to the fact that I’m making an external API request that take a variable amount of time to complete. I won’t go too in depth in async functions, but freeCodeCamp’s article on async/await is a great resource to learn more about it. Another thing you might notice is that we’re missing the endpoint we see in the first line of Example 1.
This takes us back to the third objective in this refactor process. I was taught that files should be grouped based on functionality. And while the initial implementation was passable, I didn’t quite feel satisfied with the results. Here’s my refactored file structure:
Compared to the initial file structure, we can see that there three more folders. The only one we’re concerned with here is the routers folder. This folder is what contains the all of the endpoints accessible in my API. Notice that the folder contains three files in it. Each file is a collection of endpoints related to a specific resource. Here’s what the the
pokemons.js, which contains all the endpoints that make external calls to the PokeAPI, file looks like:
Remember that async function we had before? It actually lives in a file inside our controllers folder. If we take a look at line 2 in Example 3, we’re telling our program to go into the controllers folder and grab the file that has the functions we need to use as callbacks in our router requests. In this case the file is called
pokemonControllers.js . I chose to set it up this way so that I can isolate all routes that require resources from external sources into one place. Similarly, each file in my routers folder only concerns itself with one resource like user accounts and pokemon teams.
Throughout the entire refactor process I did not touch the underlying logic that the original app used. Instead I was able to condense the syntax and add more functionality overall.
The biggest impact to this refactor is that it makes it easier for me to add or modify features in the future. What started off as a simple Node.js api that simply made external api calls is now more robust and feature-full.
If you’re curious as to what the project looks like, here’s a link to the project Github repo.