A Fruit Flavoured Guide to Creating a REST API powered by Node and MongoDB.

In this article we’ll go through the steps needed to have a working API that can fetch, insert and remove data from MongoDB, a non-relational database.

Personally I love eating fruit 🍎 so for this example we will be collecting peoples’ favorite fruit!

Once our API is up and running, ready to collect fruit, we’ll also explore ways to improve it.

If you just want to get straight to the code, click here.


Image courtesy of inspiredimages from Pexels

Some cool things we will encounter on the way.

  • How to create a simple express server locally.
  • Understand the importance of testing.
  • Learn how to insert, find and remove data from mongoDB using mongoose.
  • Build endpoints that we can call from any client application.

Some things you should know first.

  • This isn’t a node tutorial, so if you need to brush up on nodejs beforehand you can check out the docs here.
  • You should be able to install packages using npm. If not you can check out npm here.
  • You will need mongoDB set up locally. Depending on your OS, you can take a look at the osx, windows or linux guides.

So, whats the plan?

We want to build an express server, with 3 routes that will:

  • Insert a new person’s favourite fruit into our database.
  • Query peoples’ favourite fruit from our database.
  • Remove a person’s favourite fruit from our database.

All of these requests can use the same endpoint /fruit with ‘insert’ using the POST method, ‘query’ using a GET and ‘remove’ using a DELETE.

Now we have laid out the basic foundation of what we want, we can think about a game-plan to achieve it.

One proposed option would be:

  1. Construct tests in order set up the criteria for what our API should do, using our expectations to drive our design.
  2. Having our tests set up we can begin construction 👷. Our objective will be to meet the requirements we planned out.
  3. If our design has been given the green lights, we can clean up any mess we made.

Setup

Assuming we have nodejs, npm and mongoDB installed, let’s initialize our project. Run the command npm init in your terminal window, while in your folder directory and follow the instructions to create your projects package.json.

For an easier development process we’ll install nodemon, which will remove our need to repeatedly stop and start our server:

npm install -g nodemon

Now whenever we want to run our server, we will use the command:

$ nodemon index.js

With that in mind, we can set up our project structure to look something like this:

Folder structure
  • The file routes.js is the place where we will build the functionality of our 3 routes
  • The routes.test.js will be the file where we hold all of our routes’ tests.
  • The config.js will hold any variables that need to be shared across different parts of the project.
  • All our express server code and mongoose instantiation code can live in the index.js file.
  • Our package.json will contain our dependencies and scripts.

Testing……or designing?

To make sure our endpoints work correctly and return the data we expect them to, we should construct tests to validate the responses we receive from our requests.

If the thought of writing tests doesn’t make you jump for joy and start cartwheeling down the street, I’m sure you’re not alone.

But a nicer way to think about “testing” is that you are actually laying down the design of your software.

Before any engineering task it’s always a good idea to detail out our product’s requirements.

For this guide, let’s keep things short and sweet by considering 2 requirements for each route:

Insert a new person’s favourite fruit into our database.

I would like to get a confirmation that my data has been inserted and I would also like to be notified of which data I have inserted.

So this route’s response:

  • Should contain a status and the data inserted.
  • Should contain the name and the favouriteFruit that we inserted.

Query peoples’ favourite fruit from our database.

Our ideal response would contain the status and the fetched data, which could be multiple entries. So in this case, the route’s response:

  • Should contain a status and the data fetched.
  • Should contain an array of fetched data, with each array item containing a name and favouriteFruit property.

Remove a person’s favourite fruit from our database.

Our ideal response would contain a status and a confirmation of the removed data. So our response:

  • Should contain a status and the data removed.
  • Should contain the name and the favouriteFruit that we removed.

Building our tests.

For this tutorial I’ll be using jest. Which can be installed via the terminal by typing:

npm install --save-dev jest

Once we have jest installed as a dev dependency, we need to configure our test script, located in package.json by instructing our test script to use jest:

"scripts": {
"test": "jest"
}

We will also need a way to send requests. I will be using axios for that:

npm i axios --save-dev

Looks like we are ready for action!. Using our established criteria above, here is what our routes.test.js file becomes:

routes.test.js — Structuring the tests for our API routes.

Great! Ensure the server is running and then go ahead and type npm test into the terminal. I wonder what delightful results we may get?

Wouldn’t have expected anything else.

Well….. everything’s failing obviously. We don’t even have a server setup, let alone any routes. But what we do have is our requirements all set out, so we now know what to aim for. Lets go get it!


Build an express server

You can install express and save it as a project dependency by typing the following command in your project’s root folder:

$ npm i express --save

Once installed, let’s build our express server inside our index.js file. We can start by instantiating express and configuring it to listen on a port of our choosing.

index.js — Just listening on a port.
config.js — Our port configuration.

