Why am I getting this UNEXPECTED HTTP error in NodeJS - javascript

So guys i'm setting up my backend in NodeJS for an e-commerce website.
But I ran into an error trying to implement the "Order" method.
First the connection to mysql database :
let mysql = require('mysql')
let connection = mysql.createConnection({
host: 'localhost',
user: 'root',
password: '',
database: 'ecom_new'
})
connection.connect()
module.exports = connection
Then in my Models there is a Client class that contains the following method :
static order(orderData, callbackfn) {
orderData.products.map((product) => {
connection.query(`INSERT INTO orders SET
product_name = ?, owner = ?, quantity = ?, price = ?, client_name = ?, client_phone = ?, client_address = ?`,
[product.name, product.owner, product.count,product.price, orderData.clientName,
orderData.clientPhone, orderData.clientLoc], (err, result) => {
if (err) {
callbackfn(err)
} else {
callbackfn(null, result)
}
})
})
}
The orderData parameter in the order() method is a JSON posted from the frontend, that looks like this:
{
"products": [
{"name": "Item 1", "owner": "Clint", "count": 1, "price": 150},
{"name": "Item 2", "owner": "Steve", "count": 3, "price": 350},
{"name": "Item 3", "owner": "Bruce", "count": 6, "price": 110}
],
"clientName": "Tony Stark",
"clientPhone": "111111",
"clientLoc": "Malibu"
}
And finally the route that handles this request is coded like this :
router.post('/client/order', (req, res) => {
Client.order(req.body, (err, result) => {
if (err) {
res.json({RequestResult: 'ERROR', Message: err['sqlMessage']})
} else {
res.json({RequestResult: 'SUCCESS', Message: 'New order placed successfully'})
}
})
})
It works just fine when I try (once) to place an order from my frontend (and Postman).
But the issue is that whenever I try (again) to place an order i'm getting the [ERR_HTTP_HEADERS_SENT] error. Looks like i can only place an order once, which is nonsense.
I don't really know what is wrong and it is keeping me from moving on to other concerns of my project, help is needed.
Thanks

