After form submission, new data does not appear without page reload - node.js

In my ExpressJS app I have two routes to the same location, one for 'get' and one for 'post'.
On the 'get' page it dumps all the documents from my MongoDB collection, via MongooseJS, followed by a form to add a new record to the collection.
On the 'post' page it takes in the form data and adds it to the collection, and then displays the same page you see via 'get'.
It works, but after the form is submitted the new record doesn't appear unless I reload the page. I put the code that renders the page at the very bottom, below the part that adds the data to the collection so in my mind that should work but it doesn't.
exports.index = function(req, res){
Server.find({},
function(err, docs) {
if (!err){
res.render('servers', { title: 'verify', results: docs});
}
else { console.log(err);}
}
);
}
exports.add = function(req, res){
newServer = new Server({
name: req.body.name,
os: req.body.os,
osVersion: req.body.osVersion
});
newServer.save(function (err) {
if (err) {
console.log(err);
}
});
Server.find({},
function(err, docs) {
if (!err){
res.render('servers', { title: 'verify', results: docs});
}
else { console.log(err);}
}
);
}

Ok, I seem to make this mistake over an over again with callbacks. I fixed the problem.
exports.add = function(req, res){
newServer = new Server({
name: req.body.name,
os: req.body.os,
osVersion: req.body.osVersion
});
newServer.save(function (err) {
if (err) {
console.log(err);
} else {
Server.find({},
function(err, docs) {
if (!err){
res.render('servers', { title: 'verify', results: docs});
}
else { console.log(err);}
}
);
}
});
}

Yes, those DB queries are async, so callbacks will solve this issue. However, you should look into using promises. Mongoose returns promises by default or you could import your library of choice. They will come in handy when dealing with nested callbacks and queries and also Error handling.

Related

Mongoose find not returning the full collection

