The Wrong Match: When You Don’t GET What You Expected

Keren Kenzi
7 min readMar 5, 2023

--

Joel Lord’s “Learn to MERN” workshop

An essential part of building an application is defining its API (Application Programming Interface). Usually, we start with the READ (GET) API to retrieve the items we want to display. And as our application evolves, we add more routes along the way. But what happens when the routes we add share the same prefix, like GET /books/:id and GET /books/count? Can it cause any issues?

In this blog post, I will share with you an important principle about defining APIs and how to debug APIs when you don’t GET what you expected.

The Background Story

It was a beautiful Tuesday morning, the sun was shining, the birds were singing, and the “Learn to MERN” workshop had already begun. I was about half an hour late. But Joel, the instructor, was very welcoming, and luckily the first part of the workshop focused on React, which I already know, so I caught up with the rest quickly.

Wonder what MERN means? I wondered too when I read the title of Joel Lord’s workshop. MERN means building an app using MongoDB, Express, React, and Node.js.

The workshop’s main objective was to build an app of your choice using MERN. The app should display a list of items to the user and have the option to add, delete, and count items on the list. Joel gave us a few ideas we could choose from, such as the frequently used Todo list ✅, a guest book 📖 , a list of visited places 🌍, and a list of books. I chose to go with the latter (📚).

The basic design of the books app

I created the books list app using create-react-app, and added the components folder that holds all the relevant components, like Book.js which displays a single book item. At first, the data, i.e. the books list, was defined as a const in the React project. In the next phase, we added the backend, and the data was moved to the backend and fetched accordingly. And of course, later on, we stored the data in MongoDB and fetched it using express.

The architecture

GETting to The Point

During the workshop, as I was following the steps, I reached the part of defining the API. At this point, the DB was already created in MongoDB.

In this blog post, I focus on the API and I don’t go into details about React & MongoDB. If you want to learn more about MongoDB, it has great documentation available.

We have been asked to implement the API and define the following routes:

  1. GET books
  2. GET book by id
  3. Add (POST) a book
  4. DELETE a book by id
  5. GET the number of total books

I followed the instructions and added the routes one after another, testing each one with Postman. The first four worked as expected 🤩

GET books
GET book by id
Add (POST) a book
GET books showing the newly added book 📗
DELETE a book by id
GET books showing the updated list after the newly added book 📗 was deleted from the DB

But when I tested the last one, which was supposed to return the total number of books, I got an error 😳

GET books count throws an error

Do you know the feeling you get when you code something , but it doesn’t work as expected, and you have this gut feeling that there is something obvious you are missing? That is how I felt at that moment.

Can you guess why I got this error? Take a minute to look at the code 👩🏻‍💻

Books API

💡Hint: In the terminal you can see the following error:

BSONError: Argument passed in must be a string of 12 bytes or a string of 24 hex characters or an integer

Now can you guess?

If you look at the code, you can see that we have three GET API routes, the first return all the books, the second returns a book by id, and the last return the count. Let’s put the first aside and focus on the following two:

They are quite similar. They are both a GET request that starts with /books/

The first one received an id as a parameter :id , and the second one takes no parameters but adds a constant route count to get the total number of books. So we have:

GET /books/:id vs. GET /books/count

Two Possible Matches, but Only One Perfect Match

When we send an API request, the server (in this case, node.js) goes over the defined API and stops at the first match. In my case, the request GET /books/count matched with GET /books/:id, treating count as if it was the id parameter. So when it tried to create a new ObjectId(‘count’), it threw an error, as the ObjectId constructor expects 12 bytes or a string of 24 hex characters or an integer as an argument.

The solution, in this case, required a minor change. I just needed to change the order in which I defined the routes.

And the GET /books/count worked as expected:

GET /books/count working as expected 🥂
GET book by id still works as expected 😃

👍🏻 Rule of thumb: When you have API routes with the same route prefix, define these with the params last.

In this case, the error directed me to the solution, but what if instead of GET book by id, we had a different route, for example, GET book by author ? (For the sake of simplicity, let’s assume that we have one book per author and that the user searches by the full name).

What would have happened if we would define GET book by authorbefore GET books count? Any guess?

In that scenario 🎬, we also wouldn’t get the expected result, and it would be harder to find the issue since no error would be thrown.

GET books count doesn’t return the expected result

Do you notice another issue here? What else do we need to handle?

Hint: We should fix this both in GET book by author and in GET book by id

Best Practices to Debug An API

What can we do to figure things out? Or, as Esther Perel loves to say, “Where should we begin?”

Test each API route separately

We can start by testing each API route on its own by commenting out all the others and sending the tested request.

If we comment out GET book by author, GET /books/count will work as expected.

Amazing, right? 🤩

Add console.log

We might not see right away that the order of the routes is the issue. That is why the best thing to do is to add console.login the relevant places.

As we deal with data, we can add console.log(data). In order to differentiate between the 2 routes, it is best to add additional logs to specify which one was called. For example, for the GET book by author, we can add console.log(`Call to GET /books/:author with ${req.params.author} returned ${data}`)

Adding this will show the following logs when calling GET http://localhost:5050/books/count

That will surely point us in the right direction, as we can see that the wrong GET request is called. And we will probably realize that the issue is the order in which we defined the API routes, especially after reading this blog post 😉

One more small thing, remember I mentioned there is another issue we need to handle? Have you figured out what it is?

And now?

So even though there is no book with an author named ‘count’, we still return 200, and not 404. We should always return the right and the most accurate status code to the user.

Fixed returned status code

The more accurate the return status code, the easiest it is to investigate an issue.

We should also apply the same fix to GET book by id.

Conclusion

If I had only one line to summarize this blog post, I would have summed everything up by writing, “The order in which you define your API routes is important.”. This blog post gives insights on defining APIs and how to debug them when things don’t work as expected. And I hope that next time you get to define APIs, you will apply them 😃

Have more suggestions on how to debug APIs? Want to share a similar story? Feel free to add it in the comments below.

Happy coding & Keep on MERNing 🍻

Thanks Joel and PHP UK for a wonderful MERN workshop

--

--

Keren Kenzi

Keren is a senior software engineer with over a decade of experience. She holds an M.Sc. in Computer Science and loves React and learning new technologies.