Now that we have a server running, we can look to build out our routes:

routes.js — Our ideal responses.

We will need to import our routes into index.js so our express server can use them. This updates our index.js to:

Let’s test run our tests again and see how we are doing:

Success!

There you have it, a fully operational API…..

…….just kidding.

There’s an easy way to break this, what if we made another test and inserted a different name / favouriteFruit?

This example shows us that our testing suite isn’t a perfect fit for our requirements. Ideally we should come up with more conditions and write tests, but for the purpose of this guide, we will carry on.

Ok! Now it’s time to build our functionality out for real.

By this stage make sure you have your mongoDB server up and running.

You can install mongoose in similar fashion to express by typing in the terminal:

$ npm i mongoose --save

Once installed, we can import mongoose, configure our database and then instruct mongoose to connect to our mongoDB database:

config.js — Adding a database name variable.
index.js — Adding mongoose and db configuration

Build a schema and model.

Earlier, we defined our ideal data structure through our tests. It looks like this:

{
"name": "Dan",
"favouriteFruit": "kiwi"
}

It’s great that we know it, but sadly mongoDB has absolutely no idea about our data structure. It’s time to let mongoDB know about our intentions.

We can do that by creating a schema and a model for our data.

A schema is simply just a guideline for how the data should be represented. So for our project, we want two keys: a "name" and "favouriteFruit" with their values both being strings. So to create our schema, all we need to do is specify that:

const FruitSchema = new mongoose.Schema({
name: String,
favouriteFruit: String
});

Now that we have our schema sorted, we can create a model. A model corresponds to a collection in mongoDB. So by adding our model it enables us to retrieve, add and store data for that specific model in our mongoDB:

const FavouriteFruit = mongoose.model("fruits", FruitSchema);

Alright! So our index.js file now looks like this:

Our index.js file after adding our db configuration and schema / model.

So now we can move on to making our routes perform the actions we want them to!


Adding functionality to our routes.

Moving back to our routes.js file, currently all we are doing is responding with some dummy data. Time to change that.

Inserting data

Lets start with our POST route because, well…it will be more fun to add some data before we try to view and delete data which does not exist yet 😲 .

So for the POST request, we would expect that a user of the API will submit their name and favouriteFruit, like in our data structure above. We can therefore expect the incoming request to include a request body.

Fortunately we can access the incoming request body by accessing the req.body argument inside our addToDatabase function……………

……….if we add another npm package called body-parser.

As the name suggests, it will enable our express server to parse incoming request bodies.

$ npm i body-parser --save

Import and invoke it as a middleware, so our index.js grows by these lines:

body-parser. Make sure this code is positioned after you have instantiated express and created ‘app’ const.

Now, we need to build out the functionality that will enable us to store this information in our database. Mongoose has an inbuilt function called create, which we can invoke to insert our data. Calling create gives us a promise, which we can handle using .then .catch.

Mongoose method for inserting data. This will live inside our addToDatabase function.

Viewing Data

We can query our database by using mongoose’s method find. Whats more is we can also pass in a query object, which will enable us to search for entries that contain the values we seek.

Our query object will be stored inside of req.query, which we can subsequently pass into the find method. Our pattern will remain similar to our post example above:

Mongoose method for finding data. This will live inside our getFromDatabase function.

Removing Data

The method we will use for removing data will be findOneAndDelete, which takes an object as an argument, specifying which item to delete. We can therefore enable users to specify their removal conditions by using a request body. So our function will look like this:

Mongoose method for removing data. This will live inside our deleteFromDatabase function.

Putting it all together

Great! Now with our route functionality built out, our routes.js should look like this:

Now that we have that all set up, let’s run our tests again:

And there you have it! We can now insert get and delete from our database!


Things to consider, how to improve

Here are just a few considerations for our API, which can help us improve our system:

  • Refactor. Getting a working model isn’t the end result. Now that we have our tests passing, we should look to refactor our code. Making it neater / easier to read will help us and our fellow coworkers prevent any eye-strain when they stumble upon our repository.
  • Protection against incorrect request bodies. What if the request body key is incorrect? Or a POST request is submitted without a request body at all? Currently we are taking whatever request body there is and trying to insert it into the database, but we could put some checks in to prevent incorrect data from being inserted.
  • findOneAndDelete will delete something even if not request body is specified. Depending on the circumstance, that may be undesirable.
  • Improved error handling. Right now we are just sending Uh Oh! Error! to the client. What message would be more useful for them to know?
  • Improved logging. If we are to use this API for production, we would need to improve our logging. Currently we aren’t logging anything about incoming requests, process times, timestamp or which endpoint and method is being called. As our API grows, that information becomes more valuable for debugging.
  • Remove hard-coded values. Right now our model is hard-coded to "fruit". What if I wanted to add new data to a new collection?