How to use forEach when iterating over collection objects MongoDB? - javascript

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
}
}
};

Related

Sequelize update via PATCH, how to process each possible result

I'm creating a rest api for CRUD operations using Sequelize and MySql. I'm using a controller to run an update on a PATCH request to update fields of a product. It technically works, but I feel like there is a more elegant way to handle this.
Sequelize's update method will return an array of objects depending on the results. Array[0] is the number of rows affected by the update (should just be one in my case, as I'm updating by id). Array[1] will return an object with details about the update as well as all the old values and new values. Here's how I'm handling that currently:
//products.controller.js
//Update a single product using id (PUT/PATCH)
const patch = (req, res) => {
const id = req.params.id;
Product.update(req.body, { where: { id }, individualHooks: true })
.then((rowsAffected) => {
//Item not found
if (Object.entries(rowsAffected[1]).length === 0) {
res.status(404).send({
success: false,
status: 404, //Not found
message: `Product with id ${id} not found. Update failed.`,
});
return;
}
//if rowsAffected[0] === 1 then success
if (rowsAffected[0] === 1) { //row changed
res.status(200).send({
success: true,
status: 200,
message: `Product updated.`,
id: id,
payload: req.body,
});
} else {
// if rowsAffected[0] !== 1 then it failed.
res.status(200).send({
success: false,
status: 200, //Not Modified
message: `No fields have changed. Product not updated.`,
});
}
})
.catch((err) => {
res.status(500).send({
success: false,
status: 500,
message:
err.message || "Something went wrong while updating the product.",
});
});
}
As you can see, first I'm checking to see if the the update function returns the product details (meaning it successfully found it in the database). If not then sending 404. Then I check the affected rows. If 1 then success, if 0 then nothing changed. Finally I'm catching any server errors.
I feel like there is a better way rather than having to break down the update function's return (like Object.entries(rowsAffected[1]).length === 0)
This is ok if this is the only way you can check the effects of the update. What I can suggest is putting an abstraction above it.
First thing that checking (rowsAffected[0] === 1) does not make much sense, since the update is idempotent and you end up with the same resource state no matter what the actual values are. If you insist, then I would not pair success: false with a 200 ok status, because failure is failure and it requires an error message and 4xx or 5xx status. So either delete it or convert it into a proper error. Hard to find such a status code, but maybe using 409 conflict is ok in these cases https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/409 though I would just remove this part of the code. I keep it for the sake of the example.
As of the success and status properties in the body, they don't make much sense either, because they travel in the header, and it is evident from the HTTP standard that 2xx means success, 4xx and 5xx means error. So I would remove those too.
If you don't want to support detailed error codes and exception types and parameters, then just send the error messages and the body can be even a string instead of an object.
Sending the err.message to the consumers is a bad idea by unexpected errors. You don't know what you send out. You need to log them and send something general instead. Communicating errors is always a higher abstraction level stuff, many times. As of the Product with id ${id} not found. Update failed. here adding the id is not necessary, because the request contains it.
So atm. the code looks like this:
const patch = (req, res) => {
const id = req.params.id;
Product.update(req.body, { where: { id }, individualHooks: true })
.then((rowsAffected) => {
if (Object.entries(rowsAffected[1]).length === 0) {
res.status(404).send({message: `Product not found. Update failed.`});
return;
}
//if rowsAffected[0] === 1 then success
if (rowsAffected[0] === 1) { //row changed
res.status(200).send({
message: `Product updated.`,
id: id,
payload: req.body,
});
} else {
res.status(409).send({message: "No fields have changed. Product not updated."});
}
})
.catch((err) => {
res.status(500).send({message: "Something went wrong while updating the product."});
});
}
We can go further by mapping status codes to status messages and extracting the possibly repeating parts of the story into separate functions.
const patch = (req, res) => {
const id = req.params.id;
const statusMessages = {
200: "Product updated."
404: "Product not found. Update failed."
409: "No fields have changed. Product not updated.",
500: "Something went wrong while updating the product."
};
Product.update(req.body, { where: { id }, individualHooks: true })
.then(updateStatusVerification)
.then(successHandler(res, statusMessages, () => {
return {
id: id,
payload: req.body,
};
}))
.catch(apiErrorHandler(res, statusMessages));
}
function successHandler(res, statusMessages, callback){
return function (){
let body = callback();
body.message = statusMessages[200];
res.status(200).send(body);
};
}
function apiErrorHandler(res, statusMessages){
return function (err){
let statusCode = 500;
if (err instanceof NotFoundError)
statusCode = 404;
else if (err instanceof NotUpdatedError)
statusCode = 409;
res.status(statusCode).send({
message: statusMessages[statusCode]
});
};
}
function updateStatusVerification(rowsAffected){
return new Promise((resolve, reject) => {
if (Object.entries(rowsAffected[1]).length === 0)
reject(new NotFoundError);
else if (rowsAffected[0] !== 1)
reject(new NotUpdatedError);
else
resolve();
});
}
class ApiError extends Error {}
class NotFoundError extends ApiError {}
class NotUpdatedError extends ApiError {}
We can move the status messages to the documentation. So you will end up with something like this and some utility functions:
const patch = (req, res) => {
const id = req.params.id;
statusMessages = docs.product.update.statusMessages;
Product.update(req.body, { where: { id }, individualHooks: true })
.then(updateStatusVerification)
.then(successHandler(res, statusMessages, () => {
return {
id: id,
payload: req.body,
};
}))
.catch(apiErrorHandler(res, statusMessages));
}
We can go even further if this is a frequent pattern:
const patch = (req, res) => {
const id = req.params.id;
handleUpdate(
Product.update(req.body, { where: { id }, individualHooks: true }),
() => {id: id, payload: req.body},
docs.product.update.statusMessages
);
}
function handleUpdate(dbUpdatePromise, successCallback, statusMessages){
dbUpdatePromise.then(updateStatusVerification)
.then(successHandler(res, statusMessages, successCallback))
.catch(apiErrorHandler(res, statusMessages));
}
So it can be as abstract as you like, it really depends on your needs and what the current usage allows. You can decide how many and what kind of layers you need based on actual use cases and repetitions.

