Trouble with callbacks, error catching and MongoDB - javascript

I've been working on an application which allows me to add companies to a database. Originally my code was pure spaghetti, so I wanted to modularize it properly. For this purpose, I added routes, a controller and a dao.
This is how my code looks right now
Routes
app.post('/loadcompanies', (req, res)=> {
companiesController.loadcompany(req.body, (results)=>{
console.log(results);
res.send(200, "working!");
})
})
Controller
module.exports.loadCompany = (body, callback)=>{
companiesDao.loadCompany(body, callback);
}
Dao
module.exports.loadCompany = (company, callback)=>{
MongoClient.connect(conexionString, (err, database) => {
if (err) console.log(err);
db = database;
console.log(company);
db.collection('companies').insert(company, (err, result)=>{
callback({message:"Succesfully loaded company", company:result});
});
})
}
My current concern is that working with errors when modularizing like this is confusing. I tried adding a try-catch method around the db insert and throwing and error if there is one, but that doesn't seem to work. Other things I've tried is returning the error in the callback, like this:
if (err) callback (err, null);
but I end up getting a "Can't set headers after they are sent." error.
How would you handle errors in this situation? For example, in the case that someone tries to add a duplicate entry in an unique element.

You should be able to simply do the error checking inside the callback for the insert function:
db.collection('companies').insert(company, (err, result)=>{
if (err) {
callback(err, null);
return;
}
callback(null, {message:"Succesfully loaded company", company:result});
});
If you get an error like you say, that's probably because the database is actually returning an error. You could also make your errors more specific, like:
module.exports.loadCompany = (company, callback)=>{
MongoClient.connect(conexionString, (err, database) => {
if (err) {
callback(new Error('Connection error: ' + err.Error());
return;
}
db = database;
console.log(company);
db.collection('companies').insert(company, (err, result)=>{
if (err) {
callback(new Error('Insertion error: ' + err.Error());
return;
}
callback(null, {message:"Succesfully loaded company", company:result});
});
})

Here is your loadCompany done in async / await format.
Notise there is no need for error checking, errors will propagate as expected up the promise chain.
Note I've also changed loadCompany to be an async function too, so to call it you can simply do var ret = await loadCompany(conpanyInfo)
module.exports.loadCompany = async (company)=>{
let db = await MongoClient.connect(conexionString);
console.log(company);
let result = await db.collection('companies').insert(company);
return {message:"Succesfully loaded company", company:result};
}

Related

ReferenceError: err is not defined

I'm started working on my very first API using Mongo, Express and Node. When i tried to make API endponit for one specific user, console throw error ReferenceError: err is not defined. An error appears in the method I already used for auth part, and there it worked fine. The part of code where is the error, on line 5:
exports.userById = (req, res, next, id) => {
User.findById(id).exec(() => {
if(err || !user) {
return res.status(400).json({
err: "User not found"
});
}
req.profile = user //adds profile object in req with user info
next();
});
}
Also, the part of code where I tried to get a single user:
exports.getUser = (req, res) => {
req.profile.hashed_password = undefined;
req.profile.salt = undefined;
return res.json(req.profile);
}
I don't think the problem could be here, but there is also route line from routes file
router.get("/users/:userId", authController.requireSignin, userController.getUser);
Thanks everyone for the help!
I'm pretty sure err comes from exec:
User.findById(id).exec(err => {...});
Edit I guess you want to search by id and return something. Try this.
User.findById(id, (err, user) => {
// if error display errort
if(err) console.error(err);
// if user do not exists
if(!user) {// what is user ? the doc result ?
return res.status(400).json({
"err": "User not found" // i think use ""
});
}
req.profile = user //adds profile object in req with user info
next();
});

Why Is An Error Returning A Whole Document

I am trying to get a document using findOne, I have a simple error handler (if (err) console.log(err);), but is returning a whole document. I can't get past this if statement. If I remove the if (err), then it gets caught by another statement which says the document does not exist.
Using Mongoose 5.4.0, have tried removing the if statement but just gets caught by the others saying it doesn't exist. I'm also using discord.js, so wherever it says message.reply or message.channel.send - it just means that it will send a message to a channel - this has nothing to do with the error.
guildModel.findOne({"GuildName": GuildSearch}).then((err, result) => {
if (result) {
let guildEmbed = new Discord.RichEmbed()
.setTitle(GuildSearch)
.setColor("00ff65")
.setDescription(result.GuildDescription);
return message.channel.send(guildEmbed);
} else {
let NoDoc = new Discord.RichEmbed()
.setTitle("Oops!")
.setDescription(`<#!${message.author.id}>, There is no Server with the name ${GuildSearch} recorded with me.`)
.setColor("ff7f00")
.setFooter("Developed By William#8495");
return message.channel.send(NoDoc);
};
}).catch(err => {
return message.reply("Error: " + err);
});
It should just send a field of the document named GuildDescription, but it sends Error: and the whole document.
You are using "Promise style" query so the first argument should be result, not err:
guildModel.findOne({ "GuildName": GuildSearch }).then(result => {
}).catch(err => {
console.log(err)
})
Or you could use the other syntax:
guildModel.findOne({ "GuildName": GuildSearch }, (err, result) => {
if (err) console.log(err)
});
Mongoose documentation

how to break logic into a controller and a model in a node

I do not quite understand how to properly break the logic on the controllers and models in nodeJS when working with the backend application. Suppose I have an example
This code is in the model of my application, and logically I understand that the model is only responsible for choosing from the database, and the controller and everything else should be done by the controller, but I don’t quite understand how to do this and I tried to transfer part of the code to the controller and export it, but I did not succeed (Please, help, at least with this example! The main thing for me is to understand the principle of working with MVC in the node !!!
exports.currentPostPage = function(req, res){
db.query('SELECT * FROM `posts`', function (err, result) {
if (err){
console.log(err);
}
var post = result.filter(item => {return (item.id == req.params.id)? item: false})[0];
if (post === undefined){
res.render('pages/404');
} else {
res.render('pages/post-page', {postId: req.params.id, item: post});
}
});
};
So, you're on the right track. There's a lot of different ways to do it depending on preferences, but one pattern I've seen pretty commonly is to use the callback as a way to integrate. For example, let's say you have your model file:
exports.getPostById = (id, cb) => {
db.query('SELECT * FROM `posts` WHERE id=?', [id], function (err, result) {
if (err){
return cb(err); // or, alternatively, wrap this error in a custom error
}
// here, your logic is just returning whatever was returned
return cb(null, result);
});
};
Note I also am letting the DB handling the ID lookup, as it's probably more efficient at doing so for larger data sets. You didn't say what DB module you're using, but all the good ones have some way of doing parametrized queries, so use whatever works w/ your DB driver.
Anyway, the Model file therefore handles just the data interaction, the controller then handles the web interaction:
// postController.js
const model = require('../models/postModel.js'); // or whatever you named it
exports.populatePost = (req, res, next, id) => {
model.getPostById(id, (err, post) => {
if (err) return next(err); // centralized error handler
req.post = post;
next();
});
}
export.getOnePost = (req, res, next) => {
if (req.post) {
return res.render('pages/post-page', req.post);
}
// again, central error handling
return next({ status: 404, message: 'Post not found' });
}
I have mentioned central error handling; I vastly prefer it to scattering error handling logic all over the place. So I either make custom errors to represent stuff, or just do like above where I attach the status and message to an anonymous object. Either will work for our purposes. Then, in a middleware file you can have one or more handler, the simplest like this:
// middleware/errors.js
module.exports = (err, req, res, next) => {
console.error(err); // log it
if (err.status) {
return res.status(err.status).render(`errors/${err.status}`, err.message);
}
return res.status(500).render('errors/500', err.message);
}
Finally, in your routing setup you can do things like this:
const postController = require('../controllers/postController');
const errorHandler = require('../middleware/errors.js');
const postRouter = express.Router();
postRouter.param('postId', postController.populatePost);
postRouter.get('/:postId', postController.getOnePost);
// other methods and routes
app.use('/posts', postRouter)
// later
app.use(errorHandler);
As was pointed out in the comments, some folks prefer using the Promise syntax to callbacks. I don't personally find them that much cleaner, unless you also use the async/await syntax. As an example, if your db library supports promises, you can change the model code to look like so:
exports.getPostById = async (id, cb) => {
// again, this assumes db.query returns a Promise
return await db.query('SELECT * FROM `posts` WHERE id=?', [id]);
}
Then your controller code would likewise need to change to handle that as well:
// postController.js
const model = require('../models/postModel.js'); // or whatever you named it
exports.populatePost = async (req, res, next, id) => {
try {
const post = await model.getPostById(id)
req.post = post
return next()
} catch (err) {
return next(err)
}
}

Async confusion in nodejs function

I always have multiple operations in one route or endpoint. Take an example below, when a user deletes an item, I want the related file be deleted in s3 too besides deleting related collection from the database.
So is the code below ok? Does it matter if I put the first function (delete file from s3) inside the DeleteItem function?
router.post('/item/delete', function(req, res) {
if(req.body.dlt_item){
var tempArray = [];
tempArray.push({"Key":req.body.dlt_item});
s3Bucket.deleteObjects({
Bucket: 'myS3',
Delete: {
Objects: req.body.dlt_item
}
}, function(err, data) {
if (err)
return console.log(err);
});
}
Item.DeleteItem(req.body.item_id, function(err,result){
if(err){console.log(err)}
res.send({result:1});
})
});
You should organise your code like this. This will ensure that s3 deletion will start only when mongodb deletion has finished.
In your code both things happen simultaneously. this may cause issue in some cases.
If one fails and other succeeds then there will be trouble. Suppose s3 files get deleted successfully and mongo deletion fails. Then you will have many references to non existing resources.
router.post('/item/delete', function(req, res) {
if(req.body.dlt_item){
var tempArray = [];
tempArray.push({"Key":req.body.dlt_item});
Item.DeleteItem(req.body.item_id, function(err,result){
if(err)
{
console.log(err)
res.send(err);
}
else
{
//deletion from mongodb is succesful now delete from s3
s3Bucket.deleteObjects({
Bucket: 'myS3',
Delete: {
Objects: req.body.dlt_item
}
},function(err, data) {
if (err)
{
// deletion from s3 failed you should handle this case
res.send({result:1});
return console.log(err);
}
else
{
// successful deletion from both s3 and mongo.
// If you do not want to wait for this then send the response before this function.
res.send({result:1});
}
});
}
})
});

How get values out of a callback? (NodeJs + Mongoose)

I'm not too good with callbacks, and now I have problems to find a document with mongoose but use the document in the same action/controller before send a response.
uploadFile = function(req,res) {
var _objs = {};
function retrieveUser(objs,username, callback) {
User.findOne({ 'username': username })
.exec(function(err, user){
if(err) callback(err,null,null);
else callback(null,user,objs);
});
}//retrieveUser()
retrieveUser(_objs,req.body.user,function(err,user,_objs) {
if(err) console.log('ERROR: ' + err);
_objs.user = user;
console.log(_objs.user);
});
console.log(_objs);
}
So, inside the callback function the console.log() shows the object rightly, but the second console.log() shows me _objs as empty. Well, I need to fill _objs with other objects as attributes, How can I acchieve that?
Everything is working as designed. Your code will be executed as follows:
retrieveUser(_objs,req.body.user,function(err,user,_objs) {
// will be executed when the retrieveUser function completes is tasks...
if(err) console.log('ERROR: ' + err);
_objs.user = user;
console.log(_objs.user);
});
// ...meanwhile, execution will continue here.
console.log(_objs); // depending on how fast retrieveUser calls the callback, _objs will be set or (more likely) not set.
You can either continue the control flow of your application inside the callback or use frameworks like Streamline.js, async.js, Step, Seq, IcedCoffeeScript, promise.js, ...
Well this is what I did. At really for what I expected that is something similar to what I do in controllers in others frameworks of others languages, query for some entities and create another one that will be related to the previous that was queried.
uploadPic = function(req, res, next) {
var username = req.body.user,
estId = req.body.est;
async.series([
function(callback) {
User.findOne({ 'username': username})
.exec(function(err, user){
if(err) return callback(err);
if(!user) return callback(new Error("No user whit username " + username + " found."));
callback(null,user);
});
},
function(callback) {
Est.findById(estId)
.exec(function(err,est) {
if(err) return callback(err);
if(!est) return callback(new Error("No Est with ID " + estId + " found"));
callback(null,est);
})
},
],function(err,results){
if(err) return next(err);
console.log(results);
/// do my own stuffs here!
});// end async.series()
}

Categories

Resources