I think the problem is that you iterate over the products with orderData.products.map((product) => {... and for each product you call the callbackfn which in turn calls res.json({...}). So for each product a res.json({...}) is called but i think you are only allowed to call it once per request.
Try something like this in the Client class:
static order(orderData) {
return Promise.all(orderData.products.map((product) => {
return new Promise((resolve, reject) => {
//run query
if (err) reject(err)
else resolve()
})
}))
}
now you can use this function like so:
Client.order(req.body)
.then(() => res.json({ RequestResult: 'SUCCESS', Message: 'New order placed successfully' }))
.catch(err => res.json({ RequestResult: 'ERROR', Message: err['sqlMessage'] }))

Related

need Mongoose Model.find() to work when queries aren't present

I'm currently working on the freeCodeCamp Exercise Tracker Project on Replit.
my Project Link: https://replit.com/#mazorSharp/Exercise-Tracker?v=1
If you click on the link, the code I'm referring to is in the server.js file and It's the code under the comment labeled // NUMBER 3
I'm running into an issue with one of the GET routes.
GET user's exercise log: GET /api/users/:_id/logs?[from][&to][&limit]
The GET route works fine when all queries are used in the get search. Queries for the test are From, To, and Limit. If one of the queries aren't present in the GET request I get an error.
CastError: Cast to date failed for value "Invalid Date" (type Date) at path "date" for model "exerciseInfo"
What steps would I need to take to make sure if someone isn't putting in values for FROM, TO, and LIMIT queries that it wouldn't throw an error because of it?
app.get('/api/users/:_id/logs', (req, res) => {
const {from, to, limit} = req.query;
let idJson = {"id": req.params._id}
let idToCheck = idJson.id;
console.log("from=> ", from, "to=> ", to, "limit=> ", limit, "idToCheck=> ", idToCheck);
//Check ID
ExerciseInfo.findById(idToCheck, (err, data) => {
if (err) {
console.log("Error with ID => ", err);
} else {
// Find Username Documents
ExerciseInfo.find(({username: data.username}, {date: {$gte: new Date(from), $lte: new Date(to)}}), null , {limit: +limit} , (err, doc) => {
let loggedArray = []
if (err) {
console.log("error with username=> ", err);
} else {
console.log("all docs related to username=> ", doc);
let documents = doc;
let loggedArray = documents.map((item) => {
return {
"description": item.description,
"duration": item.duration,
"date": item.date.toDateString(),
}
})
const test = new LogInfo({
// "_id": idToCheck,
"username": data.username,
"from": from,
"to": to,
"count": limit,
"log": loggedArray,
})
test.save((err, data) => {
if (err) {
console.log(err);
} else {
console.log("saved exercise successfully")
res.json({
"_id": idToCheck,
"username": data.username,
"from": data.from.toDateString(),
"to": data.to.toDateString(),
"count": data.count,
"log": loggedArray,
})
}
})
}
})
}
})
})

How to check if there are no more documents to update using findOneAndUpdate

So I am learning CRUD for a school project and I followed a tutorial that was really useful. However, when I completed it I noticed that when there are no more quotes to update, it still updates quotes. How can I change this so that it will stop updating quotes that arent even there?
app.put('/quotes', (req, res) => {
quoteCollection.findOneAndUpdate(
{ name: 'Yoda' },
{
$set: {
name: req.body.name,
quote: req.body.quote
}
},
{upsert: true}
)
.then(result => {
//The if block that i am trying
if (result.deletedCount === 0) {
return res.json('No quote to delete')
}
})
.catch(error => console.error(error))
})
Why are you passing {name: "Yoda}? This route is supposed to only update the quote with "Yoda" as its name? If not, then you need to grab from the request object the quote that should be updated.
I tried to create a different version, based on the assumption that the quote that should be updated will come from the req.body:
app.put("/quotes", async (req, res) => {
//Grab the name/id/identifier for the quote you want to update from the body
const query = req.body.name;
// Try to update the document on the database
try {
const result = await quoteCollection.findOneAndUpdate(
query,
{
name: req.body.name,
quote: req.body.quote,
},
{
upsert: true,
new: true,
}
);
// If it worked, it will return the updated quote
res.status(200).json({
status: 200,
data: {
result,
},
});
} catch (err) {
res.status(400).json({
status: 400,
message: "Something went wrong",
});
}
});

Trying to populate user Document with post information, but I only have the user ID that I pushed manually

so I am trying to get the title of the post, as when I display an array of posts made on user dashboard, I can only show the ID of the post when I redirect them, this works... but I think it would make more sense to have the TITLE in the place of that. Population does not seem to push the object into the array, maybe I am understanding population incorrectly, or there is a better way to go about it... thanks .
this is the react code for that list
<ol>
{ user ? user.posts.map((item,i) => (
<React.Fragment key ={i}>
<li><Link to={`/api/posts/item/${item}`}>{item}</Link></li>
<button onClick={() => { setId(item) }}>Delete this post</button>
</React.Fragment>
)) : null }
</ol>
this is the backend code which gave me the user ID , and the populate f(n)
userModel.findOne({ email: req.body.author }, function(error, user) {
const locationURL = req.files.map((item) => item.location);
postModel.create({ ...req.body, image: locationURL }, (error, returnedDocuments) => {
if (error) {
throw new Error(error);
}
user.posts.push(returnedDocuments._id);
user.save((err) => {
console.log(err);
});
});
// this will populate the posts field in our userSchema (which contain the id references to our posts)
userModel.findOne({ email: req.body.author }).populate('posts').exec((err, user) => {
user.save((err) => {
console.log(err);
});
});
});
in the user document it looks like this, an array of ID's
[
{
"premium": false,
"max_posts": 62,
"posts_made": 57,
"posts": [
"5e21252ac51ac82838947875",
"5e212a6c3b1619294832a3f2"
],
"_id": "5e0fe3f33c2edb2f5824ddf2",
"email": "myemail#gmail.com",
"createdAt": "2020-01-04T01:01:39.840Z",
"updatedAt": "2020-01-17T03:36:36.086Z",
"__v": 22
}
]
in my userSchema I refrence Posts... but maybe I am doing it wrong and somehow can access that information...
let User = new Schema(
{
email: {
type: String,
unique: true
},
premium: {
type: Boolean,
default:false
},
max_posts: {
type: Number,
default:3
},
posts_made: {
type: Number,
default:0
},
posts: [
{
type: mongoose.Schema.Types.ObjectId,
ref: 'Post'
}
]
},
{
timestamps: true
}
);
Should I just call a axios request to get the names of those ID's that I have, I had that thought, but thought it might be unnessary calls to the DB which might become expensive. thanks.
Actually you are populating correctly, but not in the correct place. You should move the populate code inside the callback, and send the result like this.
postModel.create({ ...req.body, image: locationURL }, (error, returnedDocuments) => {
if (error) {
throw new Error(error);
}
user.posts.push(returnedDocuments._id);
user.save((err) => {
if (err) {
console.log(err);
throw new Error(err);
}
userModel.findOne({ email: req.body.author }).populate('posts').exec((err, user) => {
if (err) {
console.log(err);
throw new Error(err);
}
res.send(user); //user will have posts populated
});
});
});

Cannot add a correct invoice using ReactJS

I developped an invoice using ReactJS, nodejs and mysql, it have a dynamic table, as you can see the invoive component in this link : https://codeshare.io/5zqLeN
My router is :
exports.ajouterfact = function(req, res) {
console.log("req", req.body);
var today = new Date();
var factures = {
"Nfact": req.body.Nfact,
"Nom": req.body.Nom,
"Datefact": req.body.Datefact,
"Note": req.body.Note,
"Nomp": req.body.Nomp,
"QuantiteF": req.body.QuantiteF,
}
connection.query('INSERT INTO factures SET ?', factures, function(error, results, fields) {
if (error) {
console.log("error ocurred", error);
res.send({
"code": 400,
"failed": "error ocurred"
})
}
else {
// console.log('The solution is: ', results);
res.send({
"code": 200,
"success": "facture registered sucessfully"
});
}
})
};
My invoice is :
When I submit it, It will be added on the database by getting:
req { Nfact: '15',
Nom: 'Imen',
Datefact: '2018-09-04T09:47:32.925Z',
Note: 'Test',
QuantiteF: '' }
The Noun of product (Nomp) is null and the quantity is ''.
But when I run my backend with Postman, it works well.
How can I fix that ?

Populate subdocument in parent document Array Mongoose

I'm very new to JavaScript and Mongoose. I'm building a small project using express, mongoose and node.js.
I have a mongoose model - Client that has an Array of Transactions
var Client = mongoose.model('Client', {
name: {
type: String,
required: true,
minlength: 1
},
email: {
type: String
},
phone: {
type: Number
},
createdAt: {
type: Number,
default: null
},
transactions: [{ type: mongoose.Schema.Types.ObjectId, ref: 'Transaction' }],
_creator: {
type: mongoose.Schema.Types.ObjectId,
required: true
}
});
module.exports = {Client};
This is Transaction model:
var Client = require('./client');
var Transaction = mongoose.model('Transaction',{
_creator : { type: mongoose.Schema.Types.ObjectId, ref: 'Client' },
amount : {type: Number, min: 0},
date : {type: Number,default: null},
total: {type: Number,default: null}
});
module.exports = {Transaction};
When I POST a new Transaction it goes through and saves in db:
app.post('/clients/:id/transactions', authenticate, (req, res) => {
var id = req.params.id;
var transaction = new Transaction({
amount: req.body.amount,
date: new Date().getTime(),
total: req.body.total,
_creator: req.params.id
})
if (!ObjectID.isValid(id)) {
return res.status(404).send();
}
transaction.save().then((doc) => {
Client.findOneAndUpdate({
_id: id,
_creator: req.user._id,
transactions: req.body.transaction
});
res.send(doc);
}, (e) => {
res.status(400).send(e);
});
});
I am also able to GET all the transactions associated with the client:
app.get('/clients/:id/transactions', authenticate, (req, res) => {
var id = req.params.id;
if (!ObjectID.isValid(id)) {
return res.status(404).send();
}
Transaction.find({
_creator: id
}).then((transactions) => {
res.send({transactions});
}).catch((e) => {
res.status(400).send();
});
});
But when I make a GET call to '/clients' - Array of Transactions is empty:
{
"clients": [
{
"_id": "1095d6de3867001108b803",
"name": "Peter",
"email": "peter#gmail.com",
"phone": 1232321,
"_creator": "5321df6d57868ec7001108b801",
"__v": 0,
"transactions": [],
"createdAt": null
} ]
}
And this is the GET call to /clients
app.get('/clients', authenticate, (req, res) => {
Client.find({
_creator: req.user._id,
})
.populate('transactions.transaction')
.then((clients) => {
res.send({clients});
}, (e) => {
res.status(400).send(e);
console.log('Unable to get clients', e);
})
});
I know that I'm likely doing something completely wrong but I don't know where I need to look for my mistake. Please help!
I would check if the client exist before adding a transaction. A transaction needs a client first.
Forewarn, I'm not a fan of then and catch so this answer does not use it. I normally use async.js when dealing with multiple asynchronous operations.
Anyways, I would do it like
app.post('/clients/:id/transactions', authenticate, (req, res) => {
Client.findOne({ _id: req.params.id }, (err, client) => {
if (err)
return res.status(400).send(err);
if (!client)
return res.status(400).send(new Error('No client'));
Transaction.create({
amount: req.body.amount,
date: new Date(), // I don't think you need .getTime()
total: req.body.total,
_creator: client._id
}, (err, transaction) => {
if (err)
return res.status(400).send(err);
client.transactions.push(transaction._id);
client.save(err => {
if (err)
return res.status(400).send(err);
res.json(transaction);
});
});
});
});
Good idea to also turn on debugging mode to see your queries: mongoose.set('debug', true).
You might also find using timestamps option for Transaction schema more useful than explicitly using date field
To get clients with their transactions
app.get('/clients', authenticate, (req, res) => {
Client.find({ _creator: req.user._id }).populate('transactions').exec((err, clients) => {
if (err)
return res.status(400).send(err);
res.json(clients);
});
});
so first of all i don't exactly know what _creator key in Client model representing, it's probably user identifier who has some clients, but if I'm wrong please correct me.
Honestly I don't know why you are making two way document connection, (keeping client in transactions, and also keeping transactions in clients) in my opinion first option is better for mongodb and using that you can easily get transaction's list with find, or mongodb aggregation, but you can't get data using populate.
In second option you need to remember that one document could have maximum 16MB. And also keeping thousands of transactions in one array is not well for performance. Think about example that you have 5000 transaction and you want to show list with pagination (50 records per page), with array option you have to get whole document, and splice array to 50 records. In first option you could use mongodb skip and limit. Please think about it.
Returning to question, mistake you are doing is here:
transaction.save().then((doc) => {
Client.findOneAndUpdate({
_id: id,
_creator: req.user._id,
transactions: req.body.transaction
});
res.send(doc);
Here you don't exactly say how this document should have to updated about.
Mongoose in method findOneAndUpdate using mongodb findAndModify method. But params are used from update.
https://docs.mongodb.com/manual/reference/method/db.collection.update/
And also documentation says that you what params like:
Query#findOneAndUpdate([query], [doc], [options], [options.passRawResult], [options.strict], [callback])
So first query param is mongo query to find one document in database, next param is object with updating query, and after that you could send some additional options in third param. So your code should looks like this:
transaction.save().then((doc) => {
Client.findOneAndUpdate({
_id: id,
_creator: req.user._id,
}, {
$addToSet: {
transactions: doc._id,
}
});
res.send(doc);
You could use addToSet or push both are putting element into array, but addToSet avoiding duplicates in array. And as you se we push new transaction identifier into this array. And after all you only populate transaction key.
I hope I helped. If you have any questions please ask.

Categories