I am trying to read data from an html form through a POST, store it in a mongoDB and query it using model.find() and print it in console. But when i run this for the first time the find() is returning an empty object and on giving the next input the previous data excluding the current input is retrieved by th find(). How can i print the full collection including the freshly entered data
app.post("/", function(req, res){
postTitle = req.body.postTitle;
postDesc = req.body.postDesc;
const post = new Post({
title:postTitle,
desc:postDesc
});
post.save();
Post.find({}, function(err, data){
if(err){
console.log(err);
}else{
console.log(data);
}
});
//console.log(postTitle, postDesc);
});
The command post.save(); will just begin working and your code will continue meanwhile. When your Post.find({} ... starts working, your post.save(); haven't finished working, and thus you're not getting the results.
Change the function so you wait for the save to give you a callback with an ok and then you can query the database.
app.post("/", function(req, res) {
const postTitle = req.body.postTitle;
const postDesc = req.body.postDesc;
const post = new Post({
title: postTitle,
desc: postDesc
});
post.save(function(err) {
if (err) {
// Something went wrong with the save, log and return the error message
console.error(err);
return res.send(err);
}
console.log(`Post "${postTitle}" saved to database.`);
// Since we know that the post has been saved, continue querying the database.
Post.find({}, function(err, data) {
if (err) {
// Something went wrong with the query, log and return the error message
console.error(err);
return res.send(err);
}
console.log(data);
res.send(data);
});
});
});
This code is not tested.
You can also try async/await out, see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function and also mongoose documentation for promises & async/await https://mongoosejs.com/docs/promises.html.
I myself would write the function like this using async/await and es6.
app.post('/', async(req, res) => {
const post = new Post({
title: req.body.postTitle,
desc: req.body.postDesc
});
try {
await post.save();
const posts = await Post.find();
console.log(posts);
} catch (err) {
console.error(err);
}
res.end();
});
You can try with exec
Post.find({}).exec(function (err, d) {
if(err){
console.log(err);
}else{
console.log(d);
}
});
Or try to use async await to make sure your query is running step by step
const user_info = await Post.find({});
This code is not tested
here post.save() is an async function that means it does not complete immediately. You need to use async - await in order to wait for the save() function to finish and then you query the database.

Query multiple mongodb collections in node app using Mongoose async

In my node.js app I want to query multiple mongodb collections in a series using mongoose with the async plugin based on result from the first callback in the series.
So far I have this working code but I'm sure there is a better way of doing it async:
router.route('/project/:projectId')
.get(function(req, res) {
var getProjectDetails = function(cb) {
models.Project.findById(req.params.projectId, function(err, project) {
if(err)
res.send(err);
models.Media.find({'project_code' : project.code }, function(err, media) {
cb(null, {'project' : project, 'media': media})
});
})
}
async.parallel([getProjectDetails], function(err, result) {
res.render('details', {data: result});
});
});
As you can see I want to find all entries from the Media collection where project_code equals code from Project collection.
How can I acvhieve this without nesting my mongoose-querys?
Why do you need async in this? I hope this will work.
router.route('/project/:projectId')
.get(function(req, res) {
models.Project.findById(req.params.projectId, function(err, project) {
if(err)
return res.send(err);
models.Media.find({'project_code' : project.code }, function(err, media) {
if(err)
return res.send(err);
return res.render('details', {data: {'project' : project, 'media': media}});
});
});
});

Am I doing this API Put request correct with mongoose?

So I'm using mongoose and mongodb and express routing to do it.
I have a user document with an empty array on creation- "todolist", I want to be able to edit this to add a task to the list as well as edit those tasks.
Right now I'm thinking the best way to do it is have the server check if the req.body has certain variables set to know which part of the document I need to edit.
Is this the correct approach or am I suppose to create a new API route for something like this or am I doing it correctly by checking req variables from the client to determine what I want to edit on the server? Is there a cleaner way to do this too?
router.put('/:user_id', function(req, res) {
User.findById(req.params.user_id, function(err, user){
if(err){
res.send(err);
return err;
}
if(req.body.createTask) {
user.todolist.push({
"task_name": req.body.task_name,
"task_importance": req.body.importance,
"task_completed": false
})
}else if(req.body.edit_task){
if(req.body.edit_task_name) {
user.todolist[req.body.index].task_name = req.body.new_task_name;
}else if(req.body.edit_task_importance) {
user.todolist[req.body.index].task_importance = req.body.new_task_importance;
}else if(req.body.edit_task_completed) {
user.todolist[req.body.index].task_completed = true;
}
}
user.save(function(err){
if(err)
res.send(err);
res.json({message: 'User updated'});
})
})
});
I would recommend you to model your database with proper entities, creating a Task model separately from User and adding a reference to User, like this:
var TaskSchema = new mongoose.Schema({
user: {
type: mongoose.Schema.ObjectId,
ref: 'User'
},
name: String,
importance: String,
completed: Boolean
});
If you want to, you can also add reference to Task on User:
var User = new mongoose.Schema({
// note this is an array
todolist: [{
type: mongoose.Schema.ObjectId,
ref: 'Task'
}]
});
Doing that, to make things simpler, you should create a pre save hook to automatically add tasks to user:
TaskSchema.pre('save', function(next) {
if (this.isModified('user') && this.user) {
// $addToSet will add this document to the user's todolist (if it's not already there)
User.findOneAndUpdate({_id: this.user}, {$addToSet: {todolist: this}}, function(err, doc) {
next(err);
})
} else {
next();
}
});
This way the correct approach would be provide endpoints correlated to entities. So, to create a task, a user should request POST /tasks with body { user: '123', name: 'name', ... }:
router.post('/tasks', function(req, res) {
Task.create(req.body, function(err, task) {
if (err) return res.status(500).send(err);
return res.status(201).json(task);
});
});
It will automatically add the task to the user 123 with our pre save hook.
To edit the task, just make that on task endpoint:
router.put('/tasks/:id', function(req, res) {
Task.findById(req.params.id, function(err, task) {
if (err) return res.status(500).send(err);
if (!task) return res.status(404).send('Not Found');
task.name = req.body.name || task.name;
task.importance = req.body.importance || task.importance;
task.completed = true;
task.save(function(err, task) {
if (err) return res.status(500).send(err);
res.status(200).json(task);
});
});
});
You don't need to edit user as it only keeps tasks reference.
If you want to get users with tasks data populated (and not only their ids), just do:
router.get('/users', function(req, res) {
User.find({}).populate('todolist').exec(function(err, users) {
...
});
});
For queries like that above, I recommend you to use some library like querymen, which parses querystrings (e.g. GET /users?q=Some+Search&limit=10&page=2) to MongoDB query arguments.
Hope it helps. :)

