I am following a tutorial to make a blog, and for the MongoDB connection in the server.js file, the instructor made a boiler connection function withDB. Operations and res are props of withDB function. In line 6, is operations a function passed a prop of the withDB functions?
Below is the withDB function.
const withDB = async (operations, res) => {
try {
const client = await MongoClient.connect('mongodb://localhost:27017', { useNewUrlParser: true });
const db = client.db('my-blog');
await operations(db); // is operations a function that takes db as its props?
client.close();
} catch (error) {
res.status(500).json({ message: 'Error connecting to db', error });
}
}
Using withDB in a function
app.get('/api/articles/:name', async (req, res) => {
withDB(async (db) => {
const articleName = req.params.name;
const articleInfo = await db.collection('articles').findOne({ name: articleName })
res.status(200).json(articleInfo);
}, res);
})
yes actually operations is your callback function, you call it with db as param once you initialize your database connection.
Maybe you're not comfortable with ES6 arrow function syntax. you can find in Mdn doc a simple example with old regular function, and in your case it could be :
function findArticlesByName(articleName) {
return function(db) {
return db.collection('articles').findOne({ name:
articleName });
}
}
async function withDB(callback) {
try {
const client = await MongoClient.connect('mongodb://localhost:27017', { useNewUrlParser: true });
const db = client.db('my-blog');
return callback(db);
} catch (error) {
throw new Error({ message: 'Error connecting to db', error });
} finally {
client?.close();
}
}
app.get('/api/articles/:name', async (req, res) => {
try {
const articleInfo = await withDB(findArticlesByName(req.params.name));
res.status(200).json(articleInfo);
} catch(error) {
res.status(500).json(error);
}
})
Conclusion, you could easily inline your callback function like in your example, but maybe it's more understandable that way.
Moreover, you should avoid to use a wrapper in order to create and close your db connection after each request, because it could occur some weird errors. Databases connections or any resource-intensive tasks should be shared as much as possible.
So a better solution is to create a specific class with the default implementation of your singleton, construct a single instance of your connection at the top of your app, pass the single instance into each module that needs it then close your connection just before exiting your app.
Hope it'll help.
Related
I'm creating all users from Firebase console, Now I wanted copy all users to realtime db under users node.
Below is my Google cloud function
exports.getAllUsers = functions.https.onRequest((req, res) => {
cors(req, res, () => {
app.auth().listUsers(1000)
.then((listUsersResult) => {
var db = admin.database();
var ref = db.ref("users");
const usersRef = ref.child('users');
usersRef.set(listUsersResult, { merge: true } );
ref.once("value", function (snapshot) {
console.log(snapshot.val());
});
// admin.database().ref('users').set(listUsersResult, { merge: true })
res.send(listUsersResult);
})
.catch(function (error) {
console.log('Oh no! Firebase listUsers Error:', error);
});
});
});
By using firebase-admin, I could get all the users list but When I tried to update the DB i'm getting the below error
Oh no! Firebase listUsers Error: Error: Reference.set failed: onComplete argument must be a valid function.
How can I fix this?
You're passing { merge: true } to the call to set, but that option only exists in the Firestore API - not in the API of the Realtime Database. So:
usersRef.set(listUsersResult);
In addition, you need to handle the asynchronous nature of set and once as Renaud commented. So combining the above with that gets to:
usersRef.set(listUsersResult)
.then(() => {
ref.once("value", function (snapshot) {
console.log(snapshot.val());
res.send(listUsersResult);
});
});
That leaves the fact that your usersRef refers to /users/users in the database, which you'll probably also want to fix.
why i should commmented the code
await mongoose.connection.close()
to make it work ?
here is my code:
const { MongoClient } = require("mongodb");
const mongoose = require('mongoose');
require('dotenv').config();
async function main() {
const uri = process.env.MONGO_URL;
try {
// Connect to the MongoDB cluster
await mongoose.connect(uri)
.then(()=> console.log("connect Succes"))
.catch((err) => {console.error(err)});
await createListing();
} finally {
// Close the connection to the MongoDB cluster
await mongoose.connection.close()
console.log("connect Closed")
}
}
main().catch(console.error);
async function createListing() {
const piscSchema = new mongoose.Schema({
name: String,
date: { type: Date, default: Date.now },
pool: String,
point: Number,
isAdd:Boolean
});
const piscModel = mongoose.model('piscModel', piscSchema, 'PointPiscine');
var itemPisc = new piscModel({
date:"12.10.2022",
pool:"dsfs",
point: 70,
isAdd:false
});
itemPisc.save(function (err, res) {
if (err) console.error(err);
console.log(res)
});
console.log("fin function call")
}
when i am not commented the code that close the connection.
i got this message
it is strange because it is connected to my mongodb.
as you can see the console log:
connect Succes
fin function call
connect Closed
You are calling the following function using a callback
itemPisc.save(function (err, res) {
if (err) console.error(err);
console.log(res)
});
This way the code continues to run without waiting for the result of this operation. It will then close the database connection without waiting for the result of this save function, which leads to this error.
If you modify you function the following way, it should wait for the result and close the connection afterwards:
try {
console.log(await itemPisc.save());
} catch (err) {
console.log(err);
}
You see the function 'main' is asynchronous, all those async functions are called asynchronously. You can try calling it with .then and do everything else in that .then:
... All the previous code
main().then(()=> {
async function createListing() {
...
// All the other code
})
Please comment on this line await mongoose.connection.close() then try I hope work it perfectly!!
I seem to be getting undefined from my async code, and none of the answers I have found seem to work.
So my setup is router calls a controller, which calls a service, which calls a database. The controller is like so (removed some validation etc to reduce code)
const { userService } = require('../services/index.services');
const postUser = async (req, res) => {
try {
const user = {
user_id: req.body.userId,
user_status: req.body.userStatus,
created_at: new Date(),
updated_at: new Date(),
};
const result = await userService.createUser(user);
console.log(result);
} catch (err) {
return apiResponse.ErrorResponse(res, err);
}
};
module.exports = {
postUser,
};
You can see I have a console log of the result. Then my service class is like so
const { addUserToDb } = require('../database/users.db');
const createUser = async (user) => {
try {
const createdUser = await addUserToDb(user);
console.log(createdUser);
} catch (err) {
throw new Error(err);
}
};
module.exports = {
createUser,
};
That one also has a console log. Then finally users.db
const pool = require('./database');
const addUserToDb = (user) => {
pool.getConnection((err, connection) => {
if (err) {
throw new Error(err);
}
connection.query('INSERT INTO `users` SET ?', user, (err) => {
if (err) { throw new Error(err); }
pool.releaseConnection(connection);
return user;
});
});
};
module.exports = {
addUserToDb,
};
So you can see this adds the user to the database and returns the user. So my user is successfully being inserted into the database.
My question is why am I getting undefined in both my console logs? I am returning from all modules which is where I noticed a lot of other people had issues because of no returns.
So can someone explain why these may be undefined?
Thanks
It's because in your service class you have,
const createdUser = await addUserToDb(user);
But addUserToDb in users.db doesn't return a promise.
addUserToDb is a regular function that executes the query using callbacks.
Hence, even though you have return user; nested within the 2nd callback, it never returns the user value back to createUser in the service class as callbacks are asynchronous.
In fact, addUserToDb function in users.db doesn't wait for the callback to complete and thus returns undefined back to the service class.
And hence you see undefined being logged in your service class.
async/await only works with promises, not callbacks.
You need to refactor ./database module to use promises instead of callbacks for pool.getConnection and connection.query.
I use mysql2 npm module as it has a promise wrapper,
https://www.npmjs.com/package/mysql2#using-promise-wrapper
And the reason why console.log(result); in your controller is undefined is because you don't return anything from your service class.
You need to add return createdUser; in your service class's createUser method.
I am building tests for my node/express controller methods and using #shelf/jest-mongodb. I am creating a document first, and then when I try to find that I have to run find twice from model in order to get the results. It should get the results in the first find instead.
test.js
const { Subscription } = require('../src/models/subscription.schemaModel'); // model
const {
createSubscription,
} = require('../src/controllers/subscription.controller');
const subData = {...};
beforeAll(async () => {
await mongoose.connect(
process.env.MONGO_URL,
{ useNewUrlParser: true, useUnifiedTopology: true },
(err) => {
if (err) {
console.error(err);
process.exit(1);
}
}
);
});
afterAll(async () => {
await mongoose.connection.close();
});
describe('creates a subscription ', () => {
it('can be created correctly', async () => {
const sub = await createSubscription(subData);
await Subscription.find(); // if I comment out this line, I would get 0 results.
const subs = await Subscription.find();
expect(subs[0].items[0].sku).toBe(233234);
});
});
subscription.controller.js
const Mongoose = require('mongoose');
const { Subscription } = require('../models/subscription.schemaModel');
const isTestEnv = process.env.NODE_ENV === 'test';
module.exports.createSubscription = async (data) => {
try {
let error = null;
const doc = new Subscription(data);
doc.accountId = Mongoose.Types.ObjectId(doc.accountId);
await doc.save(function (err) {
if (err) {
logger.error(`createSubscription saving ${err}`);
error = err;
}
});
if (!error) {
logger.info(
`Subscription created => id: ${doc._id} store: ${doc.store}`
);
return doc;
} else {
return error;
}
} catch (err) {
logger.error(`createSubscription ${err}`);
}
};
The schemaModel file essentially contains the schema and exports model. Everything seems to work fine if I would do all the operations in the test file (schema+model+controller module)which defeats the purpose of testing my modules but not if I am importing. In this case I would have to run find() twice to get the results.
I have been trying multiple things from what I could find from googling, but no luck! Any help or lead would be appreciated. Also let me know if you need any other details.
Thank you!!
The only problem that posted code contains is that Mongoose promise API is mixed with legacy callback API. It appears that save results in race condition that is has been circumvented by random delay that extra find provides.
Although Mongoose documentation mentions that methods unconditionally return promises, a common pattern for JavaScript APIs that support both promises and callbacks is to enable promise control flow by omitting callback argument, and vice versa. This is most likely what happens here.
A way to avoid race conditions in such cases is to stick to promise control flow, e.g.:
beforeAll(async () => {
try {
await mongoose.connect(
process.env.MONGO_URL,
{ useNewUrlParser: true, useUnifiedTopology: true },
)
} catch (err) {
console.error(err);
process.exit(1);
}
});
I am playing around with promises, I have the following code to access my mongodb:
MongoClient.connect(url, { useUnifiedTopology: true })
.then(client => {
const db = client.db(dbName);
return db.collection('dogs');
})
.then(collection => collection.find().toArray())
.then(array => console.log(array))
// Client is not defined, how do I access it?
.finally(() => client.close())
.catch(error => {
console.log(error);
});
I can't access the client inside finally. Is there a good pattern to achieve this?
You can either use async/await:
const client = await MongoClient.connect(url, { useUnifiedTopology: true })
const db = client.db(dbName);
const dogs = db.collection('dogs');
...
When you have operations like find you can await them or using then as you did. If you are not very confident with promises, asyn/await might be clearer for you.
You can also await chained promises with then...
With async/await you can use try/catch/finally, that might be an easier solution for you.
You can wrap your code with an async function in order to use await, and create a variable before the try and catch to save the client, the code will be as follows:
const functionName = async () => {
let client;
try {
client = await MongoClient.connect(url, { useUnifiedTopology: true });
const db = client.db(dbName);
const collection = db.collection("dogs");
const array = collection.find().toArray();
console.log(array);
} catch (error) {
console.log(error);
} finally {
client.close();
}
};