I have this request:
// PUT that updates a user.
router.put('/api/user/:id', async (req: Request, res: Response) => {
const { email, name, avatar } = req.body
const userId = req.body._id
const conditions = {
_id : userId
}
const user = {$set: { "email": email, "name": name, "avatar": avatar } }
User.updateOne(conditions, user).then(doc => {
if (!doc) { return res.status(404).end() }
return res.status(200).json(doc)
}).catch(error => console.log(error))
})
And I get this response from the request:
{
"n": 0,
"nModified": 0,
"ok": 1
}
If you can find it on StackOverflow about the updateOne() method in mongoose I've probably tried it. The document isn't updating no matter what I try.
Edit: I've tried using an ObjectID in the query instead and the same result.
Edit 2: I figured it out. Was using req.body.id instead of req.params.id and I was using parameters to send the request. Thanks everyone for the help!
nModified == 0 implies that you have no user matching this id,
your route is put /api/user/:id but your user id is in req.params.id and not in req.body._id
A couple tips:
Try running the same query from mongodb at the command line, see if you get any results.
Is the "campaign_id" defined as an ObjectId in your schema? If so, try searching using the ObjectId type.
Try to change the query to :
const ObjectId = require('mongoose').Types.ObjectId;
const conditions = {
_id : new ObjectId(userId)
}
The reason for not updating is - mongoose is unable to search the with the id you provided.
if you want to update a document based on _id you can use findByIdAndUpdate()
const userId = req.body._id;
const user = { "email": email, "name": name, "avatar": avatar }
User.findByIdAndUpdate(userId , user,
function (err, docs) {
if (err){
console.log(err)
}
else{
console.log("Updated User : ", docs);
}
});
In case you've set your DB to strict mode don't forget to add strict:false in options when adding new keys. Otherwise, inserts will be silently ignored. I've just spent 2 hours wondering why my inserts don't get saved in DB despite not throwing any error.
See dos
http://mongoosejs.com/docs/guide.html#strict
const conditions = {
_id
}
const dateToUpdate = {
$set: {
"email": "email",
"name": "name",
"avatar": "avatar"
}
}
const updateRecord = await models.pdDealModel.updateOne(conditions,dateToUpdate,{
upsert:false,
strict:false
}
)
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.
I have created a sigle app with a Schema and a Model to create a Collection and insert some Documents.
I have my todoModel.js file:
const mongoose = require("mongoose");
const Schema = mongoose.Schema;
const todoSchema = new Schema({
username: String,
todo: String,
isDone: Boolean,
hasAttachment: Boolean
});
const Todos = mongoose.model("Todo", todoSchema);
module.exports = Todos;
Then I have created a setUpController.js file with a sample of my Documents. Then I create a Model and I pass my sample of Documents and my Schema. I create a response to send tje result in JSON.
Everything good here, as I get the result in json when accessing to the route.
Here is the code:
Todos.create(sampleTodos, (err, results) => {
if (!err) {
console.log("setupTodos sample CREATED!")
res.send(results);
}
else {
console.log(`Could not create the setupTodos Database sample, err: ${err}`);
}
});
My problem is that this Documents don´t get saved in the collection !! When I access to the database, nothing is there.
This is my app.js file:
mongoose.connect("mongodb://localhost:27017/nodeTodo")
.then(connection => {
app.listen(port);
})
.catch(err => {
console.log(`Could not establish Connection with err: ${err}`);
});
Could anyone help me please ?
Thank you
Try creating an instance and making the respective function call of that instance. In your case, save the document after creating an instance and it works like a charm.
const newTodos = new Todos({
username: "username",
todo: "todos",
isDone: false,
hasAttachment: flase
});
const createdTodo = newTodos.save((err, todo) => {
if(err) {
throw(err);
}
else {
//do your staff
}
})
after the collection is created you can use the function inserMany to insert also a single document the function receives an array of objects and automatically saves it to the given collection
example:
Pet = new mongoose.model("pet",schemas.petSchema)
Pet.insetMany([
{
//your document
}])
it will save only one hardcoded document
I hope it was helpful
The problem is probably simple, but my 2 AM brain can't understand what's going on anymore. I'm trying to create a profile page that shows basic public info. The way I'm trying to make it work is pulling out the users username from mongodb when registered the account by his specific _id. If needed, verification I use is JWT.
app.post('/api/user-profile', async (req,res) => {
const { token } = req.body
if(!token) {
return res.json({ status: 'error', error: 'not logged in' })
}
try {
const user = jwt.verify(token, JWT_SECRET)
const userid = user.id
const result = User.findOne({ userid })
console.log(result)
// return res.json({ status: 'ok', name: result })
} catch(error) {
// return res.json({ status: 'error', error: 'something went wrong' })
console.log(error)
}
})
I'm not sure what function should I use, findOne() or findById(). I tried to look at the documentation at mongoose, but the explanation is a bit too hard for me to understand.
P.S User = the user registration model. If needed I can paste in the code.
use findById instead of findOne if userid is _id and use await before the query, so do like this:
const result = await User.findById(userid)
if you want to use findOne :
const result = await User.findOne({"_id" : userid})
if you want a plain object javascript use .toObject after query like this:
const result = await User.findById(userid).toObject()
console.log(result)
I don't have a lot of experience with mongoose but worked with Mongo quite a lot. findOne is a direct correspondent of Mongo's findOne function which receives a query in the format {"key": "expectedValue"}. If you want to use it to get data by id the query is {"_id": user.id}.
Because fetching data by id is a common case, the lib added the method findByID which receives an ID, and then formats the query and makes an internal call to findOne.
For anyone interested, the answer is just like Mohammad Yaser Ahmadi said. Everything works fine, and by getting the username I did:
const user = jwt.verify(token, JWT_SECRET)
const userid = user.id
const result = await User.findById(userid)
const usersName = result.username
console.log(usersName)
i can not able to update nested data in my mongodb. here is my "update" module at back-end side.
exports.updateOne = (req, res) => {
if (!req.body) {
return res.status(400).send({
message: "Data to update can not be empty!"
});
}
const {id} = req.params;
console.log(req.body);
User.findByIdAndUpdate(id, req.body, { useFindAndModify: false, new: true}).populate('basic')
.then(data => {
if (!data) {
res.status(404).send({
message: `Cannot update User with id=${id}. Maybe User was not found!`
});
} else
res.send({ message: "User was dupdated successfully." , data});
})
.catch(err => {
res.status(500).send({
message:
err.message || "Error updating User with id=" + id
});
});
};
and my front-end side is;
onChangePosition(e) {
const position = e.target.value;
this.setState(prevState => ({
currentStaff: {
...prevState.currentStaff,
basic:
{
...prevState.currentStaff.basic,
position:position
}
}
}));
}
onChangeEmail(e) {
const emailBusiness = e.target.value;
this.setState(prevState => ({
currentStaff: {
...prevState.currentStaff,
emailBusiness:emailBusiness
}
}));
}
updateStaff() {
StaffDataService.updateOne(
this.state.currentStaff.id,
this.state.currentStaff
).then(response => {
console.log(response.data);
})
.catch(e => {
console.log(e);
})
}
i can change state properly, and my sending data "req.body" is what i want (it is an object). There is no problem.
as you see above, i can update "email" because it is on the main body of object, but can not update "position" (nested element) because it is inside of basic (populated data).
i tried different methods by mongoose, and tried "$set" command.
Can anyone solve this?
To update, the nested value/object in your document, you should use dot notations, so it depends from the req.body variable value.
req.body shouldn't be a Mongoose doc. In such case you mongoose.toObject.
Second thing is:
[update] Object should be: field_with_subdocument.key_value: updated_propery
like this:
/** Example doc */
{
_id: 1,
parent_field: {
baby_field: value
}
}
/** Inside async function */
...await Model.findByIdAndUpdate(id, { "parent_field.baby_field": value })
Also, take a look at [`{overwrite: true}`](https://mongoosejs.com/docs/api/model.html#model_Model.findByIdAndUpdate) option. It might be useful to you.
I faced the same issue, In my case, the defined mongoose schema for that model did not match the nested Object I was passing to the findByIdAndUpdate method. let me simplify it, here is the model
import { model, Schema } from 'mongooose';
const UserModel = model('user', new Schema({
profile: {
avatar: String,
bio: String,
}
}));
And here is the update query:
async function getDefaultProfile() {
const user = await UserModel.findById(process.env.DEFAULT_USER);
return user.profile;
}
const profile = await getDefaultProfile();
UserModel.findByIdAndUpdate('user id', {
$set: {
profile: profile
}
});
The important note was that my getDefaultProfile function returns a mongoose nested object, not a pure object. So in the returned object, I had $set, $get, etc function. So as you know this object is not what we define in the mongoose model, therefore the mongoose ignores it.
So I guess you have the same problem, or something close to my issue.
What should I do?
Run your project in debugging mode.
then check req.body or whatever that gives you the nested object (in my case getDefaultProfile).
Check it with your model, Are they equal?
And if that solution does not work for you, please try this solution, write a utility function:
export async function flatObjectAndSeparateThemByDot(
object: any,
): Promise<any> {
const res: any = {};
(function recurse(obj: any, current?: string) {
for (const key in obj) {
const value = obj[key];
// joined keys with dot
const newKey = current ? current + '.' + key : key;
if (value && typeof value === 'object') {
// it's a nested object, so do it again
recurse(value, newKey);
} else {
// it's not an object, so set the property
res[newKey] = value;
}
}
})(object);
return res;
}
then you can pass your nested object to this function and you will get something like this: { "profile.avatar": "lorem ipsum", "profile.bio": "bio temp" }. So to show you how this function works I will write a sample code:
const sampleProfile = {
profile: {
avatar: "asd",
bio: "sample"
}
}
const profile = await flatObjectAndSeparateThemByDot(sampleProfile);
await UserModel.findByIdAndUpdate('user id', {
$set: {
// other fields,
...profile
}
});
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.