I've built the following mongo client access bootstrap file:
import { MongoClient } from "mongodb";
let db = null;
// Connect to mongo
const uri = "mongodb://localhost/mydb";
const opts = { useUnifiedTopology: true };
const connect = async () => {
console.log("Connecting to database...");
let client = await MongoClient.connect(uri, opts).catch(error => {
console.log("Error connecting to database: " + err);
});
if (client) {
console.log("Database connected.");
db = client.db("mydb");
}
return client;
};
// Get database connection
const getDb = async () => {
if (!db) await connect();
return db;
};
// Get Collection
const getCollection = async name => {
let database = await getDb();
let collection = await database.collection(name);
if (!collection)
throw new Error("(mongo) Cannot get collection named " + name);
return collection;
};
export { db, getCollection };
When trying to acess the collection for the first time in another program:
import { getCollection } from "./mongoutils";
const init = async () => {
let user = await getCollection("users").findOne({ name: "Josh"});
console.log("User found!");
}
I'm getting the following error:
UnhandledPromiseRejectionWarning: TypeError: (0 , _mongo.getCollection)(...).findOne is not a function
How can I properly fix this error, keeping the whole structure async/await?
An async function returns a promise and not the resolved data.
Here getCollection() is an async function. So, calling getCollection("users") would return a promise and not the resolved collection itself as I presume what you are expecting. The correct way to do is:
import { getCollection } from "./mongoutils";
const init = async () => {
let userCollection = await getCollection("users");
try {
let user = await userCollection.findOne({ name: "Josh"})
console.log("User found!");
} catch (e) {
console.log("User not found!");
}
}
Related
I have the following code to create a connection to my MongoDB database, and to store it for future use.
const mongodb = require('mongodb');
const MongoClient = mongodb.MongoClient;
// The database will be defined once a connection to between the cluster and mongodb is created
let _database;
const uri = '';
const databaseName = 'db';
const mongoConnect = () => {
MongoClient.connect(uri)
.then((client) => {
_database = client.db(databaseName);
})
.catch((err) => {
console.log(err);
throw err;
});
};
const getDb = () => {
if (_database) {
return _database;
}
throw 'No database found!';
};
module.exports = {
mongoConnect,
getDb
}
My problem is that _database is undefined until the connection is made. If my website tries to use the database before _database is defined it will throw an error and crash.
I want to make it so instead of crashing, other portions of my code would just wait until _database is not undefined. Sounds like a await/async solution is needed, but I can't wrap my head around how to approach implementing something like that here. Any advice would be great!
First approach: To make mongoConnect an async function and await on it before any of the remaining code is executed.
const mongoConnect = async () => {
try {
const client = await MongoClient.connect(uri);
_database = client.db(databaseName);
} catch(e) {
console.log(err);
throw err;
}
};
In the beginning of your code
await mongoConnect();
//Remaning code here
Second approach: To make getDb function await till database connection is available
const mongodb = require('mongodb');
const MongoClient = mongodb.MongoClient;
const uri = '';
const databaseName = 'db';
const databasePromise = new Promise((resolve, reject) => {
MongoClient.connect(uri)
.then((client) => {
resolve(client.db(databaseName));
})
.catch((err) => {
reject(err);
});
})
const getDb = async () => {
return await databasePromise;
};
module.exports = {
getDb
}
Sample code for you to run and check the second approach:
const databasePromise = new Promise((resolve) => {
console.log("Connecting to db in 5 seconds...")
setTimeout(() => {
console.log("Done")
resolve("done")
}, 5000)
})
const getDb = async () => {
return await databasePromise;
};
console.time("First_getDb_call")
getDb().then(res => {
console.timeEnd("First_getDb_call")
console.log(res)
console.time("Second_getDb_call")
getDb().then(res => {
console.timeEnd("Second_getDb_call")
console.log(res)
})
})
**Pretty Simple approach ** Just add await before MongoClient.connect function and make function async Now It will wait for the connection to have response then move forward.
const mongodb = require('mongodb');
const MongoClient = mongodb.MongoClient;
// The database will be defined once a connection to between the cluster
and mongodb is created
let _database;
const uri = 'mongodb://localhost:27017/mydb';
const databaseName = 'db';
const mongoConnect = async () => {
await MongoClient.connect(uri)
.then((client) => {
_database = client.db(databaseName);
})
.catch((err) => {
console.log(err);
throw err;
});
};
const getDb = () => {
if (_database) {
return _database;
}
throw 'No database found!';
};
module.exports = {
mongoConnect,
getDb
}
The following code is the default async run function for the MongoDB JS driver.
async function run() {
try {
await client.connect();
const database = client.db('sample_mflix');
const movies = database.collection('movies');
// Query for a movie that has the title 'Back to the Future'
const query = { title: 'Back to the Future' };
const movie = await movies.findOne(query);
console.log(movie);
} finally {
// Ensures that the client will close when you finish/error
await client.close();
}
}
run().catch(console.dir);
Is there any way on earth to do the CRUD operations etc outside of that function, e.g in an expressjs endpoint?
Thanks.
You can have a function that connects to the DB, probably in a different file and export the function
// DB.js
async function connectToDatabase() {
try {
await client.connect();
return client.db('sample_mflix');
} catch(err) {
console.dir(err);
} finally {
await client.close();
}
}
Then import it if you're using the export method, or just call the function
app.get('/api/path', () => {
const db = await connectToDatabse();
if (db) {
const query = {
title: 'Back to the Future'
};
const movie = await db.collection('movies').findOne(query);
console.log(movie)
}
})
I have an async function 'query' that 'awaits' for the pool.query to return the results.
// db.js
const pool = new pg.Pool({
connectionString: isProduction ? process.env.DATABASE_URL : connectionString,
ssl: isProduction,
});
export const query = async ({ text, values }) => {
const start = Date.now();
try {
const results = await pool.query(text, values);
const duration = Date.now() - start;
logger.info(`executed query: ${text} duration: ${duration} rows: ${results.rowCount}`);
return results.rows;
} catch (e) {
logger.error(`error: ${e}`);
}
};
In another async function getUser() I'm 'awaiting' the query function to finish before returning the data.
// users.js
export const getUser = async (email) => {
const text = `
SELECT (user_id, email) FROM users
WHERE email = $1
`;
const values = [email];
try {
const data = await query({ text, values });
// ^ vscode says above await is doing nothing
return data.rows[0];
} catch (e) {
logger.error(`error: ${e}`);
}
};
Then in yet another async function I'm awaiting the getUser function
// auth.js
export const SignIn = async (email, password) => {
const userRecord = await getUser(email);
if (!userRecord) {
throw new Error('User not registered');
}
logger.silly('Checking password');
const validPassword = await argon2.verify(userRecord.password, password);
if (validPassword) {
logger.silly('Password is valid!');
logger.silly('Generating JWT');
const token = await generateToken(userRecord);
const user = { id: userRecord.id, email: userRecord.email };
return { user, token };
} else {
throw new Error('Invalid Password');
}
};
Inside vsCode I'm getting a warning "await has no effect on the type of this expression." only when 'awaiting' the query function call, but not when 'awaiting' the getUser function call. What am I missing here?
What does query return? If results.rows is a promise then that might explain what you're seeing.
Here is the index.ts script I am running (based on something I found on reddit):
const path = require("path");
const sql = require("mssql");
const config = require(path.resolve("./config.json"));
let db1;
const connect = () => {
return new Promise((resolve, reject) => {
db1 = new sql.ConnectionPool(config.db, err => {
if (err) {
console.error("Connection failed.", err);
reject(err);
} else {
console.log("Database pool #1 connected.");
resolve();
}
});
});
};
const selectProjects = async (name) => {
const query = `
select * from [Time].ProjectData where [Name] like concat('%', concat(#name, '%'))`;
const request = new sql.Request(db1);
const result = await request
.input("name", name)
.query(query);
return result.recordset;
};
module.exports = {
connect,
selectProjects
};
connect().then(function() {
console.log(selectProjects('General'));
}).catch(function(err) {
console.log(err);
});
When I run the script using node index (after compiling it of course), I get this in the console:
Database pool #1 connected.
Promise { <pending> }
And then the script hangs.
Apparently the await keyword creates an implicit promise; I had to change the last function call to:
connect().then(function() {
selectProjects('General').then(function(data) {
console.log(data);
});
}).catch(function(err) {
console.log(err);
});
I have this request handler on my node server. It has three MongoDB queries, and I want all the results to be returned, before the response is sent.
api.get('/getStats/:productID', (req,res)=>{
let data = {};
let dailySales = [];
let avgProduct = "";
let customers = [];
Sales.find({productID: productID}).then(
sales => {
dailySales = sales;
}
);
Products.find({}).then(
products => {
// Calculate Avg product here
avgProduct = result;
}
);
Customers.find({}).then(
customers => {
customers = customers;
}
);
data = {
dailySales,
avgProduct,
customers
};
res.json(data);
});
But running this returns
data: {
dailySales: [],
avgProduct: "",
customers: []
}
i.e. The Mongo response is returning before the data is run. Please how to I fix. Thank You
wait for all the promises to resolve before sending the actual response
const sales = Sales.find({productID: productID});
const allProducts = Products.find({});
const allCustomers = Customers.find({});
Promise.all([sales, allProducts, allCustomers])
.then(data => res.json(data));
you can try using the Promise.all where you can pass the MongoDB queries as parameter to it ,the promise will be resolved when all the queries return the result in the array
Try using the in-built util.promisify function along with
async-await to get data correctly!
const promisify = require('utils').promisify;
const salesFindOnePromise = promisify(Sales.find);
const productsFindAllPromise = promisify(Products.find);
const customersFindAllPromise = promisify(Customers.find);
findDailySalesByIdAsync = async (productID) => {
try {
return await salesFindOnePromise({ productID: productID });
} catch(err) {
throw new Error('Could not fetch the appropriate sales with productID');
}
}
findProductsAsync = async () => {
try {
return await productsFindAllPromise({});
} catch (err) {
throw new Error('Could not fetch sales!');
}
}
findCustomersAsync = async () => {
try {
return await customersFindAllPromise({});
} catch (err) {
throw new Error('Could not fetch customers!');
}
}
api.get('/getStats/:productID', async (req,res)=>{
try {
const dailySales = await findDailySalesByIdAsync(productID);
const avgProduct = await findProductsAsync();
const customers = await findCustomersAsync();
const data = {
dailySales,
avgProduct,
customers
};
return res.status(200).send(data);
} catch(err) {
console.err(`Failed because: {err}`);
throw new Error('Could not fetch data because of some error!');
}
});