unfortunately I have problems to post an Array to my Express Server.
I post an item and if it exists in the db it will be deleted.
My code:
item.routes.js
module.exports = app => {
const item = require("../controllers/item.controller.js");
app.post("/itemr", item.IsReserved);
};
item.controller.js
const Item = require("../models/item.model.js");
exports.IsReserved = (req, res) => {
// Validate request
if (!req.body) {
res.status(400).send({
message: "Content can not be empty!"
});
}
const item = new Item({
itemId: req.body.assetId,
botId: req.body.botId,
});
Item.IsReserved(item, (err, data) => {
if (err)
res.status(500).send({
message:
err.message || "Some error occurred while creating the Item(db)."
});
else res.send(data);
});
};
item.model.js
const sql = require("./db.js");
Item.IsReserved = (RItem, result) => {
sql.query(`SELECT * FROM ReservedItems WHERE itemId = "${RItem.itemId}"`, (err, res) => {
if(res.length){
sql.query(`DELETE FROM ReservedItems WHERE itemId = "${RItem.itemId}"`, (err, res) => {
if (err) {
console.log("error: ", err);
result(err, null);
return;
}
result(null, { id: res.insertId, ...RItem });
});
}
else{
result(null, { message: "nothing to do"});
}
});
};
module.exports = Item;
A single item works great with a post like this:
{
"assetId" : 3,
"botId" : "1"
}
but if I want to insert multiple items like this:
[
{
"assetId" : 3,
"botId" : "1"
},
{
"assetId" : 4,
"botId" : "1"
}
]
The result from this is "nothing to do"...
My sql log shows: Query SELECT * FROM ReservedItems WHERE itemId = "undefined"
I saw another post here which had a similar problem, but I don't know how to implement something like ".create()" into my code to recognize multiple items.
I would be very thankful for every answer!
There are many approaches to fix your problem :)
First of all in your controller, this is not possible anymore as you send an array of items in your request payload. So you need to define an array aswell :
const items = req.body.map((item) => new Item({
itemId: item.assetId,
botId: item.botId,
});
As your method IsReserved handles only 1 item, you can use Promise.all to apply modifications. Something like this :
Promise.all( items.map( (item) => Item.isReserved(item)))
.then( (result) => ...do something here)
.catch( (error) => ...do something else here)
Another approach could be to change the SQL statement and use WHERE IN
SELECT * FROM ReservedItems WHERE itemId IN (itemid1, itemid2...)
This will get you an array of items that you can then delete the same way. It seems better in terms of performance.
I also recommand to use async/await syntax as it's way easier to read code containing promises
Related
In an Express JS connected to a mySQL db, I am trying to get some data of an already defined route/ query:
// customers.model.js
CUSTOMERS.getAll = (result) => {
let query = "SELECT * FROM customers"
sql.query(query, (err, res) => {
if (err) {
console.log("error: ", err)
result(null, err)
return
}
result(null, res)
})
}
// customers.controller.js
// GET customers is a standalone route and should output all the customers when called.
const CUSTOMERS = require("../models/customers.model.js")
exports.findAll = (req, res) => {
return CUSTOMERS.getAll((err, data) => {
if (err)
res.status(500).send({
message: err.message ||
"Some error occurred while retrieving customers...",
})
else res.send(data)
})
}
In payments.controller.js I would firstly like to get all users so I can do something with the data:
// payments.controller.js
// GET payments is also a standalone route and should get the customers,
// do something with the data and output a calculation with the help of this data
const CUSTOMERS = require("../models/customers.model.js")
exports.calculateAll = (req, res) => {
const customers = CUSTOMERS.getAll((err, data) => {
console.log('this always has correct data', data)
if (err) return err
else return data
})
console.log('this is always undefined', customers)
...
res.send(whatEverCalculatedData)...
}
But that data here is always undefined.
What am I doing wrong in the above, and what's the correct way to call this route inside another route?
I know it has similarities with this question but I couldn't sort it out for my particular example.
It's due to your call which is asynchronous.
You must wait your data being ready before rendering the results.
Maybe you could to use Promises or async/await statements.
For example:
CUSTOMERS.getAll = async () => {
const query = "SELECT * FROM customers";
try {
return await sql.query(query);
} catch (e) {
console.log(`An error occurred while fetching customers: ${e.message}.`);
return null;
}
}
exports.calculateAll = async (req, res) => {
try {
const data = await CUSTOMERS.getAll();
res.send(whatEverCalculatedData);
} catch (e) {
res.send(`Something went wront: ${e.message}.`);
}
}
I have been trying like crazy to find a solution to my problem, but nothing seems to work and I don't know where I am going wrong. I am creating an app using the PERN stack, and I have an array of data with a length of 24.
I iterate through my data array with following snippet of code (this is after trying to find solutions but the result is always the same):
const createEntry = async function (data) {
let whatever = await Promise.all(
data.map(async (item) => {
try {
console.log(`${item.name}`);
await Entry.post("/", item); //call to database
} catch (err) {
console.log(err);
}
})
);
whatever.then(console.log("I hate my life."));
};
I know the entire data array is being iterated through because of the console.logs, but the call to Entry.post() is only happening like maybe six times, and I am not getting all of my data entered into my database.
My express app.post code looks like this:
app.post("/url", async (req, res) => {
try {
const results = await db.query(
"INSERT INTO database (id, name) values ($1, $2)",
[
req.body.id,
req.body.name,
]
);
res.send({
status: "success",
results: results.rows.length,
data: {
entry: results.rows[0],
},
});
} catch (err) {
console.log(`${err.detail} for ${req.body.name}`);
}
});
So, I resolved this on my own and found a working solution.
My createEntry code from my question:
const createEntry = async function (data) {
let whatever = await Promise.all(
data.map(async (item) => {
try {
console.log(`${item.name}`);
await Entry.post("/", item); //call to database
} catch (err) {
console.log(err);
}
})
);
whatever.then(console.log("I hate my life."));
};
now looks like this:
const createEntry = async function (data) {
try {
let result = await CreateDB.post("/", data);
return result;
} catch (err) {
console.log(err);
}
};
And my app.post code
app.post("/url", async (req, res) => {
try {
const results = await db.query(
"INSERT INTO database (id, name) values ($1, $2)",
[
req.body.id,
req.body.name,
]
);
res.send({
status: "success",
results: results.rows.length,
data: {
entry: results.rows[0],
},
});
} catch (err) {
console.log(`${err.detail} for ${req.body.name}`);
}
});
Now looks like this:
app.post("/url", async (req, res) => {
try {
const results = await db.query(
"INSERT INTO database (id, name) values ($1, $2)",
[
req.body.id,
req.body.name,
]
);
res.send(res.rows[0]);
} catch (err) {
console.log(`${err.detail} for ${req.body.name}`);
}
});
And my call to my createEntry is:
let temp = {obj: some object};
createEntry(temp).then((newEntry) => {
dbArray.push(newEntry.data);
manipulateData(newEntry.data);
});
And with this I am now able to create a database entry, retrieve the database object and do work with it and it works for any size array which makes me really happy. So hopefully, if anyone has a similar problem, this can help.
Hi I am building an RESTful API in Node using mongoose to manage data on a practice food delivery site I am building.
I want to setup a patch route that will remove an order Item from my items array nested in my Orders document based on a request from the user identifying the specific item with a name or ID.
I have a patch route which pushes a new order item into the Items Array nested in the Orders document, I want this patch route to also be able to remove a specific Item from the array based on a prop such as name or ID
I have tried using the Update and UpdateOne methods and I think I'm just getting the syntax wrong or something as I keep getting errors.
Server.js:
require("dotenv").config()
const express = require("express");
const mongoose = require("mongoose");
const app = express();
mongoose.connect(process.env.DATABASE_URL)
const db = mongoose.connection
db.on("error", () => console.error(error))
db.once("open", () => console.log("connected to database"))
app.use(express.json())
const subscribersRouter = require("./routes/subscribers")
const suscribersLoginRouter = require ("./routes/login")
const restaurantsRouter = require("./routes/restaurants")
const ordersRouter = require("./routes/orders")
app.use("/subscribers", subscribersRouter)
app.use("/login", suscribersLoginRouter)
app.use("/restaurants", restaurantsRouter)
app.use("/orders", ordersRouter)
app.listen(3000, () => {
console.log("Server has started on port 3000")
});
Order Model:
const mongoose = require("mongoose")
const orderSchema = new mongoose.Schema({
userID: {
type: String,
required: true
},
total: {
type: Number,
required: true
},
items: {
type: Array,
required: true
}
})
module.exports = mongoose.model("order", orderSchema)
Orders Route (you will see here that I have a patch route which pushes a new order item into the Items Array nested in the Orders document, I want this patch route to also be able to remove a specific Item from the array based on a prop such as name or ID, the issue I am have is 1. How to create an if statement that gets the update of the item to be triggered and the the code id use in that if statement to actually update that Item)
const express = require("express")
const router = express.Router()
const Order = require("../models/order")
// Getting All
router.get("/", async (req, res) => {
try {
const orders = await Order.find()
res.json(orders)
} catch (err) {
res.status(500).json({
message: err.message
})
}
})
// Getting One
router.get("/:id", getOrder, (req, res) => {
res.json(res.order)
})
// Creating One
router.post("/", async (req, res) => {
const order = new Order({
userID: req.body.userID,
total: req.body.total,
items: req.body.items
})
try {
console.log(order)
const newOrder = await order.save()
res.status(201).json(newOrder)
} catch (err) {
res.status(400).json({
message: err.message
})
}
})
// Updating One
router.patch("/:id", getOrder, async (req, res) => {
if (req.body.userID != null) {
res.order.userID = req.body.userID
}
if (req.body.total != null) {
res.order.total = req.body.total
}
if (req.body.items != null) {
const currentItems = res.order.items
const newItem = req.body.items
currentItems.push(newItem)
}
try {
const updatedItems = await res.order.save()
res.json(updatedItems)
} catch (err) {
res.status(400).json({
message: err.message
})
}
})
// Deleting One
router.delete("/:id", getOrder, async (req, res) => {
try {
await res.order.remove()
res.json({
message: "Deleted Order"
})
} catch (err) {
res.status(500).json({
message: err.message
})
}
})
async function getOrder(req, res, next) {
let order
try {
order = await Order.findById(req.params.id)
if (order === null) {
return res.status(404).json({
message: "Cannot Find Order"
})
}
} catch (err) {
return res.status(500).json({
message: err.message
})
}
res.order = order
next()
}
module.exports = router
TEST Requests:
# ORDERS
# Get All
GET http://localhost:3000/orders
###
#Get One
GET http://localhost:3000/orders/627fe8e575a8229d0ae81e73
###
#Create One
POST http://localhost:3000/orders
Content-Type: application/json
{
"userID": "627f8b476fa64425928750c9",
"total":50,
"items": [
{
"name": "Burder",
"price": "R20",
"description": "A good Fuggen Waffel"
},
{
"name": "Hotdog",
"price": "R20",
"description": "A good Fuggen Waffel"
},
{
"name": "Bunny Chow",
"price": "R20",
"description": "A good Fuggen Waffel"
},
{
"name": "Pizza",
"price": "R20",
"description": "A good Fuggen Waffel"
}
]
}
###
#Delete One or all
DELETE http://localhost:3000/orders/628202c3b208aebc7f7f8f98
###
# Update on (add Order Item)
PATCH http://localhost:3000/orders/628202c3b208aebc7f7f8f98
Content-Type: application/json
{
"items": {
"name": "gravy",
"price": "R20",
"description": "A good Fuggen Waffel"
}
}
###
I'm not sure I understood you correctly. I understood that you need the PATCH route to also delete an item from the items array by name.
So here is my solution to it:
Because you already fetched the order and you just want to delete a specific item from the items property, you can use filter to do so before saving the order document.
res.order.items = res.order.items.filter(({ name }) => name !== itemNameToRemove);
Like this:
// Updating One
router.patch("/:id", getOrder, async(req, res) => {
const {
userID,
total,
items,
itemNameToRemove
} = req.body;
if (userID != null) {
res.order.userID = userID;
}
if (total != null) {
res.order.total = total;
}
if (items != null) {
const newItem = items;
res.order.items.push(newItem);
if (itemNameToRemove) {
res.order.items = res.order.items.filter(({
name
}) => name !== itemNameToRemove);
}
}
try {
const updatedItems = await res.order.save()
res.json(updatedItems)
} catch (err) {
res.status(400).json({
message: err.message
})
}
})
you can use $pull for this.
Order.update(
{ userID : "userID123" },
{$pull : {"items" : {"name":"gravy"}}}
)
This will delete the object with name as gravy belong to the userID : userID123
I am working on an application where I can save destinations to my Mongo DB. I would like to throw a custom error when trying to save a destination that already exsist in the DB. Mongoose prevents that from happening but I want clear and userfriendly error handling.
// post a new destination
router.post('/',
(req, res) => {
const newCity = new cityModel(
{
name: req.body.name,
country: req.body.country
}
)
newCity.save()
.then(city => {
res.send(city)
})
.catch(err => {
res.status(500).send('Server error')
})
});
Before saving a new destination, you can check if there is document already using findOne method, and if it exists you can return a custom error.
router.post("/", async (req, res) => {
const { name, country } = req.body;
try {
const existingDestination = await cityModel.findOne({name,country});
if (existingDestination) {
return res.status(400).send("Destionation already exists");
}
let newCity = new cityModel({ name, country });
newCity = await newCity.save();
res.send(city);
} catch (err) {
console.log(err);
res.status(500).send("Server error");
}
});
Note that I guessed the duplication occurs when the same country and name exist. If it is not what you want, you can change the query in findOne.
Since you've created unique index, When you try to write duplicate then the result would be :
WriteResult({
"nInserted" : 0,
"writeError" : {
"code" : 11000,
"errmsg" : "E11000 duplicate key error index: test.collection.$a.b_1 dup key: { : null }"
}
})
Your code :
Constants File :
module.exports = {
DUPLICATE_DESTINATION_MSG: 'Destionation values already exists',
DUPLICATE_DESTINATION_CODE: 4000
}
Code :
//post a new destination
const constants = require('path to constants File');
router.post('/',
(req, res) => {
const newCity = new cityModel(
{
name: req.body.name,
country: req.body.country
}
)
try {
let city = await newCity.save();
res.send(city)
} catch (error) {
if (error.code == 11000) res.status(400).send(`Destination - ${req.body.name} with country ${req.body.country} already exists in system`);
/* In case if your front end reads your error code &
it has it's own set of custom business relates messages then form a response object with code/message & send it.
if (error.code == 11000) {
let respObj = {
code: constants.DUPLICATE_DESTINATION_CODE,
message: constants.DUPLICATE_DESTINATION_MSG
}
res.status(400).send(respObj);
} */
}
res.status(500).send('Server error');
})
I'm stuck in a function I'm working with ( I can be doing this all wrong ). So a quick explanation, I want to add bulk data in a collection, the collection is called "Sites" the format of the CSV is site,country,type. I'm trying to use promises for this (Bluebird). So consider the code:
Promise.each(sites, sites => new Promise((resolve, reject) => {
//console.log(sites);
let name = tools.extractDomain(req, res, sites[0]);
let country = sites[1];
let group = sites[2];
if (name != "" && country != "" && group != "") {
Site.findOne({ name: name }, "_id", function(err, duplicate) {
if (false) {
console.log("Duplicate site: " + duplicate);
} else {
//console.log("Adding " + name)
let site = new Site()
site.name = name
site.meta = {}
site.group = group
site.country = country
site.geomix = []
site.addedBy = req.user._id
site.addedAt = Date.now()
site.saveAsync().then(function(response){
tools.saveHistory(req, res, response._id, response.name, "Website Meta fetched.");
tools.saveHistory(req, res, response._id, response.name, "Link added for the first time."); //Save in history
resolve(site);
}).catch(function (e){
console.log(name);
reject();
});
}
});
}else{
console.log('Wrong Format');
}
}).then((data) => {
console.log('All websites processed!');
addedSites.push(data);
}).catch(err => {
//console.error('Failed');
}));
res.send({ status: 'ok', message: ''});
I'm making ajax calls so I return a res.send({ status: 'ok', message: ''}), I know that its in the incorrect place and I want to send some data along the res.send. Currently it sends the headers before the code actually finishes. I want to send the headers after all the data is added in Mongo but for every each in this case he resolve() so if I send the headers inside the ".then" of the ".each" I will get headers already sent error.
This might be a bit confusing. I feel I'm not doing this right. I'm going a bit crazy as well as I can't find a proper example that I can understand and implement.
But in the end my main question is: using an Ajax call what's the proper way to add let's say 1000 records in a collection using promises and actually control properly those who fail to add and those who don't?
Right now my code actually works but the logic is wrong for sure.
Thanks.
You can use bulkWrite on your model.
Ref: http://mongoosejs.com/docs/api.html#model_Model.bulkWrite
EDIT:
Sorry I misunderstood you. You need to move res.send({ status: 'ok', message: ''}); to then() and catch() blocks, so you will get something like this:
Promise.each(sites, sites => new Promise((resolve, reject) => {
// stuff you did before
}).then((data) => {
console.log('All websites processed!');
addedSites.push(data);
res.send({ status: 'ok', message: ''});
}).catch(err => {
res.send({ status: 'failed', message: err.message});
}));
This is what I came too, if someone can tell me if this is a good arch.
exports.addBulkSite = function(req, res, next) {
let siteArray = csv.parse((req.body.sites).trim()),
addedSites = [],
failedSites = [],
duplicated = [],
sites = siteArray,
size = sites.length,
processed = 0,
meta;
Promise.each(sites, sites => new Promise((resolve, reject) => {
let name = tools.extractDomain(req, res, sites[0]),
country = sites[1],
group = sites[2];
if (name != "" && country != "" && group != "") {
Site.findOneAsync({ name: name }, "_id").then(function(duplicate) {
duplicated.push(duplicate);
reject({name:name, message: 'Duplicated', critical:false});
}).catch(function(notDuplicated){
let site = new Site()
site = {
name: name,
meta: {},
group: group,
country: country, geomix:{},
addedBy: req.user._id,
addedAt:Date.now()
}
site.saveAsync().then(function(response){
tools.saveHistory(req, res, response._id, response.name, "Website Meta fetched.");
tools.saveHistory(req, res, response._id, response.name, "Link added for the first time."); //Save in history
resolve(site);
}).catch(function (e){
console.log(e);
reject({name:name, message: 'Error saving in the database. Please contact the administrator.', critical: true});
});
});
}else{
reject({name:name, message: 'Paramaters are missing', critical:false});
}
}).then((data) => {
processed++;
addedSites.push(data);
if(processed==size){
console.log('out');
res.send({ status: 'ok', addedSites: addedSites, failedSites: failedSites, duplicated: duplicated});
}
}).catch((err) => {
processed++;
console.log(err);
failedSites.push(err);
if(processed==size){
console.log('out');
res.send({ status: 'ok', addedSites: addedSites, failedSites: failedSites, duplicated: duplicated});
}
}));
}