Find document from mongoose collections with specific condition - node.js

Recently I start using MongoDB with Mongoose on Nodejs.
This code works as it should, and returns me all data i need :
const getAllPosts = async () => {
try {
return (await PostModel.find().populate('user')).reverse();
} catch (error) {
console.log(error);
throw Error('Error while getting all posts');
}
};
But now I only need individual posts, which in the tags (represented as an array in the PostModel) contain the data that I will pass in the request.
For example, I will make a GET request to /posts/tag111 and should get all posts that have "tag111" in the tags array.
Any ways to do this?

If you are using expressjs, your route should be something like:
whatever.get('/:tag', postController.getAllPostTagged)
The function you use in your route (called getAllPostTagged above) should be similar to this one, in which you get the path param tag from req:
const postController = {
getAllPostTagged = async(req, res) => {
try {
return (await PostModel.find({tags: req.params.tag}));
} catch (error) {
console.log(error);
throw Error('Error while getting all posts');
}
}
}
The key here is to know where the params are obtained (from req.params) .

Related

How to return success on a Post API call to MongoDB in NodeJS

I'm new to fetching and posting data using an API, and I can't work out how to do something once my Post has been completed.
I have a function that calls the API with the Post data. I need to set the loading state to false once the Post has been completed. Everything works apart from that, the data gets sent to Mongo, I just need to turn off my loading spinner once it has completed.
How do I do this, please?
This is how I'm trying to do it:
const postData = async () => {
setLoading(true)
await axios.post('/api/addData',form)
.then(response => {
setLoading(false)
})
}
And this is the API bit:
import { connectToDatabase } from "util/mongodb"
export default async (req, res) => {
const { db } = await connectToDatabase()
await db
.collection("posts")
.insertOne(req.body);
}
There is two potential problem in your code, first you're not sending any data back to the front in your backend code. Usually you send back the id of the inserted element (It can be usefull to do some mutation in your front), you'll also need to try catch your call to the db to notify that something went wrong to the front end side :
import { connectToDatabase } from "util/mongodb"
export default async (req, res) => {
try {
const { db } = await connectToDatabase()
const insertedPost = await db
.collection("posts")
.insertOne(req.body);
res.status(201).send(insertedPost.insertedId);
// again it's up to you to know what can be usefull to your front-end to use
// Look at http status code online to know what's the best fit
} catch (err) {
res.status(500).send(err.message);
// send whatever that can be usefull for your front end to handle the error
}
}
In your front-end code you're using await with .then, it's weird usage. You can put your setLoading(false) after the await without the .then but you'll still need to try catch it. What I prefer to do is using the finally block to stop loading, so if my api call fail the loading is still stopped :
const postData = async () => {
setLoading(true)
try {
const response = await axios.post('/api/addData',form)
// do something with response
} catch (err) {
// notify user that something went wrong
} finally {
setLoading(false);
}
}
const postData = () => {
setLoading(true)
axios.post('/api/addData',form)
.then(response => {
// do something with response
})
.catch((err) => {
// notify user that something went wrong
})
.finally(() => {
setLoading(false);
})
}

Mongoose Function Cannot be Called in different file in Node.Js

I created some functions containing MongoDB methods in one File. It works well when I access the function from the same file, but when I am trying to access the function from another file, it doesn't work.
Here is the code
const Chain = require('../database/models/chains')
const getlatestChain = async () => {
try {
const thechains = await Chain.countDocuments()
if (thechains < 2) {
throw new Error('there is only one chain!')
}
return thechains
} catch (error) {
return error
}
}
module.exports = {
getlatestChain: getlatestChain
}
It doesn't work when I call it from another file
const thechain = require('../src/utils/chain')
require('../src/database/database')
thechain.getlatestChain()
.then((result) => {
console.log('how many documents : ' + result)
}).catch((err) => {
console.log(err)
});
error
TypeError: Chain.countDocuments is not a function
check the chains model to make sure you are exporting the countDocuments function, check the spelling as well if it is exported

findById(req.params.id) returns null as response?

My issue is different than others, when I passed /:id then I return JSON yeah its ok, but issue is that when I give wrong objectId it return null value with statusCode 200 instead of error that this id is wrong. according to my perception it call .catch block instead of .then block because id is not available on database.
const get_id_docs = async (req, res) => {
await models
.findById(req.params.id)
.then(result => {
res.send(result)
})
.catch(err => {
res.sendStatus(404).send("Link Not Found")
})
};
There are two cases, one an invalid id, and the other a valid id but doesn't exists in db.
If you want to differentiate an invalid id, you can validate it before querying, and return 404.
Also you mixed async await and Promise, one of them must be used in this case.
const mongoose = require("mongoose");
const get_id_docs = async (req, res) => {
const isValidId = mongoose.Types.ObjectId.isValid(req.params.id);
if (!isValidId) {
res.status(404).send("Link Not Found - invalid id");
}
try {
const result = await models.findById(req.params.id);
if (result) {
res.send(result);
}
res.status(404).send("Link Not Found - does not exists");
} catch (err) {
res.status(500).send(err.message);
}
};
And if you prefer then catch
const mongoose = require("mongoose");
const get_id_docs = (req, res) => {
const isValidId = mongoose.Types.ObjectId.isValid(req.params.id);
if (!isValidId) {
res.status(404).send("Link Not Found - invalid id");
}
models.findById(req.params.id).then(result => {
if (result) {
res.send(result);
}
res.status(404).send("Link Not Found - does not exists");
})
.catch (err) {
res.status(500).send(err.message);
}
};
You are composing various general-purpose libraries that fill a variety of use cases. In particular, database abstraction frameworks typically try to percent a collection like facade over the underlying data stores. In JavaScript, methods like Array.prototype.find return undefined rather than throwing errors. And I think the authors of mongoose are trying to write analogously behaving APIs.
In addition to providing intuitive default Behavior, by not throwing errors, this enable a wider range of use cases, such as checking for existence, to be handled without boilerplate.
Given that, you want something like the following
const get_id_docs = async (req, res) => {
const result = await models.findById(req.params.id);
if (result) {
res.send(result);
}
res.sendStatus(404).send("Link Not Found");
};
Note that the above has other advantages including that it does not propagate other kinds of errors as 404s arbitrarily.

