Search code examples
javascriptmongodbasynchronouses6-promisehapi.js

How do you return mongodb info to the browser inside a hapi.js route handler?


I'm just getting started with hapi.js (^17.3.1) and mongodb (^3.0.7), and with asynchronous js code.

Inside a route handler, I'm trying to retrieve data from the database. As a test, I'm storing a string inside a variable "s" built by looping through database collection records. The expected output to the browser is

start dbInfo1 dbInfo2 dbInfoN end

I've tried various versions of this code:

module.exports = {
    method: 'GET',
    handler: async function (request, reply) { 
        return await getRoutes();
    }       
}

async function getRoutes() {

    var s = "start";
    const mongo = require('mongodb').MongoClient;
    const mongoUrl = "mongodb://127.0.0.1:27017/";

    return // I'm returning this whole thing because hapi.js says it wants a promise. (500 error)
        await mongo.connect(mongoUrl)
        .then(function(client) {

            client.db("dbName").collection("collectionName")
            .find({})
            .forEach(function (record) {
                console.log(record.item);
                s += " | " + record.item;      
            });

            s + " end";  // But I've tried placing "return" here (500 error)

        });
        // I've also tried ".then(function(s) { return s + 'end' }) here but it seems to only have the same set of options/problems manifest.

        // I've also made it so that I place "return s + 'end'" here (displays "start end" with nothing in the middle).

}

I've tried placing the return statement in different places. I either get an http 500 error in the console

Debug: internal, implementation, error
Error: handler method did not return a value, a promise, or throw an error
dbInfo1
dbInfo2
dbInfoN

if I return the promise itself or from inside the promise, or I get

start end

in the browser if I return from outside the promise.

In either case, the console.log statement prints out the dbInfos output.

I've tried different placements, inclusions, and omissions of async and await with pretty much the same results. I've also tried wrapping what is being returned inside getRoutes into an explicit Promise using "new Promise(...". In this case, the console logs the dbInfos, but the browser hangs.

How do I await that "foreach" function before returning the variable s?


Solution

  • Finally! Got it working with this code:

    module.exports = {
        method: 'GET',
        handler: function (request, reply) { 
            return getRoutes();
        }
    }
    
    function getRoutes() {
    
        const mongo = require('mongodb').MongoClient;
        const mongoUrl = "mongodb://127.0.0.1:27017/";
    
        return mongo.connect(mongoUrl)
            .then(async function(client) {
    
                var s = "start";
    
                var documents = await 
                    client.db("dbName").collection("collectionName")
                    .find()
                    .toArray();
    
                for (const doc of documents) 
                    s += " | " + await doc.item;      
    
                return s + " end";
    
            });
    
    }
    

    The issue was that I thought that since "getRoutes" was marked as "async", the stuff inside ".then" was async as well. But I really needed to mark "function(client)" as "async". I also needed to stop using "forEach" and use a more traditional iteration over the collection.

    I had actually marked "function(client)" as "async" before, but it was out of blind trial and error, and so I never used "await" properly. I didn't really start to understand it until I read this blog by Anton Lavrenov.

    Though I only asked the question recently, I was working on it before that for a long time. Really happy with where I'm at now. And of course thank you @desoares for pointing out my silly error in the version of code I was working with above.