This is my search route:
router.get("/search", async (req, res)=>{
let search = {}
if(req.query.q !=null && req.query.q!==""){
search.title = new RegExp(req.query.q, "i")
search.description = new RegExp(req.query.q, "i")
}
try{
console.log(search)
const posts = await Post.find(search)
res.render("posts/search", {posts: posts, search: req.query})
}catch(err){
console.log(err)
}
})
When I send my query like this, my console returns me this value:
{ description: /whateverIamsearchingfor/i, tags: /whateverIamsearchingfor/i }
And when I search only one of them and comment out the other line I get this: (no surprises)
{ description: /whateverIamsearchingfor/i}
Naturally, It works when only I search for either titles or descriptions. How do I fix this? I want to be able to make a search on both of these parts of this model at the same time. (or maybe even search the other parts as well)
Yay. I made it. In case anybody is still wondering how, I'll post it here:
const posts = await Post.find({ "$or": [
{ title: search.title },
{ description: search.description }
] });
Works like a charm now.
Related
I need to change a few database fields in my backend controller before returning the object to the frontend.
Currently, I am doing it in the front end like this with my returned object:
for (let contribution of contributions) {
contribution["title"] = "You Added One"
contribution["launchName"] = contribution.name
contribution["launchId"] = contribution._id
contribution["date"] = contribution.addedAt
contribution["content"] = contribution.description
}
But I am now trying to do this work in the backend using Mongo.
This is my controller:
const Launch = require('../models/launch')
const User = require('../models/user')
async function getRecentActivityByUserId (req, res) {
const { userId } = req.params
const user = await User.findOne({ userId }).lean() || []
const contributions = await Launch.find({ _id: { $in: user.contributions } })
return res.status(200).send(contributions.reverse())
}
So this correctly returns an object to the frontend but I still need to change the database field names.
So I tried this:
async function getRecentActivityByUserId (req, res) {
let recents = []
const { userId } = req.params
const user = await User.findOne({ userId }).lean() || []
const contributions = await Launch.find({ _id: { $in: user.contributions } }).aggregate([
{
$addFields: {
plans: {
$map:{
input: "$launch",
as: "l",
in: {
title: "You Added One",
launchName: "$$l.name",
launchId: "$$l._id",
date: "$$l.addedAt",
content: "$$l.description",
}
}
}
}
},
{
$out: "launch"
}
])
return res.status(200).send(contributions.reverse())
}
The above throws an error saying that I .aggregrate is not a function on .find. Even if I remove the .find, the object returned is just an empty array so I'm obviously not aggregating correctly.
How can I combine .find with .aggregate and what is wrong with my .aggregate function??
I also tried combining aggregate with find like this and get the error Arguments must be aggregate pipeline operators:
const contributions = await Launch.aggregate([
{
$match: {
_id: { $in: user.contributions }
},
$addFields: {
plans: {
$map:{
input: "$launch",
as: "l",
in: {
title: "You Added a Kayak Launch",
launchName: "$$l.name",
launchId: "$$l._id",
date: "$$l.addedAt",
content: "$$l.description",
}
}
}
}
},
{
$out: "launch"
}
])
EDIT: Just realized that I have the word plans in the aggregate function and that is not relevant to my code. I copied this code from elsewhere so not sure what the value should be.
I figured it out. This is the solution:
async function getRecentActivityByUserId (req, res) {
let recents = []
const { userId } = req.params
const user = await User.findOne({ userId }).lean() || []
const contributions = await Launch.aggregate([
{
$match: {
_id: { $in: user.contributions }
}
},
{
$addFields: {
title: "You Added One" ,
launchName: "$name",
launchId: "$_id",
date: "$addedAt",
content: "$description"
}
}
])
if(contributions) {
recents = recents.concat(contributions);
}
return res.status(200).send(recents.reverse())
}
The actual problem from the question was a small syntax error which has been noted and corrected in the self-reported answer here.
I noted in the comments there that the current approach of issuing two separate operations (a findOne() followed by an aggregate() that uses the results) could be simplified into a single query to the database. The important thing here is that you will $match against the first collection (users or whatever the collection name is in your environment) and then use $lookup to perform the "match" against the subsequent launches collection.
Here is a playground demonstrating the basic approach. Adjust as needed to the specifics of your environment.
In Node JS I have an endpoint where I am trying to get data from two different mongo collections and when trying to piece the data together I am not able to add another property to the JSON object.
const getLessonWithTopics = async (req, res) => {
const lessonId = req.params.id;
// Get the lesson
Lesson.findOne({_id: lessonId}).exec((err, data) => {
let lesson = data;
Topic.find().where('_id').in(data.topics).exec((err, topics) => {
if(err) res.status(500).send("Error something went wrong");
lesson.associatedTopics = topics;
console.log(lesson);
res.json(lesson)
})
})
}
When logging lesson to console it does not have the associatedTopics property even though through searching online I have found multiple instances where some is saying this is how you would add this property. (Ex. Add new attribute (element) to JSON object using JavaScript )
I have tried using var as well, to see if that would change something (maybe make it mutable) it did not.
** When logging the topics object to console it does log the data that I expected so the variable 'topics' is not the issue **
I'm sure that it is something simple that I am missing and hoping someone with a large brain can help figure this out for me.
Any help would be appreciated, Thank you!
Can you try once with the following code?
const getLessonWithTopics = async (req, res) => {
const lessonId = req.params.id;
// Get the lesson
try {
const lession = await Lession.findOne({ _id: lessonId }).lean()
const topics = await Topic.find({ _id: { $in: lession.topics } }).lean()
lession.associatedTopics = topics
res.json(lesson);
} catch (err) {
console.log(err)
res.status(500)
}
My goal is to get the one product object back depending on the product id i marked in green (out of the whole products array in my mongo DB)
My Backend entry looks as follows:
router.get("/:id", async (req, res)=> {
const mid=req.params.id;
console.log(mid)
const products = await Product.findOne({ id: mid })
console.log(products)
if (products) {
res.send(products);
} else {
res.status(404).send({message:"product not found"})
}
});
Connsole.log(mid) on line three works and it gives the right id back. However when i try to filter that one array depending on the value in line three i always get back the first object of my Database, which is the gopro camera, instead of the right object.
The Output looks as Follows:
632834528
{
_id: '5f9849daf641a82b257d529b',
id: 3484,
agentId: 66343,
title: 'GoPro Camera',
slug: 'gopro',
What am i doing Wrong?
I tried const products = await Product.find({ id: mid }) as well, but it gives me the whole array back instead of just the one object.
I think it's returning a Query. Try:
const products = await Product.findOne({ id: mid }).exec();
This solution worked for me:
I have to use the Expressasynchandler like this:
Edit: This solution might not be the best (see comments on answer above)
router.get("/:id", expressAsyncHandler(async (req, res) => {
const mid=req.params.id;
const products = await Product.findOne({ id: mid })
if (products) {
res.send([products]);
} else {
res.status(404).send({message:"product not found"})
}
}));
I know i have to use some but for some reason i cant seem to get it right. i have a collection in my mongodb database of posts. each post has an array of objects named "likes" that references the users that liked this post. so in my backend i want to check if the user exists in the likes array of the post. if it does not exist then like the post, else return with an appropriate message on my react frontend. The code i will include always returns false from some so a user can like a post infinite times.
exports.postLike = async (req, res, next) => {
const postId = req.query.postId;
const userId = req.query.userId;
console.log('postId: ' + postId);
try{
const post = await Post.findById(postId).populate('creator').populate('likes');
const user = await User.findById(userId);
if (!post.likes.some(post => post._id === user._id)){
post.likes.push(user);
console.log('liked a post');
const result = await post.save();
res.status(200).json({ message: 'Post liked!', post: result });
} else {
console.log('Post already liked!');
res.status(200).json({ message: 'Post already liked!', post: post });
}
}catch (err) {
if (!err.statusCode) {
err.statusCode = 500;
}
next(err);
}
};
i clearly haven't understood, yet, how some works so if you can help that would be great. also if you have any other solution that would be good in this case then please post it. i tried some random codes with indexOf and includes for checking but it didn't work either. i am not sure which is the right way to check if the user object is included in the "likes" array of objects. i would prefer not to write any function of my own to check this, i want to do it using an existing function/method provided by javascript.
Going to offer a different route here. You are fetching all the data including a join to the creator and likes just to add a like to the collection. This is a little wasteful and can be achieved by just doing an update and use $addToSet which will add the like if it does not exist.
You then just check nModified in the result to know if it was added or not. So you can have:
const result = await Post.updateOne(
{
id: 1
},
{
$addToSet: {
likes: {
userId: mongoose.Types.ObjectId(req.query.userId)
}
}
}
);
console.info(result.nModified === 1);
Alternatively, you can use some as follows using === to compare type and value:
posts.likes.some(like => like.userId.toString() === req.query.userId)
MongoDB.ObjectId is a wrapper around a primitve, just like Number or Boolean. And just like
new Boolean(true) === new Boolean(true)
will be false, your comparison will fail too. You have to take out the primitive for comparison:
post._id.valueOf() === user._id.valueOf()
I am trying to make a search api using nodejs and MongoDB. I tried to google about this and I did find something there but while trying to implement I get an error saying. I don't know how to fix this honestly I don't know anything about making search API. So any help or suggestion will be helpful for me.
This is the link of the post I found on google Building a simple search api.
error
{
"error": {
"message": "Cast to ObjectId failed for value \"search\" at path \"_id\" for model \"Post\"",
"name": "CastError",
"stringValue": "\"search\"",
"kind": "ObjectId",
"value": "search",
"path": "_id"
}
}
This is my code
postController.search = (req, res) => {
var response = [];
if(typeof req.query.title !== 'undefined'){
db.Post.filter(function(post) {
if(post.title === req.query.title){
console.log(req.body);
response.push(post);
console.log(post);
}
});
}
response = _.uniqBy(response, '_id');
if(Object.key(req.query).length === 0){
response = db.Post
}
res.json(response);
};
data in the collection
"data": [
{
"isDeleted": false,
"_comments": [],
"_id": "5d39122036117d2ea81b434c",
"title": "facebook post",
"link": "facebook.com",
"_creator": {
"createdAt": "2019-07-25T01:42:21.252Z",
"username": "adityakmr"
},
"createdAt": "2019-07-25T02:21:20.634Z",
"__v": 0
},
]
If you're trying to create an API to search mongoDB collection based on title i.e; a text field try implementing text search feature of mongoDB : text search in mongoDB
, Just create a text index on title field & then create an API with post method which takes in parameter that can be queried against title field.
Text search can be a bit tricky it can help you for fuzzy/partial/full text searches - use of regex is also much beneficial.
Checkout links for node.js API example :
MongoDB NodeJs Docs
Full Text Search with MongoDB & Node.js
Text Searching with MongoDB
First of all, you need to use async/await for modularize your code. I suggest don't write your whole code in your controller.js file, API can be made by following the way (routes - controller - utils ).
postRoutes.js
postRouter.get('/search-post', postCtr.searchPost);
postController.js
const postUtils = require('./postUtils');
const postController = {};
postController.searchPost = async (req, res) => {
try {
const { title } = req.query;
const result = await postUtils.searchPost(title);
return res.status(200).json(result);
} catch (err) {
return res.status(err.code).json({ error: err.error });
}
};
module.exports = postController;
postUtils.js
const Post = require('./postModel');
const postUtils = {};
postUtils.searchPost = async (title) => {
try {
let result = [];
if(title){
// Even you can perform regex in your search
result = await Post.find({ title: title });
}
return result;
} catch (err) {
const errorObj = { code: 500, error: 'Internal server error' }; // It can be dynamic
throw errorObj;
}
};
module.exports = postUtils;
postModel.js
const mongoose = require('mongoose');
const postSchema = new mongoose.Schema({
user: {
type: mongoose.Schema.Types.ObjectId,
ref: 'user',
required: true,
},
// Your fields ...
}, { collection: 'post', timestamps: true });
const post = mongoose.model('post', postSchema);
module.exports = post;
Using this structure you can easily debug your code and It's also manageable.
In the link you specified above, they are using array of objects stored in file called store.js, but not mongoDB. So directly they are filtering using Array.filter method.
But in mongoDB using mongoose(object modeling tool) you can make use of collection.find() method.
So solution to your problem is as follows
postController.search = async (req, res) => {
var response = [];
if (req.query.title) {
response = await db.Post.find({title: req.query.title});
}
res.json(response);
};
find is inbuilt query method which helps in querying the collections, you can pass multiple properties for querying.