Async/await in Express with multiple MongoDB queries

I have a fairly straightforward CRUD app which renders the results of two queries onto one page. The problem that arose once I got this to "work" was that the page required a refresh in order to display the results. On first load, no results were displayed.
I came to figure out that this is a problem/symptom of Node's asynchronous nature. I've been trying to approach this problem by using async/await, and from hours of messing with things, I feel like I'm quite close to the solution, but it's just not working out - I still need a manual refresh to display/render the results on the .ejs page.
The code:
var entries = [];
var frontPageGoals = [];
app.get('/entries', async (req,res) => {
if (req.session.password) {
const entriesColl = await
db.collection('entries')
.find()
.sort({date: -1})
.toArray((err, result) => {
if (err) { console.log(err) }
else {
for (i=0; i<result.length; i++) {
entries[i] = result[i];
}
}
});
const goalsColl = await
db.collection('goals')
.find()
.toArray((err, result) => {
if (err) {console.log(err)}
else {
for (i=0; i<result.length; i++) {
frontPageGoals[i] = result[i];
}
}
});
res.render('index.ejs', {entries: entries, frontPageGoals: frontPageGoals});
}
else {
res.redirect('/');
}
});
Now, I can conceive of a few problems here, but honestly I'm just at my wits end trying to figure this out. For example, I'm sure it's problematic that the empty lists which will contain the results to be passed when the page renders are outside the actual async function. But after trying to move them a dozen different places within the async area... still no dice.
Any help would be hugely appreciated! This is basically the last big "thing" I need done for this app.
I'm not 100% sure about your database driver, but assuming that the toArray() returns a promise (which it does in the default mongodb driver), the await will actually return the value you expect in your callback, result in your case, or in case there was an error, which you expected it as err in your callback, it will be thrown, thus forcing you to use try-catch blocks, in your case, you would just use console.log(err) in the catch block, since you aren't doing any handling
Here's your code after updating :
app.get("/entries", async (req, res) => {
if (req.session.password) {
try {
const entries = await db
.collection("entries")
.find()
.sort({ date: -1 })
.toArray();
const frontPageGoals = await db
.collection("goals")
.find()
.toArray();
res.render("index.ejs", {
entries: entries,
frontPageGoals: frontPageGoals
});
} catch (err) {
console.log(err);
}
} else {
res.redirect("/");
}
});
EDIT
However, if you don't know about promises -which async/await are basically promises-, and wanna just do it using callbacks -not advised-, you would have to just send your response in the callback, and nest the 2nd query in the first query's callback, here is the code,, with some comments to hopefully help you out:
app.get("/entries", (req, res) => {
if (req.session.password) {
// First query
db.collection("entries")
.find()
.sort({ date: -1 })
.toArray((err, entryResult) => {
if (err) {
console.log(err);
} else {
// In the callback of the first query, so it will
// execute 2nd query, only when the first one is done
db.collection("goals")
.find()
.toArray((err, frontPageResult) => {
if (err) {
console.log(err);
} else {
// In the callback of the 2nd query, send the response
// here since both data are at hand
res.render("index.ejs", {
entries: entryResult,
frontPageGoals: frontPageResult
});
}
});
}
});
} else {
res.redirect("/");
}
});
I have removed the async keyword since you no longer need it
I renamed the callback arguments, instead of just result, because both callbacks would have the same argument name, and you would have had to store it in a temp variable

Express API returning an unwanted "data" section in my GET all requests

I'm currently building a rest API and I'm having an unexpected output when I make a /GET request.
When i make a get request to the API, it returns
{
data: {
[{myExpectedObjects},{myExpectedObjects}]
}
}
however, I'm expecting my get request to return just the array of objects. Below is the code im using to accomplish the rest calls
Create controller
const create = (req, res) => {
let dataModel = generateModel(genericDataFromReq);
dataModel = new dataModel({
genericData,
specificData,
});
dataModel.save().then((data) => {
res.status(201).send(data);
}, (e) => {
res.status(500).send(e);
});
}
};
get all controller
const list = (req, res) => {
const dataModel = generateModel(dataToGet);
dataModel.find().then((data) => {
if (data.length === 0) {
res.status(404).send('failed');
} else {
res.status(200).send({ data });
}
}, (e) => {
res.status(500).send(e);
});
};
generate data model
function generateModel(dbCollectionName) {
try {
return generateDataModel(dbCollectionName);
} catch (e) {
return mongoosee.model(`${dbCollectionName}`);
}
}
I know the code is a bit unconventional but I've set up a generic rest API to take in different types of requests and I found this solution to be the best way of doing this.
Any ideas on why my get all request is tacking on a "data" section before my array of objects (which is what I'm actually interest in)?
I believe the issue is in this line:
else {
res.status(200).send({ data });
}
When you put curly braces around a variable, it creates a key-value pair where the key is the name of the variable and the value is the value of the variable. So get rid of the curly braces and it should work as you expect. See the parts that mention this ES2015 notation here:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Object_initializer

Resources