REST API with KoaJS and MongoDB (Part – 2)

In our last post about REST API with KoaJS and MongoDB, we got started with KoaJS and learned to create simple views. We also saw how we can access the query strings and incoming JSON payloads. In this tutorial, we are going to go ahead and implement the RESTful routes and CRUD operations with MongoDB.

In case you’re new to REST API development, you might also want to check out the REST API Concepts and our REST API Tutorials with Flask and Django REST Framework.

Installing MongoDB and NodeJS Driver

I am assuming you have installed MongoDB already on your system. You can install MongoDB using their official installer on Windows or Homebrew on OS X. On Linux systems, you can use the package manager that ships with your distro.

If you don’t have MongoDB installed locally, don’t worry, you can also use a free third party mongo hosting service like mLab.

Once we have MongoDB setup and available, we’re going to install the NodeJS driver for MongoDB next.

npm i -S mongodb

Connecting to MongoDB

We will create a separate file named mongo.js and put the following codes in it:

const MongoClient = require('mongodb').MongoClient;
const MONGO_URL = "mongodb://localhost:27017/polyglot_ninja";


module.exports = function (app) {
    MongoClient.connect(MONGO_URL)
        .then((connection) => {
            app.people = connection.collection("people");
            console.log("Database connection established")
        })
        .catch((err) => console.error(err))

};

Our module exports just one function which takes the app object as the only parameter. Once called, the function connects to our mongodb instance and once connected, sets the people property on our app instance. The people property would actually be a reference to the people collection on our database. So whenever we will have access to the app instance, we will be just using the app.people property to access the collection from within our app. If the connection fails, we will have the error message printed on our terminal.

We have used promises instead of callback. Which makes the code a bit cleaner. Now in our index.js file, we will call the exported function like this:

require("./mongo")(app);

That should import the function and invoke it. Assuming everything worked fine, you should see the message saying database connection established when you run the app next time.

Please Note: We didn’t create the mongodb database or the collection ourselves. MongoDB is smart enough to figure out that we used the names of non existing database / collection and create them for us. If anything with that name exists already, just uses them.

Inserting Records Manually

Before we can start writing our actual code, let’s connect to our mongo database and insert some entries manually so we can play with those data. You can use the command line tool or a mongodb GUI to do so. I will use the command line tool.

$ mongo
MongoDB shell version: 3.2.7
connecting to: test
> use polyglot_ninja
switched to db polyglot_ninja
> db
polyglot_ninja
> db.people.insert({"name": "masnun", "email": "masnun@gmail.com"})
WriteResult({ "nInserted" : 1 })
> db.people.find()
{ "_id" : ObjectId("597ef404b5256ba58d26ac53"), "name" : "masnun", "email" : "masnun@gmail.com" }
>

I inserted a document with my name and email address in the people collection of the polyglot_ninja db.

Implementing The Routes

Now we will go ahead and implement the routes needed for our REST API.

Please note: Too keep the actual code short, we will skip – validation, error handling and sending proper http status codes. But these are very important in real life and must be dealt with proper care. I repeat – these things are skipped intentionally in this tutorial but should never be skipped in a production app.

GET /people (List All)

This is going to be our root element for the people api. When someone makes a GET request to /people, we should send them a list of documents we have. Let’s do that.

// List all people
router.get("/people", async (ctx) => {
    ctx.body = await ctx.app.people.find().toArray();
});

Now if we run our app and visit the url, we shall see the document we created manually listed there.

POST /people (Create New)

Since we already have the body parser middleware installed, we can now easily accept JSON requests. We will assume that the user sends us properly valid data (in real life you must validate and sanitize) and we will directly insert the incoming JSON into our mongo collection.

// Create new person
router.post("/people", async (ctx) => {
    ctx.body = await ctx.app.people.insert(ctx.request.body);
});

You can POST JSON to the /people endpoint to try it.

curl -X POST \
  http://localhost:3000/people \
  -H 'cache-control: no-cache' \
  -H 'content-type: application/json' \
  -d '{"name": "genji", "email": "genji.shimada@polyglot.ninja"}'

Now go back to the all people list and see if your new requests are appearing there. If everything worked, they should be there 🙂

GET /people/:id (Get One)

To query by mongo IDs we can’t just use the string representation of the ID but we need to convert it to an ObjectID object first. So we will import ObjectID in our index.js file first:

const ObjectID = require("mongodb").ObjectID;

The rest of the code will be simple and straightforward:

// Get one
router.get("/people/:id", async (ctx) => {
    ctx.body = await ctx.app.people.findOne({"_id": ObjectID(ctx.params.id)});
});

PUT /people/:id (Update One)

We usually use PUT when we want to replace the entire document. For single field updates, we prefer PATCH. In the following code example, we have used PUT but the code is also valid for a PATCH request since mongo’s updateOne can update as many fields as you wish. It can update just one field or the entire document. So it would work for both PUT and PATCH methods.

Here’s the code:

// Update one
router.put("/people/:id", async (ctx) => {
    let documentQuery = {"_id": ObjectID(ctx.params.id)}; // Used to find the document
    let valuesToUpdate = ctx.request.body;
    ctx.body = await ctx.app.people.updateOne(documentQuery, valuesToUpdate);
});

The updateOne method requires a query as a matching criteria to find the target document. If it finds the document, it will update the fields passed in an object (dictionary) in the second argument.

Delete /people/:id (Delete One)

Deleting one is very simple. The deleteOne method works just like the updateOne method we saw earlier. It takes the query to match a document and deletes it.

// Delete one
router.delete("/people/:id", async (ctx) => {
    let documentQuery = {"_id": ObjectID(ctx.params.id)}; // Used to find the document
    ctx.body = await ctx.app.people.deleteOne(documentQuery);
});

What’s Next?

In this tutorial, we saw how we can implement RESTful routes and use MongoDB CRUD operations. We have finally created a very basic REST APIs. But we didn’t validate or sanitize incoming data. We also didn’t use proper http status codes. Please go through different resources on the internet or our earlier REST API tutorials to learn more about those.

In our future tutorials, we shall be covering authentication, serving static files and file uploads.