Displaying mongoose objects on ejs page

I am using Node/Express and mongoose to build an app. I am creating 'Photo' objects in my database (schema shown below) which is basically just an image object.
var PhotoSchema = new mongoose.Schema({
image: { data: Buffer, contentType: String },
name: String
});
I can successfully upload and create photo objects and now I am creating an /images view where I want to extract every photo object and render it as an image. I am using ejs for my views. After doing some googling I hav come up with the following for my images.ejs file :
<% photos.forEach(function(photo){ %>
<img src="/img/<%=photo._id%>" >
<% }) %>
When I load /images it says "photos is not defined".
This is the first time I have tried to directly extract mongoose objects onto an ejs page - is there something else I need to do/include so that my function understands I am referring to the photo objects in my database?
I also have the following routes:
router.get('/img', function (req, res, next) {
try {
Photo.findOne({}, function (err, doc) {
if (err)
res.send(err);
if (doc) {
res.contentType(doc.image.contentType);
res.send(doc.image.data);
}
});
}
catch (e) {
res.send(e);
}
});
router.get('/img/:id', function (req, res) {
try {
Photo.findOne({ _id: req.params.id }, function (err, doc) {
if (err)
res.send(err);
res.setHeader('Cache-Control', 'public, max-age=3000000');
res.contentType(doc.image.contentType);
res.send(doc.image.data);
});
}
catch (e) {
res.send(e);
}
});
router.get('/images', function (req, res) {
try {
Photo.find({}, function (err, doc) {
if (err)
res.send(err);
res.render('images', { images: doc });
});
}
catch (e) {
res.send(e);
}
});
Thank you!
Your view variable is images (res.render('images', { images: doc });), not photos. Rename one or the other and it should work.

Express Route stops working after a set amount of Angularjs Post

I'm having some trouble getting AngularJS, Express and MongoDB to work together. I'm still new to all three of these and trying to make a simple 'to do' app and toggle a check button to mark something complete. I able to execute a function on the angular side that will do a $http 'POST' to an express route that ultimately updates a MongoDB record. I am able to do this 6 times before it stops working. I can see in the inspector console that the function is still being called, but somewhere between the $http 'POST' and executing the route, it doesn't register it anymore. I no longer see console.log activity on my Express server from the route.
Here is my toDoController.js
toDoApp.controller('ToDoController', function ToDoController($scope, $http, itemsData) {
... other functions
$scope.toggleToDo = function(item) {
console.log('updateToDo()');
console.log(item);
$http({
method: 'POST',
url: '/todolist/toggle_to_do',
data: item
}).
success(function(data, status, headers, config) {
console.log('success post');
}).
error(function(data, status, headers, config) {
console.log(data);
console.log(status);
console.log(headers);
console.log(config);
});
}
}
Express app.js route
app.post('/todolist/toggle_to_do', todolist.toggleToDo);
Express route.js
var MongoClient = require('mongodb').MongoClient;
var ObjectID = require('mongodb').ObjectID; // used to create ObjectID when looking for the record
var collection
var database
// Connect to Database ------------------------------------------------
MongoClient.connect('mongodb://127.0.0.1:27017/test', function(err, db){
if(err) throw err;
console.log('\033[96m + \033[39m connected to mongodb');
collection = db.collection('to_do_list');
database = db;
}
);
// update record
exports.toggleToDo = function(req, res) {
console.log("-toogle 'To Do item' completed status");
console.log(req.body.completed);
console.log(req.body._id);
collection.update({
_id: new ObjectID(req.body._id)
},
{
$set: {
completed: req.body.completed
}
},
function(err) {
if(err) console.warn("Could not write to DB");
else console.log("Item successfully updated!");
}
);
};
Any help would be appreciated! Thanks!
Looks like I didn't end my response.
res.end();
exports.toggleToDo = function(req, res) {
console.log("-toogle 'To Do item' completed status");
console.log(req.body.completed);
console.log(req.body._id);
collection.update({
_id: new ObjectID(req.body._id)
},
{
$set: {
completed: req.body.completed
}
},
function(err) {
if(err) console.warn("Could not write to DB");
else console.log("Item successfully updated!");
}
);
res.end();
};

Resources