Building an async / await Promise Based MongoDB Query Library

And importing them into your Express routes

In this article we will discuss how to seamlessly build out an asynchronous MongoDB query library for your app routes to utilise. This is a continuation from my article on using Promises, async and await with MongoDB, where I explained how to create asynchronous MongoDB queries using Promises.

What we will cover:

  • How to structure a database library in your project.
  • Minimal syntax for defining a list of asynchronous functions, each of which can be exported on an individual basis. These functions host Promises that handle your Mongo queries.
    Each of these functions will be referred to as a Mongo export.
  • Importing our Mongo exports into your app routes to be used in API calls.

Defining the Structure

The structure we are aiming for compliments the default Express setup by adding a db folder within the routes directory. We are aiming for the following setup:

routes/
db/
dbAuth.js
dbUser.js
...
auth.js
user.js
...

As projects grow manageability becomes super important. We need an easy to read file structure with an easy to maintain syntax and flow to our code.

In the previous article we handled the whole process in our API call. We defined the Promise and Promise handler function in the API call itself. That worked well for establishing our understanding of how Promises and asynchronous functions worked, but as the project grows things will quickly become messy and unreadable.

There is a better way, with a list of exportable Promise based functions.

Creating the list of Mongo Exports

The idea here is to create a list of exports in each db file, designed to be easily imported into our routes. For early disclosure, the way we will be importing them into our routes is by using ES6 syntax:

routes/auth.js

const { getSignedInUser, 
updateAuthToken,
validateAuthToken,
destroyAuthToken } = require('./db/dbAuth');

Here I have taken the auth.js example route file from above. We can expect all of our authentication related database queries to be called inside of this file, from db/dbAuth.js.

So what exactly are we importing? Well, each of these imports is an asynchronous function that defines and calls the Promise at the same time:

  • getSignedInUser is an async function with its related Promise and Mongo query within.
  • updateAuthToken is an async function with its related Promise and Mongo query with.
  • etc…

Ultimately, what we are aiming for is extraordinarily minimised syntax. As a template of what will follow, this will represent one Mongo export:

getSomethingFromMongo: async (arg1, arg2) => (await (() => (   new Promise((resolve, reject) => 
(clientConnect().then(client => {
const db = client.db('my-database');
//execute query and resolve promise...
})
))))()),

(The highlights in bold are the only pieces of code that will change from export to export).

So using this idea, let’s jump into the dbAuth.js example file to examine how an export function is structured:

Quite a lot is happening in this example — and there are also 2 functions being imported from a dbMongo file too — clientConnect and clientClose!

Let’s get to that in a second, but we are simply defining a Promise within dbMongo.js to connect to the client and return it, and to close the client. By doing this we no longer need to include require(‘mongodb’).MongoClient in every db library file. Instead we are using these export functions to connect and close the client.

Breaking down the syntax

Let’s highlight how our export functions are being constructed:

  • They all need to be defined within module.exports { };
  • An aesthetically pleasing multi-line comment is used to define each export for easy lookup.
  • We are using arrow functions and wrapping its content with brackets (), as opposed to curly braces {}. Why? Because the result of a singular statement is being returned. Curly braces {} allow us to wrap multiple statements within one code block, followed by a return statement. With brackets (), a single statement is returned without having to explicitly define return someObject;. Our code is cut down significantly.
  • Within getSignInUser, we await the result of an anonymous function that holds our Promise object, and our Mongo query within. This anonymous function is defined, and is then executed at the end of its delcaration with the use of more brackets ().

Brackets, and more brackets

Examine the end of the getUserSignIn function and you will see ))))()), .

  • ) )))()) bracket 1: closes our Promise handler function
  • ) ) ))()) bracket 2: closes our new Promise declaration
  • )) ) )()) bracket 3: closes our await anonymous function
  • ))) ) ()) bracket 4: closes our await anonymous function reference
  • )))) () ) executes the anonymous function we just defined
  • )))) () ) end bracket: closes getUserSignIn

This is not readable, but it doesn’t have to be. This pattern can be duplicated for every export function. All we are interested in is what happens after our connected Mongo client object is returned by the clientConnect promise. Everything else is minimised boilerplate for our convenience.

A quick look at dbMongo.js

This file contains our clientConnect and clientClose export functions, which have been defined in the same way as the example above:

Within clientConnect, we are defining a Promise that simply connects to your Mongo cluster. This is returned in a client object.

clientClose is more of a convenience function that just calls Mongo’s close() to disconnect from the cluster.

Importing and using your exports

The most rewarding part of this process is actually using your newly created Mongo library within your routes, which is rather simple. All that is needed is to import and then call the function, with a then() handle attached to it:

const { getSignInUser } = require('./db/dbAuth');
router.post('/signin', (req, res, next) => {

getSignInUser(req.body.username).then(data => {

//handle data returned from Mongo

}).catch(e => {
next(e);
});
});

This structure is very easy to maintain, and grow:

  • There is no mention of Mongo or the Promises that come with them in our route files. We are only interested in the data returned by the exports here — not the infrastructure of how that happened.
  • All our Mongo related code is now comfortably sitting in the db/ folder. Each request essentially boiling down to the following template:
getSomethingFromMongo: async (arg1, arg2) => (await (() => (   new Promise((resolve, reject) => 
(clientConnect().then(client => {
const db = client.db('my-database'); //execute query and resolve promise...
})
))))()),
  • The db folder can be expanded to hundreds of exports without interfering with other parts of your app.
  • Only the exports needed from your db files are imported into your routes making for clean, readable code.

This concludes this extension to the introduction of using async / await and Promises for MongoDB queries. Enjoy building your database libraries in Javascript!

Continue Reading…

Programmer and Author. Director @ JKRBInvestments.com. Creator of LearnChineseGrammar.com for iOS.

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store