Error while deleting a value of element in mongoDB array using filter function?

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.

Res value is null in an app.get call done from vue.js front-end to express back-end

I am calling this code from the front-end and confirmed that there is a proper db connection and that the Id value is properly passed, and that there is a corresponding value in the database, but for some reason, res is null. What am I missing?
app.get("/api/walletlogin/user/:userId", (req, res) => {
id = req.params.userId
var query = {_id: id}
db.collection("Users").findOne(query, (err, result) => {
if (result) {
console.log(result.userName)
} else {
console.log('No User')
}
})
Here is the front-end call:
axios.get('/api/walletlogin/user/' + accounts)
.then((response) => {
console.log('Logged in With ' + accounts)
router.push('/account')
})
.catch((errors) => {
console.log('Cannot log in')
})
}).catch((err) => {
console.log(err, 'err!!')
})
You could try to convert your id to an objectID.
var ObjectId = require('mongodb').ObjectId;
var id = ObjectId(req.params.userId);
to search by id, you must use the ObjectID class from the mongodb package. Here is an example invented by me, it does not reflect the real work, but I hope it will become clear on it:
const { ObjectID } = require('mongodb');
const id = '5ee4f69bfa0b960de8aec158'; // in your example is req.params.userId
db.collection('users').findOne({ _id: new ObjectID(id)}, (error, result) => {
if (error) {
throw error;
}
console.log(result);
})
I am adding the details of the issue initially encountered in case someone else would experience it in the future. The value that is passed from the front-end is a cryptocurrency address. For some reason, some of the characters passed were upper-case, while the same address had been stored in the database with these same characters as lower case. Thus, one needs to add code to make sure that the case of the letters found in the respective addresses is ignored.
J

Create a nested return model with Knex.js

