Node js MongoDb specific page view counter - node.js

I'm making app with MEAN stack and I want on every get request to increase viewCounter on specific document ( Property ) inside collection.
If i put this code inside get request of requested property
Property.findByIdAndUpdate('id', { $inc: { counter: 1 } }, {new: true})
It will increase loading of data and i want to do that after user gets his data.
So is the best way to do this just to send additional request to the database after initial data is loaded ?
Property {
name: '',
description: '',
...,
viewCounter: 5
}
exports.getProperty = catchAsync(async (req, res, next) => {
query = await Property.findById(req.params.id).lean();
if(!query) {
return next(new AppError('No property found with that ID', 404))
}
res.status(200).json({
status: 'success',
data: {
query
}
})
})

Node events can be used to keep the counter of events.
Official document
Reference for code
eventEmitter.on('db_view', ({ parameters }) => {
eventTracker.track(
'db_view',
parameters
);
})
eventEmitter.on('db_view', async ({ user, company }) => {
Property.findByIdAndUpdate('id', { $inc: { counter: 1 } }, {new: true})
})

Try to send request after making sure your document has loaded.
angular.element($window).bind('load', function() {
//put your code
});

Related

MERN - update specific string in an array's object

I am using mongoose to connect my backend (Express) server to database. I want to do normal CRUD operations - but I am able to do it only for direct data in object, but I need to be able to access also array data.
Example of my model:
const LeewaySchema = new mongoose.Schema({
name: {
type: String,
},
shirt: [
{
name: String,
image: String,
},
],
With the following code I am able to update only name of the object, but I need to be able to update also name in shirt array
Here is working approach when changing name of object:
app.put('/update', async (req, res) => {
const updateName = req.body.updateName;
const id = req.body.id;
console.log(updateName, id);
try {
await ClosetModel.findById(id, (error, closetToUpdate) => {
closetToUpdate.name = updateName;
closetToUpdate.save();
});
} catch (err) {
console.log(err);
}
res.send('success');
});
And I tried the same with shirt array, just specifying the correct path
app.put('/update-shirt', async (req, res) => {
const updateShirtName = req.body.updateShirtName;
const id = req.body.id;
try {
await ClosetModel.findById(id, (error, closetToUpdate) => {
closetToUpdate.shirt.name = updateShirtName; // different path here
closetToUpdate.save();
});
} catch (err) {
console.log(err);
}
res.send('success');
});
The server crashes and /update-shirt conflicts with /update path
I am using the same route and frontend for READ
useEffect(() => {
axios
.get('http://localhost:8000/read')
.then((response) => {
setListOfClosets(response.data);
})
.catch(() => {
console.log('error');
});
}, []);
And update name function calling with button onClick:
const updateCloset = (id) => {
const updateName = prompt('Enter new data');
axios
.put('http://localhost:8000/update', {
updateName: updateName,
id: id,
})
.then(() => {
setListOfClosets(
listOfClosets.map((val) => {
return val._id === id
? {
_id: id,
name: updateName,
email: val.email,
}
: val;
})
);
});
};
I don't really know how to do update for shirt's name, I tried to copy paste and just change path and url of course, but it did not work.
The question doesn't actually describe what specific transformation (update) you are attempting to apply to the document. Without knowing what you are attempting to do, there is no way for us to help advise on how to do it.
Say, for example, that the document of interest looks like this:
{
_id: 1,
shirt: [
{ name: "first shirt", image: "path to first shirt" },
{ name: "second shirt", image: "path to second shirt" },
{ name: "third shirt", image: "path to third shirt" }
]
}
Also let's say that the application hits the /update-shirt endpoint with an id of 1 and a updateShirtName of "updated shirt name". Which entry in the array is that string supposed to be applied to? Similarly, how would that information be passed to the server for it to construct the appropriate update.
It is absolutely possible to update documents in an array, here is some documentation about that specifically. But the actual structure of the command depends on the logic that you are attempting to provide from the application itself.
The only other thing that comes to mind here is that the motivation for the schema described in the question seems a little unclear. Why is the shirt field defined as an array here? Perhaps it should instead just be an embedded document. If so then the mechanics of updating the field in the subdocument are more straightforward and none of the aforementioned concerns about updating arrays remain relevant.
just make an update api where you just have to pass the id and and pass the shirt in the findByIdAndUpdate query and hit the postman by passing the below code.
shirt: [
{
name: "jhrh",
image: String,
},
],

Modify mongodb records before sending to api response

I'm using MongoDB as a database and express.js to build API.
I want to modify the response before sending it to the client.
here is my express.js code...
products.route("/:id")
.get((req, res) => {
mdb.get().collection('products').find({_id:parseInt(req.params.id), status:1}).toArray()
.then(response => res.status(200).json(response))
.catch(error => console.error(error));
})
Like, I want to add some calculated filed to the response. suppose my response object from the database is this
response = {
id: 1001,
name: 'Apple',
price: 120
}
Now, I want to add image field. so, my final response object will be
response = {
id: 1001,
name: 'Apple',
price: 120,
image: '/assets/images/'+id+'.jpg'
}
Please help me with this I'm very new in express.js
Inside your router handler, you would do something like this:
Products
.findOneAndUpdate({ _id: req.params._id, status: 1 }, {
$set: {
images: '/assets/images/' + req.params._id + '.jpg'
}
},{
new: true
})
.then(product => {
if(!product) return res.status(404).json("Product not found");
else return res.status(200).json(products);
})
.catch(err => {
console.error(err);
return res.status(500).json(err);
});
Use the findOneAndUpdate method from mongodb on your Products collection to update a document inside that collection.
findOneAndUpdate takes three objects as parameters:
first one specifies the query condition
second specifies the updated document
Here we are using the $set operator to add a image property to the existing document.
third is an options object
set new to true to tell mongo to return the updated document instead of the original one, which is the default
Here is a reference to the docs, for more information: https://docs.mongodb.com/manual/reference/method/db.collection.findOneAndUpdate/
EDIT: To modify the response only, you would do inside the .then:
.then(products => {
const modifiedProducts = products.map(product => ({
...product,
image: '/assets/images/' + product._id + '.jpg'
}));
return res.status(200).json(modifiedProducts);
})
Here we are creating a new object for each product that is being returned, by copying its original contents plus the new image property, then returning that modifiedProducts array to the client

ExpressJS: Sequelize method update need to show updated data as result not num of row updated

I've API using ExpressJS and ORM Sequelize. I'm trying to do update using method update() from Sequelize. By default, it method will return number of row updated. But I want the result is the new data that just updated to show as response.
Here is my code:
update: async function (req, res, next) {
var current_address_id = req.body.current_address_id,
address = req.body.address
PersonalInfo.findOne({
where: {
id: req.params.id
}
}).then(personal => {
Address.create(
{
address: address,
}
).then( resAddress => {
PersonalInfo.update(
{
current_address_id: resAddress.dataValues.id
},
{
where: {
id: req.params.id
}
}
).then(resultUpdate => {
console.log(resultUpdate);
responseUtil.success(res, resultUpdate);
}).catch(err => {
responseUtil.fail(res, err);
})
})
})
}
When I do console.log(resultUpdate); It give me [1] as the num of row updated. What I need is the data of PersonalInfo that just updated.
After consulting the documentation for what returns from the update operation for Sequelize, it returns the following:
an array with one or two elements. The first element is always the
number of affected rows, while the second element is the actual
affected rows (only supported in postgres with options.returning
true.)
So, as you can see from your code, your update is returning an array with the number of affected rows, which is what the documentation says it will do. You can't change what the library itself will return.
You do have access to the values you are updating earlier on in your function, and if you really want, you could do a find on the record you are updating, which will return your model: http://docs.sequelizejs.com/class/lib/model.js~Model.html#static-method-findOne
You only need to add returning: true at your query. Your code would be like
update: async function (req, res, next) {
var current_address_id = req.body.current_address_id,
address = req.body.address
PersonalInfo.findOne({
where: {
id: req.params.id
}
}).then(personal => {
Address.create(
{
address: address,
}
).then( resAddress => {
PersonalInfo.update(
{
current_address_id: resAddress.dataValues.id
},
{
where: {
id: req.params.id
},
returning: true
}
).then(resultUpdate => {
console.log(resultUpdate);
responseUtil.success(res, resultUpdate);
}).catch(err => {
responseUtil.fail(res, err);
})
})
})
}

Mongodb/mongoose omit a field in response [duplicate]

I have a NodeJS application with Mongoose ODM(Mongoose 3.3.1). I want to retrieve all fields except 1 from my collection.For Example: I have a collection Product Which have 6 fields,I want to select all except a field "Image" . I used "exclude" method, but got error..
This was my code.
var Query = models.Product.find();
Query.exclude('title Image');
if (req.params.id) {
Query.where('_id', req.params.id);
}
Query.exec(function (err, product) {
if (!err) {
return res.send({ 'statusCode': 200, 'statusText': 'OK', 'data': product });
} else {
return res.send(500);
}
});
But this returns error
Express
500 TypeError: Object #<Query> has no method 'exclude'.........
Also I tried, var Query = models.Product.find().exclude('title','Image'); and var Query = models.Product.find({}).exclude('title','Image'); But getting the same error. How to exclude one/(two) particular fields from a collection in Mongoose.
Use query.select for field selection in the current (3.x) Mongoose builds.
Prefix a field name you want to exclude with a -; so in your case:
Query.select('-Image');
Quick aside: in JavaScript, variables starting with a capital letter should be reserved for constructor functions. So consider renaming Query as query in your code.
I don't know where you read about that .exclude function, because I can't find it in any documentation.
But you can exclude fields by using the second parameter of the find method.
Here is an example from the official documentation:
db.inventory.find( { type: 'food' }, { type:0 } )
This operation returns all documents where the value of the type field is food, but does not include the type field in the output.
Model.findOne({ _id: Your Id}, { password: 0, name: 0 }, function(err, user){
// put your code
});
this code worked in my project. Thanks!! have a nice day.
You could do this
const products = await Product.find().select(['-image'])
I am use this with async await
async (req, res) => {
try {
await User.findById(req.user,'name email',(err, user) => {
if(err || !user){
return res.status(404)
} else {
return res.status(200).json({
user,
});
}
});
} catch (error) {
console.log(error);
}
In the updated version of Mongoose you can use it in this way as below to get selected fields.
user.findById({_id: req.body.id}, 'username phno address').then(response => {
res.status(200).json({
result: true,
details: response
});
}).catch(err => {
res.status(500).json({ result: false });
});
I'm working on a feature. I store a userId array name "collectedUser" than who is collected the project. And I just want to return a field "isCollected" instead of "collectedUsers". So select is not what I want. But I got this solution.
This is after I get projects from database, I add "isCollected".
for (const item of projects) {
item.set("isCollected", item.collectedUsers.includes(userId), {
strict: false,
})
}
And this is in Decorator #Schema
#Schema({
timestamps: true,
toObject: {
virtuals: true,
versionKey: false,
transform: (doc, ret, options): Partial<Project> => {
return {
...ret,
projectManagers: undefined,
projectMembers: undefined,
collectedUsers: undefined
}
}
}
})
Finally in my controller
projects = projects.map(i => i.toObject())
It's a strange tricks that set undefined, but it really work.
Btw I'm using nestjs.
You can do it like this
const products = await Product.find().select({
"image": 0
});
For anyone looking for a way to always omit a field - more like a global option rather than doing so in the query e.g. a password field, using a getter that returns undefined also works
{
password: {
type: String,
required: true,
get: () => undefined,
},
}
NB: Getters must be enabled with option { toObject: { getters:true } }
you can exclude the field from the schema definition
by adding the attribute
excludedField : {
...
select: false,
...
}
whenever you want to add it to your result,
add this to your find()
find().select('+excludedFiled')

mongoose to display ref object in json

I'm new to mongoose, using the following controller:
const Creport = require('../models/creport.model.js');
exports.save = (req, res) => {
const creport = new Creport({
curso_id: req.body.curso_id,
nombre: req.body.nombre,
....
});
creport.save()
.then(data => {
res.send(data);
}).catch(err => {
res.status(500).send({
message: err.message
});
});
};
in the creport.model.js:
curso_id: {
type: Schema.Types.ObjectId,
ref: 'Curso'
},
this will create a json file like:
{"curso_id":"5b5a14e8ej1a18ac0b5e5433","nombre":"el nombre",....}
while I'm looking for:
{"curso_id":"curso No. 1","nombre":"el nombre",....}
EDIT:
using populate:
exports.findAll = (req, res) => {
Creport.find().populate('curso_id')
.then(creports => {
res.send(creports);
}).catch(err => {
res.status(500).send({
message: err.message
});
});
};
will output:
[{"_id":"5b5ce554967f6a36f0c84fe6","curso_id":{"_id":"5b5a14e8ej1a18ac0b5e5433","name":"curso No. 1"},"nombre":"el nombre"....}]
For return the data in this format, you need to use the aggregate method.
In my tests, I created one course and one Creport and after I executed this aggregate:
CreportModel.aggregate([
{"$match":{_id:creport._id}},
{"$lookup":{
from:"cursos",
localField:"curso_id",
foreignField:"_id",
as:"cursos"
}
},
{"$project":{"curso_id": {"$arrayElemAt":["$cursos.name",0]},"nombre": "$nombre","_id":0}}
])
.then(result=>{
console.log(result)
})
Result:
If you want to add more fields in the result, you need to change the $project phase.
e.g
{"$project":{"curso_id": {"_id":1,"$arrayElemAt":["$cursos.name",0]},"nombre": "$nombre"}}
0 : means that will remove the field in the return
1 : means that will show the field in the return
Mongoose Documentation: Aggregate Lookup
You could use populate method, if you read the mongoose documentation you will find there's very easy way to apply.
http://mongoosejs.com/docs/populate.html
curso_id is an ObjectId. The returned json confirms this.
The semantic is correct.
You should probably add a
curso_number: {
type: Schema.Types.String
},

Resources