How to CRUD with Node.js and MongoDB

Node.js and MongoDB make for a very fast and flexible development experience. Just watch how quickly and easily we can create, read, update, and delete a document in MongoDB with Node.

How to CRUD with Node.js and MongoDB
Thinkstock

MongoDB was one of the first NoSQL data stores, and it is the most popular NoSQL data store today. The Node.js JavaScript runtime continues to hold a dominate place in back-end development. Together they make a highly flexible and dynamic technology stack.

As you’ll see, Node.js and MongoDB allow you to quickly implement essential application functionality like CRUD (create, read, update, and delete) operations. In this article, we’ll take a look at the CRUD basics, using the latest Node.js MongoDB driver (version 3.6+).

Node.js and MongoDB setup

You’ll need Node.js and MongoDB installed on your system, and a command line with the curl command available. (If you’re using a Linux, MacOS, or Windows 10 version since 2018, you most likely have curl.)

You can download MongoDB for your OS here. Once downloaded, you can install it as a service or run it as an executable. Either way, ensure that MongoDB is running by opening a command line and running the mongo command. (You may need to add the command to your path if you didn’t install as a service.) This gives you access to the MongoDB instance running on your system.

Next, ensure that you have Node.js and npm installed. At the command line, type node -v. If Node.js is installed, you’ll get the version number. If not, go to the Node.js download page and install Node on your machine.

Curl allows you to perform simple HTTP requests from the command line. For example, if you run curl www.google.com you’ll receive the markup from the Google main page.

Create a Node.js project

Now go to a convenient folder where you’ll create a new project. Type npm init. For the project name, use node-mongo-intro. You can accept the other defaults.

Now add the dependencies you need. In the project directory you just created, type npm install mongodb polka --save. This will install both the Node.js driver for MongoDB (allowing your project to access MongoDB) and the Polka HTTP server, which you’ll use for handling HTTP requests.

Edit the package.json file to include a start script, as in Listing 1.

Listing 1. A start script

"scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "start": "node src/index" // <-- add this line
  },

Now create a /node-mongo-intro/src/index.js file, and put the contents of Listing 2 in it.

Listing 2. HTTP test in index.js

const polka = require('polka');

polka()
  .get('/create', (req, res) => {
    res.end(`works`);
  })
  .listen(3000, err => {
    if (err) throw err;
    console.log(`> Running on localhost:3000`);
  });

Now start the server with npm run start. The server will listen on port 3000. You can test it with curl http://localhost:3000/create. If you do, you should see the response (along with some curl request info) of “works.”

Insert a record in MongoDB

Now we’re going to perform a simple insert. This is the C in CRUD. Update the index.js file to look like Listing 3.

Listing 3. A simple insert

const polka = require('polka');
const { MongoClient } = require("mongodb");

polka()
  .get('/create', (req, res) => {
    const client = new MongoClient("mongodb://localhost:27017");
    async function run() {
      try {
        await client.connect();
        const database = client.db("intro");
        const collection = database.collection("quotes");

        const result = await collection.insertOne({"quote":"Life is what happens to you while you're busy making other plans."});
        res.end(JSON.stringify(result));
      } catch (e) {
        console.log("Error: " + e);
      } finally {
        await client.close();
      }
    }
    run().catch(console.dir);
  })
  .listen(3000, err => {
    if (err) throw err;
    console.log(`> Running on localhost:3000`);
  });

The code in Listing 3 opens a connection to the MongoDB instance on your local system, then specifies a database ("intro") and collection ("quotes"). A collection is analogous to a table in a relational database.

Next, the code inserts a document (analogous to a SQL record) and sends the results back in an HTTP response.

Run the insert

First, stop and restart the node server by hitting Ctrl-C. Then run this command at the command line: 

npm run startcurl http://localhost:3000/create

Verify the insert

One thing you’ll notice if you come from a SQL background is that we didn’t create a table and schema before we did this work. We didn’t even create the database we used. MongoDB does all of this for us, and it can accept any kind of structured key-value document into the collection.

