0

I am exporting this function in Node but I need the Booking.find function to complete before the rest of the code continues.

Original:

    module.exports = function() {
    return function secured(req, res, next) {
        if (req.user) {
        const mongoose = require('mongoose');
        const Booking = mongoose.model('Booking');
        Booking.find((err, docs) => {  // I need this to run first
            bookingsCount = docs.length;
        });
        const userProfile = req.user;
        res.locals = {
            count: bookingsCount,  // so that when the page loads this var is available
            userProfile: JSON.stringify(userProfile, null, 2),
            name: userProfile.displayName,
            loggedInEmail: userProfile.emails[0].value,
            isAuthenticated: req.isAuthenticated(),
        };

        return next();
        }
        req.session.returnTo = req.originalUrl;
        res.redirect('/login');
    };
    };

I tried to create separate functions with a callback but I don't think that worked correct because that would then be an asynchronous approach but I believe I need to make this part synchronous.

Then I tried this below How to wait for the return of a Mongoose search async await and it seems to be returning correctly each time.

Updated:

    module.exports = function () {
        return async function secured(req, res, next) { // added async
            if (req.user) {
                const mongoose = require('mongoose');
                const Booking = mongoose.model('Booking');
                await Booking.find((err, docs) => { // added await
                    bookingsCount = docs.length;
                });
                const userProfile = req.user;
                res.locals = {
                    count: bookingsCount,
                    userProfile: JSON.stringify(userProfile, null, 2),
                    name: userProfile.displayName,
                    loggedInEmail: userProfile.emails[0].value,
                    isAuthenticated: req.isAuthenticated(),

                };
                return next();
            }
            req.session.returnTo = req.originalUrl;
            res.redirect('/login');
        };
    };

Is this correct usage of await in such a case and in middelware for each page request and can I safely assume that the page wont load until the Booking.find promise is resolved?

Attempt 1 as suggested:

    module.exports = function () {
        return async function secured(req, res, next) {
            if (req.user) {
                let docs;

                try {
                    docs = await Booking.find((err, docs) => {
                        const bookingsCount = docs.length;
                        const userProfile = req.user;

                        res.locals = {
                            count: bookingsCount,
                            userProfile: JSON.stringify(userProfile, null, 2),
                            name: userProfile.displayName,
                            loggedInEmail: userProfile.emails[0].value,
                            isAuthenticated: req.isAuthenticated(),
                        };
                    });

                    return next();
                } catch (err) {
                    console.log(err);
                    return next(err);
                }
            }
            req.session.returnTo = req.originalUrl;
            res.redirect('/login');
        };
    };

Booking model as requested:

    const mongoose = require('mongoose');

    const bookingSchema = new mongoose.Schema({
      firstName: {
        type: String,
        required: 'This field is required',
      },
      lastName: {
        type: String,
        required: 'This field is required',
      },
      tourType: {
        type: String,
        required: 'This field is required',
      },
      dateBooked: {
        type: String,
        required: 'This field is required',
      },
      tourDate: {
        type: String,
        required: 'This field is required',
      },
      pax: {
        type: String,
        required: 'This field is required',
      },
      phone: {
        type: String,
        required: 'This field is required',
      },
      customerEmail: {
        type: String,
        required: 'This field is required',
      },
      pickupAddress: {
        type: String,
        required: 'This field is required',
      },
      operatorName: {
        type: String,
        required: 'This field is required',
      },
      paidStatus: {
        type: String,
        required: 'This field is required',
      },
      notes: {
        type: String,
      },
      guidesName: {
        type: String,
      },
      guidesEmail: {
        type: String,
      },
      bookingCreatedSent: {
        type: Boolean,
      },
      calendarEventCreated: {
        type: Boolean,
      },
      clientReminderSent: {
        type: Boolean,
      },
      remindOperators: {
        type: Boolean,
      },
      remindGoCapeGuides: {
        type: Boolean,
      },
      feedbackSent: {
        type: Boolean,
      },
    });

    // Custom validation for email
    bookingSchema.path('customerEmail').validate((val) => {
      emailRegex = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
      return emailRegex.test(val);
    }, 'Invalid e-mail.');

    mongoose.model('Booking', bookingSchema);

DB.js - model is dependent

const mongoose = require('mongoose');
require('dotenv').config();
env = process.env.NODE_ENV;
envString = env;

// mongoDB connection string
const url = process.env['MONGO_DB_URL' + envString];
console.log(url);
mongoose.connect(url, {useNewUrlParser: true, useUnifiedTopology: true, useFindAndModify: false})
    .then(() => {
      console.log('connected!', process.env.PORT || '8000');
    })
    .catch((err) => console.log(err));

