i am trying to use mongodb to get some data for our analytics page, there's alot of data that's processing but for some reason the express returns empty array as response even when i use console log i see the data on terminal.
This is my endpoint
import Analytics from '../classes/Analytics';
import { Router } from 'express';
import role from '../middleware/role';
const router = Router();
router.use(role('admin'));
router.get('/analytics/weekly', async (req, res) => {
try {
const data = await Analytics.getWeekly();
return res.status(200).send({ data });
} catch (err) {
console.log(err);
return res.status(500).send({ message: 'Something went wrong, please try again later!' });
}
});
module.exports = router;
This is where all the magic happens for data variable
class Analytics {
static async getWeekly() {
// ignore this one day difference thing
const startDate = moment().subtract(1, 'days').startOf('week').format();
const endDate = moment().subtract(1, 'days').endOf('week').format();
try {
const orders = await Order.find(
{
sent_at: {
$gte: startDate,
$lte: endDate,
},
},
{ user: 1, created_at: 1, sent_at: 1 }
);
let counter = [];
for await (const item of orders) {
const date = moment(item.sent_at).format('YYYY-MM-DD HH');
if (!counter[date]) counter[date] = [];
if (!counter[date][item.user.username]) counter[date][item.user.username] = 0;
counter[date][item.user.username] += 1;
}
return counter;
} catch (err) {
console.log(err);
}
}
}
The point of the static method above is to fetch all orders and count how many times which user has handled the order.
Now when i console.log the data from the router endpoint i see the data on console perfectly just how i wanted it to be
'2021-07-03 22': [
Johnson: 10,
Mikaels: 15,
Vasquez: 24,
Blaskovich: 3
],
'2021-07-03 23': [
Johnson: 2,
Vasquez: 12,
Mikaels: 15,
Blaskovich: 5
]
The problem is when i make a request to my endpoint it returns an empty array []. What am i missing here?
Change this:
if (!counter[date]) counter[date] = [];
to this:
if (!counter[date]) counter[date] = {};
And this:
let counter = [];
to this:
let counter = {};
Your code here:
if (!counter[date][item.user.username]) counter[date][item.user.username] = 0;
Is adding a property to an array, not adding an array element. And JSON.stringify() ignores properties on an array. It only serializes actual array elements so when you try to send the JSON version of the array, it always appears empty.
So, when you call res.status(200).send({ data });, the .send() method serializes your object which contains a bunch of arrays that have no actual array elements, only properties.
By changing the arrays to be objects, then JSON.stringify() will serialize all those properties. Arrays and Objects have many things in common, but they are not the same. You should use the appropriate type for your situation.
In addition, change this:
} catch (err) {
console.log(err);
}
to this:
} catch (err) {
console.log(err);
throw err;
}
So that you are properly propagating errors back to the caller. Otherwise, you're just eating the error and returning undefined, both of which will cause problems for the caller.
Related
I am building a fitness tracker through a class, it gives me built in tests to use as well. I am having an issue with passing this one in specific. I shortened the test specs for convenience.
Expected[{"activities": [{"activityId": 3,
Received {"publicRoutines": [{"activities": [{"activityId": 3,
1. Gets a list of public routines for a particular user.
2. Gets a list of all routines for the logged in user
I understand that the publicRoutines are sent in the res.send() but without the curly brackets, it sends over a failed test that is in my catch. Is there a way to send over both of these functions in my code to match the expected result?
usersRouter.get(`/:username/routines`,async(req,res) =>{
const username = req.params.username
try{
if(username){
const userRoutines = await getAllRoutinesByUser({username});
const publicRoutines = await getPublicRoutinesByUser({username})
console.log(publicRoutines, userRoutines)
res.send({publicRoutines, userRoutines})
}else{
return null;
}
}catch(error){
throw Error('Failed to get', error)
}
})
Yes, you can modify your code to send the expected result format by combining the two objects into a single object:
usersRouter.get(`/:username/routines`, async (req, res) => {
const username = req.params.username;
try {
if (username) {
const userRoutines = await getAllRoutinesByUser({ username });
const publicRoutines = await getPublicRoutinesByUser({ username });
console.log(publicRoutines, userRoutines);
res.send({ activities: [...publicRoutines.activities, ...userRoutines.activities] });
} else {
return null;
}
} catch (error) {
throw Error("Failed to get", error);
}
});
This way, you are combining the arrays of activities from both publicRoutines and userRoutines and returning it in the format that the test is expecting.
I tried to find the solutions over here but unable to get success while using $pull as the array values I have does not contain `mongo_id'.
So the scenario is that , I am trying to delete the specific comment of the particular user which I am passing through query params. M
My mongo data looks like this:
Now I am making API Delete request like this : http://localhost:8000/api/articles/learn-react/delete-comment?q=1 on my localhost .
ANd finally my code looks like this:
import express from "express";
import bodyParser from "body-parser";
import { MongoClient } from "MongoDB";
const withDB = async (operations, res) => {
try {
const client = await MongoClient.connect(
"mongodb://localhost:27017",
{ useNewUrlParser: true },
{ useUnifiedTopology: true }
);
const db = client.db("my-blog");
await operations(db);
client.close();
} catch (error) {
res.status(500).json({ message: "Error connecting to db", error });
}
};
app.delete("/api/articles/:name/delete-comment", (req, res) => {
const articleName = req.params.name;
const commentIndex = req.query.q;
withDB(async(db) => {
try{
const articleInfo = await db.collection('articles').findOne({name:articleName});
let articleAllComment = articleInfo.comments;
console.log("before =",articleAllComment)
const commentToBeDeleted = articleInfo.comments[commentIndex];
//console.log(commentToBeDeleted)
// articleAllComment.update({
// $pull: { 'comments':{username: commentToBeDeleted.username }}
// });
articleAllComment = articleAllComment.filter( (item) => item != commentToBeDeleted );
await articleAllComment.save();
console.log("after - ",articleAllComment);
//yaha per index chahiye per kaise milega pta nhi?
//articleInfo.comments = gives artcle comment
res.status(200).send(articleAllComment);
}
catch(err)
{
res.status(500).send("Error occurred")
}
},res);
});
I have used the filter function but it is not showing any error in terminal but also getting 500 status at postman.
Unable to figure out the error?
I believe you'll find a good answer here:
https://stackoverflow.com/a/4588909/9951599
Something to consider...
You can use MongoDB's built-in projection methods to simplify your code.
https://docs.mongodb.com/manual/reference/operator/projection/positional/#mongodb-projection-proj.-
By assigning a "unique ID" to each of your comments, you can find/modify the comment quickly using an update command instead of pulling out the comment by order in the array. This is more efficient, and much simpler. Plus, multiple read/writes at once won't interfere with this logic during busy times, ensuring that you're always deleting the right comment.
Solution #1: The recommended way, with atomic operators
Here is how you can let MongoDB pull it for you if you give each of your comments an ID.
await db.collection('articles').updateOne({ name:articleName },
{
$pull:{ "comments.id":commentID }
});
// Or
await db.collection('articles').updateOne({ name:articleName, "comments.id":commentID },
{
$unset:{ "comments.$":0 }
});
Solution #2 - Not recommended
Alternatively, you could remove it by index:
// I'm using "3" here staticly, put the index of your comment there instead.
db.collection('articles').updateOne({ name:articleName }, {
$unset : { "comments.3":0 }
})
I do not know why your filter is erroring, but I would recommend bypassing the filter altogether and try to utilize MongoDB's atomic system for you.
please suggest me how to make a selection from database comparing the ID in the collection with each element of the array?
Here is the code that unfortunately returns an empty array:
index(req, res) {
Room.find({_id: req.user.rooms.forEach((item)=>{
return item;
})
})
.then((rooms) => {
console.log(rooms)
res.send(rooms)
}
)
.catch(err => res.status(400).json(err));
}
req.user.rooms - each item of this array is ID, that I want to compare with what is in the collection Room.
It's pretty straight-forward in their docs for how to query items in a list.
Your code should look something like this:
index(req, res) {
// Additional validation should be done to make sure that req.user.rooms
// is an array with length > 0. I'll leave that for you to do.
const rooms = req.user.rooms;
Room.find({ _id: rooms})
.then((rooms) => {
console.log(rooms)
res.send(rooms)
})
.catch(err => res.status(400).json(err));
}
Going beyond that, you should really not be doing DB queries from your controller; it's not a good architectural practice This is how it could look in your node service
roomController.js
const RoomService = require("/path/to/services/directory"); // Let services contain all business logic. Don't give them anything related to your web server framework
async index(req, res) {
// Additional validation should be done to make sure that req.user.rooms
// is an array with length > 0. I'll leave that for you to do.
try {
const rooms = await RoomService.retrieveById(req.user.rooms);
res.send( { success: true, data: rooms } ); // We send back the result when we get one
} catch ( err ) {
// We respond to the client with our error, ideally you'll log it too
res.status( err.statusCode ).send( { success: false, error: err } );
}
}
RoomService.js
const Room = require("/path/to/your/model");
module.exports = {
retrieveById: async function(ids) {
try {
return await Room.find({ _id: ids}); // Typically this would be abstracted to a DB layer. but putting it here for brevity
} catch( err ) {
throw new Error( err ); // This is caught in our controller, which we send to client
}
}
};
I am new to JS and async operations. In a router of nodeJS using express, I have aggregated some data from mongo using mongoose. The data is weather data collected from different sites every 15 minutes interval. I processed the data with mongoose aggregate pipeline to get hourly data and group by each site. But the data needs a further process to get periods where for example relative humidity over 90% and assign scores to each period so I wrote some synchronous functions that target each site (each geojson object).
Mongoose looks something like that:
module.exports.filteredData = function (collection, dateInput) {
return collection.aggregate([
{
$addFields :{
DateObj: {
$dateFromString: {
dateString: "$DateTime",
format: '%Y-%m-%d'
}
},
}
},
{
$addFields :{
NewDateTimes: {
$dateFromParts:{
'year': {$year: '$DateObj'},
'month':{$month: '$DateObj'},
'day':{$dayOfMonth: '$DateObj'},
'hour': {$toInt: "$Time"}
}
}
}
}
...
synchronouse functions:
const calcDSV = function(featuresJSON){
// featuresJSON
const SVscore = [];
const tuEval = featuresJSON.features.properties.TU90; // array
const obArr = featuresJSON.features.properties.OB; // array
const periodObj = getPeriods(tuEval);// get period position
const paramObj = getParams(periodObj, obArr); // get parameters
const periodDate = getPeriodDate(featuresJSON, periodObj);
const removeTime = periodDate.beginDate.map(x=>x.split('T')[0]);
let hourly = paramObj.hourCounts;
let avgTemps = paramObj.avgTemps;
for(let i = 0;i<hourly.length; i++){
let score = assignScore(avgTemps[i], hourly[i]);
SVscore.push(score);
}
// output sv score for date
const aggreScore = accumScore(removeTime, SVscore);
aggreScore.DSVdate = aggreScore.Date.map(x=>new Date(x));
featuresJSON.features.properties.periodSV = SVscore;
featuresJSON.features.properties.Periods = periodDate;
featuresJSON.features.properties.DSVscore = aggreScore;
return featuresJSON;
}
Now I am stuck on how to apply those function on each site return by the mongoose aggregate pipeline on a post request:
router.post('/form1', (req, res, next)=>{
const emdate = new Date(req.body.emdate);
const address = req.body.address;
const stationDataCursor = stationData.filteredData(instantData, emdate);
stationDataCursor.toArray((err, result)=>{
if(err){
res.status(400).send("An error occurred in Data aggregation")
};
res.json(result.map(x=>calcDSV.calcDSV(x)));
})
});
I tried in the callback:
stationDataCursor.toArray((err, result)=>{
if(err){
res.status(400).send("An error occurred in Data aggregation")
};
res.json(result.map(async (x)=>await calcDSV.calcDSV(x))));
})
and using then():
stationDataCursor.toArray().then((docArr)=>{
let newfeature = await docArr.map(async (x)=> await calcDSV.calcDSV(x))));
res.json(newfeature);
})
or make calcDSV() returns new promise
return new Promise((rej, res)=>{
resolve(featuresJSON);
})
I would expect to see all sites with a new feature added in the HTTP response output. But most of the time, I got ReferenceError: error is not defined.
I think I have figured it out:
after all, have to make all synchronous functions asynchronous by prepending async to those functions;
rewrite this part in the post router function, especially the array map part. I read from this. and in the map() gonna have try...catch... in it, otherwise it won't work.
await stationDataCursor.toArray().then(async (docArr)=>{
const newfeature = await Promise.all(docArr.map(async function(x){
try{
const feature = await calcDSV.calcDSV(x);
return feature
} catch(err){
console.log("Error happened!!! ", err);
}
}));
res.json(newfeature)
})
Hope it helps.
I use NodeJS to insert documents in MongoDB. Using collection.insert I can insert a document into database like in this code:
// ...
collection.insert(objectToInsert, function(err){
if (err) return;
// Object inserted successfully.
var objectId; // = ???
});
// ...
How can I get the _id of inserted object?
Is there any way to get the _id without getting latest object inserted _id?
Supposing that in same time a lot of people access the database, I can't be sure that the latest id is the id of object inserted.
A shorter way than using second parameter for the callback of collection.insert would be using objectToInsert._id that returns the _id (inside of the callback function, supposing it was a successful operation).
The Mongo driver for NodeJS appends the _id field to the original object reference, so it's easy to get the inserted id using the original object:
collection.insert(objectToInsert, function(err){
if (err) return;
// Object inserted successfully.
var objectId = objectToInsert._id; // this will return the id of object inserted
});
There is a second parameter for the callback for collection.insert that will return the doc or docs inserted, which should have _ids.
Try:
collection.insert(objectToInsert, function(err,docsInserted){
console.log(docsInserted);
});
and check the console to see what I mean.
As ktretyak said, to get inserted document's ID best way is to use insertedId property on result object. In my case result._id didn't work so I had to use following:
db.collection("collection-name")
.insertOne(document)
.then(result => {
console.log(result.insertedId);
})
.catch(err => {
// handle error
});
It's the same thing if you use callbacks.
I actually did a console.log() for the second parameter in the callback function for insert. There is actually a lot of information returned apart from the inserted object itself. So the code below explains how you can access it's id.
collection.insert(objToInsert, function (err, result){
if(err)console.log(err);
else {
console.log(result["ops"][0]["_id"]);
// The above statement will output the id of the
// inserted object
}
});
if you want to take "_id" use simpley
result.insertedId.toString()
// toString will convert from hex
Mongo sends the complete document as a callbackobject so you can simply get it from there only.
for example
collection.save(function(err,room){
var newRoomId = room._id;
});
You could use async functions to get _id field automatically without manipulating data object:
async function save() {
const data = {
name: "John"
}
await db.collection('users').insertOne(data)
return data
}
Returns (data object):
{
_id: '5dbff150b407cc129ab571ca',
name: 'John',
}
Now you can use insertOne method and in promise's result.insertedId
#JSideris, sample code for getting insertedId.
db.collection(COLLECTION).insertOne(data, (err, result) => {
if (err)
return err;
else
return result.insertedId;
});
Similar to other responses, you can grab the variable using async await, es6+ features.
const insertData = async (data) => {
const { ops } = await db.collection('collection').insertOne(data)
console.log(ops[0]._id)
}
Another way to do it in async function :
const express = require('express')
const path = require('path')
const db = require(path.join(__dirname, '../database/config')).db;
const router = express.Router()
// Create.R.U.D
router.post('/new-order', async function (req, res, next) {
// security check
if (Object.keys(req.body).length === 0) {
res.status(404).send({
msg: "Error",
code: 404
});
return;
}
try {
// operations
let orderNumber = await db.collection('orders').countDocuments()
let number = orderNumber + 1
let order = {
number: number,
customer: req.body.customer,
products: req.body.products,
totalProducts: req.body.totalProducts,
totalCost: req.body.totalCost,
type: req.body.type,
time: req.body.time,
date: req.body.date,
timeStamp: Date.now(),
}
if (req.body.direction) {
order.direction = req.body.direction
}
if (req.body.specialRequests) {
order.specialRequests = req.body.specialRequests
}
// Here newOrder will store some informations in result of this process.
// You can find the inserted id and some informations there too.
let newOrder = await db.collection('orders').insertOne({...order})
if (newOrder) {
// MARK: Server response
res.status(201).send({
msg: `Order N°${number} created : id[${newOrder.insertedId}]`,
code: 201
});
} else {
// MARK: Server response
res.status(404).send({
msg: `Order N°${number} not created`,
code: 404
});
}
} catch (e) {
print(e)
return
}
})
// C.Read.U.D
// C.R.Update.D
// C.R.U.Delete
module.exports = router;