Open the mongo shell with mongo, and enter the command use intro. This switches to the intro database that was automatically created. Now enter the db.quotes.find() command, and you’ll see that the record was inserted. Notice that MongoDB automatically generated a unique ID on the "_id" field. You can override this by specifying one yourself on the document.

Retrieve a document in MongoDB

Now let’s get the document back out. Add the .get() mapping seen in Listing 4. This is the R in CRUD.

Listing 4. Retrieve a document

.get('/retrieve', (req, res) => {
    const client = new MongoClient("mongodb://localhost:27017");
    async function run() {

      try {
        await client.connect();
        const database = client.db("intro");
        const collection = database.collection("quotes");

        const cursor = collection.find({}, {});

        let items = [];
        await cursor.forEach(function(doc){
          items.push(doc);
        });
        res.end(JSON.stringify(items));
      } catch (error){
        console.warn("ERROR: " + error);
        if (errCallback) errCallback(error);
      } finally {
        await client.close();
      }
    }
    run().catch(console.dir);
  })

Listing 4 connects in the same fashion as in Listing 3, then issues a find command, with an empty query. This means it matches all documents. Next it takes the response and marshals it into an array to be sent back to the client.

Notice that the cursor operations are asynchronous, as is the collection.insertOne operation from Listing 3. We use the await keyword to handle these without nested callbacks.

Test the new endpoint (after stopping and starting the server again) with curl http://localhost:3000/retrieve and you’ll see the collection is returned.

Update a document in MongoDB

Now for the U in CRUD. This is handled in Listing 5.

Listing 5. Updating a document

.get('/update', (req, res) => {
    const client = new MongoClient("mongodb://localhost:27017");
    async function run() {
      try {
        await client.connect();
        const database = client.db("intro");
        const collection = database.collection("quotes");

        const updateDoc = {
          $set: {
            author:
              "John Lennon",
          },
        };

        const result = await collection.updateOne({}, updateDoc, {}); // <-- empty filter matches all docs
        res.end("Updated: " + result.modifiedCount);
      } catch (e) {
        errCallback(e);
      } finally {
        await client.close();
      }
    }
    run().catch(console.dir);
  })

Listing 5 again connects to the database, then creates an update document. This document tells MongoDB what to change, by specifying a $set field containing an object with the fields and values to change. In our case, we set the author field to "John Lennon", the quoter of the quote in question.

Next, Listing 5 uses the updateOne() function to execute the update doc. That final empty-object argument is the filter. In this case, we want to match on all documents, so we leave it blank.

Finally, we send back the number of documents we updated (one).

Delete a document in MongoDB

The final letter in the CRUD acronym is D for delete.

The mapping for the delete operation is shown in Listing 6.

Listing 6. Deleting a document

.get('/delete', (req, res) => {
    const client = new MongoClient("mongodb://localhost:27017");
    async function run() {
      try {
        await client.connect();
        const database = client.db("intro");
        const collection = database.collection("quotes");
        const query = { };
        const result = await collection.deleteOne(query);
        if (result.deletedCount === 1) {
          res.end("Successfully deleted one document.");
        } else {
          res.end("Deleted 0 documents.");
        }
      } finally {
        await client.close();
      }
    }

Here again we use an empty query to match all of the documents in the "quotes" collection. The async collection.deleteOne() function returns a result telling us how many documents have been affected.

Restart the server (Ctrl-C) and issue a new curl command:

curl http://localhost:3000/delete

You can verify the doucument has been deleted with curl http://localhost:3000/retrieve.

Dynamic CRUD duo

And there you have it, the full lifecycle for a document in the MongoDB data store: create, read, update, and delete.

The Node.js and MongoDB combo (with some help from Polka) makes for a very fast and flexible development experience. Learn more about Node.js here and more about MongoDB and other NoSQL data stores here.

Copyright © 2021 IDG Communications, Inc.