I'm using Knex.js to query a MySQL database in a Hapi.js route. The following code works but requires a nested query:
{
path: '/recipes',
method: 'GET',
handler: (req, res) => {
const getOperation = Knex.from('recipes')
// .innerJoin('ingredients', 'recipes.guid', 'ingredients.recipe')
.select()
.orderBy('rating', 'desc')
.limit(10)
.then((recipes) => {
if (!recipes || recipes.length === 0) {
res({
error: true,
errMessage: 'no recipes found'
});
}
const recipeGuids = recipes.map(recipe => recipe.guid);
recipes.forEach(r => r.ingredients = []);
const getOperation2 = Knex.from('ingredients')
.whereIn('recipe', recipeGuids)
.select()
.then((ingredients) => {
recipes.forEach(r => {
ingredients.forEach(i => {
if (i.recipe === r.guid) {
r.ingredients.push(i);
}
});
});
res({
count: recipes.length,
data: recipes
});
});
});
}
}
Is there a way to create a return model with Knex.js that has nested objects that match the parent's id/guid so that I don't have nested promises?
Short answer: No.
With Knex, you can retrieve data the same as with SQL, which is record based, not object based, so the closest that you could come would be to use a join to allow doing just a single select to retrieve a single array having elements: recipes, guids, ingredients. This would repeat the recipe & guid for each ingredient, which you avoid by using nested objects. (See the answer below by #Fazal for an example of this.)
As another alternative, you could store the ingredients as a 'blob' field in the recipe table, but I don't believe that MySQL would allow you to create an Array field, so when retrieving the data, you would have to do a transform of the field into the array. And transform it from the Array before updating it into the table. Like: storableData = JSON.stringify(arrayData) and arrayData = JSON.parse(storableData)
There are a few other things that I would suggest to help you improve the code though. (Yeah, I know, not really the question here):
Separate the routing functionality from data handling.
Also, separate data manipulation functionality from retrieval.
Use throw & .catch for creating and handling unsuccessful responses.
The separation of routing, data retrieval, data manipulation makes testing, debugging, and future comprehension easier as each function has a more atomic purpose.
Throwing/.catching unsuccessful process conditions makes it much simpler to have more comprehensive error processing by allowing you to put (most of the time) a single .catch in your router response handling (Hapi.js may even do this .catch for you???).
Also, see the other .catch and .on('query-error' that I added for logging errors. You may have a different logging mechanism you want to use rather than the console. I use Winston. And note that .on('query-error' is NOT a .catch. There will still be an Error() that is thrown, and must be handled somewhere, this will just give you good info about the failure close to the source.
(Sorry, the below code is untested)
path: '/recipes',
method: 'GET',
handler: (req, res) => {
return getRecipeNIngredients()
.then((recipes) => {
res({
count: recipes.length,
data: recipes
});
})
.catch((ex) => {
res({
error: true,
errMessage: ex.message
});
});
};
function getRecipeNIngredients() {
let recipes = null;
return getRecipes()
.then((recipeList) => {
recipes = recipeList;
const recipeGuids = recipes.map(recipe => recipe.guid);
recipes.forEach(r => r.ingredients = []);
return getIngredients(recipeGuids);
})
.then((ingredients) => {
recipes.forEach(r => {
ingredients.forEach(i => {
if (i.recipe === r.guid) {
r.ingredients.push(i);
}
});
});
return recipes;
})
.catch((ex) => {
console.log(".getRecipeNIngredients ERROR ex:",ex); // log and rethrow error.
throw ex;
});
};
function getRecipes() {
return Knex.from('recipes')
// .innerJoin('ingredients', 'recipes.guid', 'ingredients.recipe')
.select()
.orderBy('rating', 'desc')
.limit(10)
.on('query-error', function(ex, obj) {
console.log("KNEX getRecipes query-error ex:", ex, "obj:", obj);
})
.then((recipes) => {
if (!recipes || recipes.length === 0) {
throw new Error('no recipes found')
}
})
};
function getIngredients(recipeGuids) {
return Knex.from('ingredients')
.whereIn('recipe', recipeGuids)
.select()
.on('query-error', function(ex, obj) {
console.log("KNEX getIngredients query-error ex:", ex, "obj:", obj);
})
};
I hope this is Useful!
Gary.
I created a library that return nested object even it has types for typescript
Nested Knex
import * as n from 'nested-knex';
n.array(
n.type({
id: n.number("recipe.id", { id: true }),
title: n.string("recipe.title"),
ingredients: n.array(
n.type({
id: n.number("ingredients.id", { id: true }),
title: n.string("ingredients.title")
})
)
})
)
.withQuery(
knex
.from("recipes")
.innerJoin("ingredients", "recipes.guid", "ingredients.recipe")
.select()
.orderBy("rating", "desc")
.limit(10)
)
.then(recipes => {});
so recipes even have types
You can easily avoid nest query. Just use subquery as-
knex.select('*')
.from(function () {
this.select('*').from('recipes').limit(10).as('recipes'); // limit here
})
.leftJoin('ingredients', 'ingredients.recipe_id', 'recipes.guid')
.then((rec) => {
console.log(rec);
})
see.. just few lines of code.

Easy way to increment Mongoose document versions for any update queries?

I want to start taking advantage of Mongooses document versioning (__v key). I was having an issue actually incrementing the version value, then I found that you have to add this.increment() when executing a query.
Is there a way to have automatically incremented? For now, I just added it to the pre middleware for a update-type queries:
module.exports = Mongoose => {
const Schema = Mongoose.Schema
const modelSchema = new Schema( {
name: Schema.Types.String,
description: Schema.Types.String
} )
// Any middleware that needs to be fired off for any/all update-type queries
_.forEach( [ 'save', 'update', 'findOneAndUpdate' ], query => {
// Increment the Mongoose (__v)ersion for any updates
modelSchema.pre( query, function( next ) {
this.increment()
next()
} )
} )
}
Which seems to work.. But I kinda thought there would already be a way to do this within Mongoose.. am I wrong?
I'd say this is the way to go. pre middleware fits exactly this need, and I don't know any other way. In fact this is what I'm doing in all my schemas.
What you need to be aware of though, is the difference between document and query middleware.
Document middleware are executed for init, validate, save and remove operations. There, this refers to the document:
schema.pre('save', function(next) {
this.increment();
return next();
});
Query middleware are executed for count, find, findOne, findOneAndRemove, findOneAndUpdate and update operations. There, this refers to the query object. Updating the version field for such operations would look like this:
schema.pre('update', function( next ) {
this.update({}, { $inc: { __v: 1 } }, next );
});
Source: mongoose documentation.
For me the simplest way to do that is :
clientsController.putClient = async (req, res) => {
const id = req.params.id;
const data = req.body;
data.__v++;
await Clients.findOneAndUpdate({ _id: id }, data)
.then( () =>
{
res.json(Ok);
}
).catch ( err => {
Error.code = '';
Error.error = err;
res.json(Error);
})
};

Categories