//db.close();

require('./booking.model');

Use-able attempt:

    module.exports = function() {
    return async function secured(req, res, next) {
        if (req.user) {
        const Booking = require('../model/booking.model');
        let docs;

        try {
            docs = await Booking.find((err, docs) => {
            const bookingsCount = docs.length;
            const userProfile = req.user;

            res.locals = {
                count: bookingsCount,
                userProfile: JSON.stringify(userProfile, null, 2),
                name: userProfile.displayName,
                loggedInEmail: userProfile.emails[0].value,
                isAuthenticated: req.isAuthenticated(),
            };
            });

            return next();
        } catch (err) {
            console.log(err);
            return next(err);
        }
        }
        req.session.returnTo = req.originalUrl;
        res.redirect('/login');
    };
    };       
ZADorkMan
  • 301
  • 4
  • 19

1 Answers1

1

In your updated code, you are both trying to use await and callback.

Also to catch errors in await, we need to use try catch block.

So you can rewrite your function like this:

const mongoose = require("mongoose");
const Booking = mongoose.model("Booking");

module.exports = function() {
  return async function secured(req, res, next) {
    if (req.user) {
      let docs;

      try {
        docs = await Booking.find();

        const bookingsCount = docs.length;
        const userProfile = req.user;

        res.locals = {
          count: bookingsCount,
          userProfile: JSON.stringify(userProfile, null, 2),
          name: userProfile.displayName,
          loggedInEmail: userProfile.emails[0].value,
          isAuthenticated: req.isAuthenticated()
        };
        return next();
      } catch (err) {
        console.log(err);
        return next(err);
      }
    }
    req.session.returnTo = req.originalUrl;
    res.redirect("/login");
  };
};

And to solve the problem in the original code, you need to move the res.locals related code inside to the Find callback like this so that it only works after Find worked.

module.exports = function() {
  return function secured(req, res, next) {
    if (req.user) {
      const mongoose = require("mongoose");
      const Booking = mongoose.model("Booking");
      Booking.find((err, docs) => {
        bookingsCount = docs.length;
        const userProfile = req.user;
        res.locals = {
          count: bookingsCount,
          userProfile: JSON.stringify(userProfile, null, 2),
          name: userProfile.displayName,
          loggedInEmail: userProfile.emails[0].value,
          isAuthenticated: req.isAuthenticated()
        };

        return next();
      });

      next();
    }
    req.session.returnTo = req.originalUrl;
    res.redirect("/login");
  };
};

UPDATE:

You need to export your model in booking after schema code like this:

module.exports = mongoose.model('Booking', bookingSchema);

And import it like this in your function:

const Booking = require("../models/booking"); //TODO: update your path

Instead of this line:

const Booking = mongoose.model("Booking");
SuleymanSah
  • 17,153
  • 5
  • 33
  • 54
  • Thank you, great, I see your logic with the try can catch however implementing your snippets produces `Booking is not defined` - I added my implementation attempt to my question. – ZADorkMan Jan 10 '20 at 12:16
  • @ZADorkMan better to export Booking model wher you define booking schema, can you add the schema code to the question so that I can show what to do. – SuleymanSah Jan 10 '20 at 12:17
  • @ZADorkMan I made an update at the end of my answer, can you make those changes and try? – SuleymanSah Jan 10 '20 at 12:40
  • Aha of course, I forgot to add the model call. But I added `const mongoose = require('mongoose'); const Booking = mongoose.model('Booking');` to just below `if (req.user) {` and it seems to work, but if I may ask, why do you suggest updating the model export instead? – ZADorkMan Jan 10 '20 at 12:46
  • @ZADorkMan you need to specify schema when using mongoose.model. Please try my code. – SuleymanSah Jan 10 '20 at 12:47
  • @ZADorkMan please exports and import Booking model as I showed in the answer. – SuleymanSah Jan 10 '20 at 12:57
  • Thank you @SuleymanSah I have added the complete code to my questions 'usable attempt' at the bottom - is that how you meant? Everything seems to run now but I am unsure of why it is better except for the try/catch. I will continue to analyze your suggestions and test that changing `mongoose.model('Booking', bookingSchema);` to `module.exports = mongoose.model('Booking', bookingSchema);` creates no breaks anywhere. – ZADorkMan Jan 10 '20 at 13:15
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/205752/discussion-between-suleymansah-and-zadorkman). – SuleymanSah Jan 10 '20 at 13:21