Failing to make axios.put from react to node.js server - node.js

I have react component where i trying to change status of object. However I'm failing and didn't understand whether I'm on right way.
This is nodeJS server with express and mongoose.
Frond end side:
handleSubmit2(event){
const body= {status: this.state.newstatus}
event.preventDefault()
axios.put('http://127.0.0.1:3010/api/users/:user_id',{body})
alert("Status Changed")
}
in render:
{hits.map(item => <li key={item._id}> {item.firstname} {item.lastname} <br /> <strong> {item.status}</strong>
<form autoComplete="off" className="new_status" onSubmit={this.handleSubmit2}>
<FormControl>{this.loadoptions()}</FormControl>
<input type="submit" value="Submit" className="input"/>
<br />
</form>
My back-end:
router.put( '/:user_id', ( req, res ) => {
console.log(req.body.body.status)
User.findByIdAndUpdate(req.params.user_id, req.body.body.status)
.then(data => {
if(follower)
res.send(data);
else
res.status(404).send("Follower not found");
})
.catch (e => res.status(400).send("Follower not exist"));
});
UserSchema:
const userSchema = new mongoose.Schema ({
firstname: String,
lastname: String,
status: String
});
const User = mongoose.model('User', userSchema);
Link to git:
https://github.com/fmpro12/users_add
When i submit form i want to change status of specific people from array.
What I'm doing wrong?

Related

Not populating mongoDB database with data being entered on angular

I am writing a post form function using MEAN stack which saves the data to the DB.
When entering the data through postman on the node, express, mongoose side it stores in the database. however when entering the date through the angular frontend, the data isnt storing, this method i used for other forms and it worked however this one just doesn't:
HTML:
<form [formGroup]="form" (submit)="addMessage()">
<mat-form-field>
<mat-label>Username:</mat-label>
<input
placeholder="Username"
matInput
formControlName="username"
class="form-control"
type="string"
required
/>
</mat-form-field>
<br />
<mat-form-field>
<mat-label>Message:</mat-label>
<input
placeholder="Type Message Here..."
matInput
formControlName="message"
class="form-control"
type="string"
required
/>
</mat-form-field>
<br />
<mat-form-field>
<mat-label>Message Date:</mat-label>
<input
placeholder="Type Message Here..."
matInput
formControlName="messageDateTime"
class="form-control"
type="date"
required
/>
</mat-form-field>
<br />
<button mat-raised-button color="basic" type="submit">Send</button>
<br />
<mat-divider></mat-divider>
</form>
Typescript:
import { Component, OnInit } from '#angular/core';
import { FormControl, FormGroup } from '#angular/forms';
import { Router } from '#angular/router';
import { MessageBoardService } from 'src/app/service/message-board.service';
import { Message } from 'src/app/models/messages.interface';
#Component({
selector: 'app-message-board',
templateUrl: './message-board.component.html',
styleUrls: ['./message-board.component.css']
})
export class MessageBoardComponent implements OnInit {
messages: Message[] = [];
constructor(private messageService: MessageBoardService) { }
form = new FormGroup({
username: new FormControl(''),
message: new FormControl(''),
messageDateTime: new FormControl(''),
});
addMessage() {
console.log('adding');
const formData = new FormData();
formData.append('username', this.form.value.username);
formData.append('message',this.form.value.message);
formData.append('messageDateTime',this.form.value.messageDateTime);
this.messageService.postMessage(formData).subscribe((d) => {
console.log(d);
});
//window.location.reload();
}
ngOnInit(): void {
this.messageService.getMessage().subscribe((M: Message[]) => {
this.messages = M;
})
}
}
Service:
postMessage(data: any){
return this.http.post<any>("http://localhost:3000/Messages", data)
.pipe(map((res:any)=>{
return res;
}))
}
The get function works fine in the services it is only the post.
Posting data using postman works well, but from the frontend it just saves the default data that is set in the mongoose schema
Schema:
const mongoose = require('mongoose');
const MessagesSchema = new mongoose.Schema({
username:{
type: String,
required: false,
default: "User"
},
message:{
type: String,
required: false,
default:"Content"
},
messageDateTime:{
type: Date,
required: false,
default: Date.now
}
})
const Messages = mongoose.model( 'Messages', MessagesSchema);
module.exports = Messages
Data Entered Using Angular Frontend:
Data Saved in Database:
(Console Output):
{username: 'User', message: 'Content', messageDateTime:
'2022-03-04T23:23:32.040Z', _id: '62229f740a9c53a525774f01', __v: 0}
message: "Content" messageDateTime: "2022-03-04T23:23:32.040Z"
username: "User"
__v: 0
_id: "62229f740a9c53a525774f01" [[Prototype]]: Object
(Data stored accessed by postman):
{
"_id": "62229f740a9c53a525774f01",
"username": "User",
"message": "Content",
"messageDateTime": "2022-03-04T23:23:32.040Z",
"__v": 0
},
I'm not sure why do you need FormData, as I have never used it in Angular
I generally send data like this to backend
let dataToSend: any = {
username: this.form.value.username,
message: this.form.value.message,
messageDateTime: this.form.value.messageDateTime
}
this.messageService.postMessage(dataToSend).subscribe((d) => {
console.log(d);
});
I'll also update the service and Content-Type header, assuming your backend is expecting JSON.
let headers = new Headers();
headers.append('Content-Type', 'application/json');
postMessage(data: any)
{
http.post('http://localhost:3000/Messages', JSON.stringify(data), {
headers : headers
}).pipe('Rest of the Code');
}

MERN stack: Cast to Number failed for value 'NaN'

I'm creating a simple user registration page where account level (1 = user & 2= admin) is putting up an error when i post a request through axios. This seemed to be a somewhat common error corrected mostly by having a default number value in the schema, which I already have. Furthermore, I do not come across this error when I test with postman.
Error:
Error: users validation failed: accLevel: Cast to Number failed for value "NaN" (type number) at path "accLevel", username: Path `username` is required., password: Path `password` is required.
at ValidationError.inspect (E:\SLIIT\Y2S2\ITP\FitnessFactory\backend\node_modules\mongoose\lib\error\validation.js:48:26)
However, when I test with post man I do not get this error and mongodb works. Like so. It also works for accLevel in int form.
{
"username": "Mary",
"password": "mary",
"accType": "customer",
"accLevel": "1"
}
Heres the user schema
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
const userSchema = new Schema({
username : {
type: String,
required: true
},
password : {
type: String,
required: true
},
accType: {
enum: ["customer", "instructor"],
//required: true
},
accLevel: {
type: Number,
default: 1
}
})
const User = mongoose.model('users', userSchema);
module.exports = User;
Heres the user route and controller file
const router = require("express").Router();
let User = require("../models/user");
//createUserAccount
router.route("/createUserAccount").post((req,res) => {
const username = req.body.username;
const password = req.body.password;
const accType = req.body.accType;
const accLevel = Number(req.body.accLevel);
const newUser = new User({
username,
password,
accType,
accLevel
})
newUser.save().then(() => {
res.json("User account created")
}).catch((err) => {
console.log(err);
})
})
//login
router.route("/userLogin/:id").post(async(req,res) => {
})
module.exports = router;
Heres is the component file for createUserAccount.jsx
//Create account component
import React, {useState} from "react";
import axios from 'axios';
//bootstrap imports
import Form from 'react-bootstrap/Form';
import Button from "react-bootstrap/Button";
import Container from 'react-bootstrap/Container';
import Row from 'react-bootstrap/Row';
import Col from 'react-bootstrap/Col';
//custom styles
import '../styles/CreateAccount.css';
function CreateAccount(){
//set states
const [username, setUsername] = useState("");
const [password, setPassword] = useState("");
const [reEnterPassword, setReEnterPassword] = useState("");
const [accType, setAccType] = useState("");
const [accLevel, setAccLevel] = useState("");
//handle change
function handleSubmit(e){
e.preventDefault();
if (password !== reEnterPassword){
alert("Passwords do not match!")
}
else if (accType === ''){
alert("Select account type!")
}
else if (accLevel === ''){
alert("Select account level!")
}
else{
const newUser = {
username,
password,
accType,
accLevel
}
console.log(newUser);
axios.post("http://localhost:8070/user/createUserAccount").then(()=>{
alert("account created")
}).catch((err)=>{
alert(err)
})
}
}
return (
<div>
<div className='createAccountBlock'>
<h1>Create Account</h1>
<form onSubmit={handleSubmit}>
<Form.Group name="username" className="mb-3" controlId="username">
<Form.Label>Username</Form.Label>
<Form.Control type="text" placeholder="Enter Username" className='formText'
onChange = {(e)=>{
setUsername(e.target.value);
}}
/>
</Form.Group>
<Form.Group name="password" className="mb-3" controlId="password">
<Form.Label>Password</Form.Label>
<Form.Control type="password" placeholder="Password" className='formText'
onChange = {(e)=>{
setPassword(e.target.value);
}}
/>
</Form.Group>
<Form.Group name="reEnterPassword" className="mb-3" controlId="reEnterPassword">
<Form.Label>Re-enter Password</Form.Label>
<Form.Control type="password" placeholder="Re-enter Password" className='formText'
onChange = {(e)=>{
setReEnterPassword(e.target.value);
}}
/>
</Form.Group>
<Container>
<Row>
<Col>
<Form.Select name="accType" aria-label="Default select example"
onChange = {(e)=>{
setAccType(e.target.value);
}}
>
<option>Select Account Type</option>
<option value="customer">Customer</option>
<option value="instructor">Instructor</option>
</Form.Select>
</Col>
<Col>
<Form.Select name="accLevel" aria-label="Default select example"
onChange = {(e)=>{
setAccLevel(e.target.value);
}}
>
<option>Select Account Level</option>
<option value="1">Level 1 (user)</option>
<option value="2">Level 2 (admin)</option>
</Form.Select>
</Col>
</Row>
<div className='flex-container'>
<Button variant="primary" type="submit" className='btn1'>
Create Account
</Button>
</div>
</Container>
</form>
</div>
</div>
);
}
export default CreateAccount;
It works when I test with postman for both int and string, but fails using axios. Btw, I tried parseInt from the frontend as well.
Any Ideas?
It doesn't look like you're sending a data object in your POST request.
axios.post("http://localhost:8070/user/createUserAccount").then(()=>{
alert("account created")
}).catch((err)=>{
alert(err)
})
axios.post needs a second argument of the object that you are sending.

Mongoose: push a string into array

I have a website where any logged-in user can leave a review for the shop.
So basically I have two schemas:
const journalSchema = new mongoose.Schema({
title: String,
category: String,
subcategory: String,
rating: Number,
review: [{type: String}],
link: String,
description: String,
});
const userSchema = new mongoose.Schema ({
username: String,
password: String,
journal: [{type: mongoose.Schema.Types.ObjectId, ref: 'Journal'}]
});
const Journal = mongoose.model("Journal", journalSchema);
const User = mongoose.model("User", userSchema);
form from the ejs file:
<div class="container my-3">
<h1>Compose</h1>
<form class="" action="/stats" method="post">
<div class="form-group">
<label for="review">Description</label>
<textarea id="review" class="form-control" name="journalReview" rows="5" cols="30"></textarea>
</div>
<button class="btn btn-primary my-2" type="submit" name="button">Publish</button>
</form>
</div>
post route:
app.post("/stats", function(req, res){
if(req.isAuthenticated()){
const favJournal = req.body.savedJournal;
const userId = req.user.id;
const userReview = req.body.journalReview;
User.findById(userId, function(err, foundUser){
Journal.findById(favJournal, function(err, foundJournal){
if(err){
console.log(err);
}
else{
if(foundUser){
foundJournal.review.push(userReview);
foundJournal.save(function(){
console.log(foundJournal);
});
foundUser.journal.addToSet(foundJournal);
foundUser.save(function(){
res.redirect("/favourite");
});
}
}
});
})
.populate('journal')
.exec(function(err, user){
if(err) return handleError(err);
});
}
else{
res.redirect("/login");
}
});
Every time I try to push review from the ejs file I keep getting this error:
events.js:353
throw er; // Unhandled 'error' event
^
TypeError: Cannot read property 'review' of null
at C:\Users\HelloThere\Desktop\miesto\app.js:337:24
at C:\Users\HelloThere\Desktop\miesto\node_modules\mongoose\lib\model.js:5065:18
at processTicksAndRejections (internal/process/task_queues.js:77:11)
Emitted 'error' event on Function instance at:
at C:\Users\HelloThere\Desktop\miesto\node_modules\mongoose\lib\model.js:5067:15
at processTicksAndRejections (internal/process/task_queues.js:77:11)
I tried different solutions from similar posts. Like mongoose methods: findOneAndUpdate and updateOne, but they just return null.
Instead of getting the shop and manipulating it with JavaScript code and then saving it back to the database, you could achieve this through findOneAnUpdate and $push operator.
For instance, this query
Shop.findById( shopId, (shop) => {
shop.products.push(product);
shop.save();
}
can be done through this query
Shop.findOneAndUpdate(
{ _id: shopId },
{ $push: {
products: product
}
})
$pop, $push, and $pull are very powerful tools to manipulate arrays in Mongoose. Take a look at the docs.
For the error you're getting, I think you're getting because you're passing a wrong journalId to findById. Check with MongoDB Compass if you do have a document with that id favJournal
I think I figured out the cause of the problem, I have two post forms in my ejs file, and since both forms have submit button with nothing differentiating them, only the first form gets called in the post route.

Trouble rendering of documents with referenced fields on Vue.js mongoose

Recently, I picked up Vue.js and Mongoose to develop a personal project to mainly track Ingredients' on hand quantities on a certain online game.
Different dishes require different ingredients. Lotus Seed - Bird Egg soup, Jewelry Soup and Jade Parcels all require different number of Lotus Head as its ingredients.
I update an ingredient's quantity by using updateOne on ingredients collection.
Unfortunately, I originally embedded the ingredients on foods / dishes, which I realized problematic
recently, coz literally you just count ingredients what you currently have.
So a food document now looks like this
{
"_id" : ObjectId("5fca4ada32195d5814510242"),
"foodName" : "Lotus Seed and Bird Egg Soup",
"onHandQty" : 20,
"ingredients" : [
"5fca481432195d581451023f",
"5fca483932195d5814510240",
"5fca48a232195d5814510241"
]
}
I read about Mongoose's populate(), and tested to output one food/dish. Unfortunately there's nothing coming out of Vue.js front-end after trying that code.
server/models/Food.js
const { Router } = require('express');
const FoodItem = require('../../models/Food');
const IngredientItem = require('../../models/Ingredient');
const router = Router()
router.get('/', async(req, res) =>{
try {
const food = await FoodItem.findOne({
foodName: 'Lotus Seed and Bird Egg Soup'
}).populate('ingredients').
exec(function (err, food) {
if (err) return handleError(err);
console.log('The food is %s', food.foodName);
});
res.send(food);
} catch (error) {
res.status(500).json({
message: error.message
})
}
});
module.exports = router
A portion of component where ingredients are rendered
client/src/components/Food.vue
<div class="tile is-ancestor">
<div class="tile">
<div class="tile is-parent">
<div class="tile is-child box">
<template v-if="food.ingredients">
<div class="ingredients-block">
<p>Ingredients List:</p>
<ul class="ingredients-list">
<li class="row" v-for="ingredient in food.ingredients" :key="ingredient._id">
<div id="ingredient-image-container">
<img class="image is-64x64" :src="require(`../assets/images/food_inv/${ingredient.imagePath}.png`)" alt="ingredient.ingredientName" :title="ingredient._id">
{{ingredient.ingredientName}}
</div>
<div class="required-qty-container">
<!-- <i class="material-icons" id="required-inner-qty">food_bank</i> -->
Required:
{{ ingredient.requiredQty }}
</div>
<div class="on-hand-qty-container">
<p>On Hand:</p>
<input v-if="ingredient.onHandQty < ingredient.requiredQty" class="input is-danger on-hand-input" type="number" v-model="ingredient.onHandQty" min="0">
<input v-else class="input is-primary on-hand-input" type="number" v-model="ingredient.onHandQty" min="0">
<!-- <button class="button is-primary save-button" #click="test({ingredient_id: ingredient._id, onhandqty: ingredient.onHandQty})"><i class="material-icons">save</i></button> -->
<button class="button is-primary save-button" #click="$emit('update-qtys', {ingredient_id: ingredient._id, onhandqty: ingredient.onHandQty})"><i class="material-icons">save</i></button>
</div>
</li>
</ul>
</div>
</template>
</div>
</div>
</div>
</div>
Whole project on Github: Food Inventory
Quick Fixes,
change your food schema's ingredients field from object to array,
const foodSchema = new mongoose.Schema(
{
foodName: String,
imagePath: String,
effect: String,
onHandQty: Number,
// correct this to array
ingredients: [{
type: mongoose.Schema.Types.ObjectId,
ref: 'Ingredient'
}]
}
);
there are 2 ways to call mongoose methods first exec() with callback and second without exec() callback,
exec with callback that you have used buy need to send response (res.send(food) or res.json(food)) from inside the exec call function,
router.get('/', async(req, res) =>{
try {
await FoodItem.find()
.populate('ingredients')
.exec(function (err, food) {
if (err) return handleError(err);
console.log('The food is %s', food);
// put response here
res.json(food);
});
} catch (error) {
res.status(500).json({ message: error.message })
}
});
exec without call back
router.get('/', async(req, res) =>{
try {
const food = await FoodItem.find()
.populate('ingredients')
.exec();
res.json(food);
} catch (error) {
res.status(500).json({ message: error.message })
}
});

Redirect response into a dynamic url

I am using node js with:
express framework
hbs framework
mongodb
My task is to redirect my response to a url with a format /users/:name/course/:courseId.
After running the code with the parameters name=James and courseId=1234, I get /users/James/course/1234 in the browser url. However, I am not able to post to /users/James/course/1234.
Edit: the data is being successfully posted, but I am getting a message Cannot GET /users/James/course/1234.
I have the following code:
app.js:
app.get('/', (req, res) => {
res.render('form.hbs')
})
app.post('/saveUsers', [
// firstname must contain letters only
check('firstname', 'First name should contain only letters').isAlpha(),
// email must be in a email format
check('email', 'Email field must be in a email format').isEmail(),
// courseId must be numerical and exactly 4 digits
check('courseId', 'course ID should contain only numbers').isNumeric(),
check('courseId', 'course ID should be exactly 4 digits').isLength({ min: 4, max: 4 })
], (req, res) => {
const errors = validationResult(req)
if (!errors.isEmpty()) {
return res.status(400).json({ errors: errors.array(), data:req.body})
}
var db = utils.getDb()
db.collection('users').insertOne({
name: req.body.firstname,
email: req.body.email,
courseId: req.body.courseId
}, (err, result) => {
if (err) {
Response.send('Unable to insert a student')
}
console.log(result.ops)
nameUrl = result.ops[0].name
courseIdUrl = result.ops[0].courseId
res.redirect(`/users/${nameUrl}/course/${courseIdUrl}`)
})
})
app.post(`/users/${nameUrl}/course/${courseIdUrl}`, (req, res) => {
res.json(result.ops)
})
form.hbs:
<!DOCTYPE html>
<html>
<body>
<h1>Welcome</h1>
<p>Enter your name, email and course ID:</p>
<form action="/saveUsers" method="POST" id="myForm">
<input type="text" placeholder="firstname" name="firstname">
<input type="email" placeholder="email" name="email">
<input type="text" placeholder="courseId" name="courseId">
<input type="submit" value="Submit">
</form>
</body>
</html>
It looks like you are using template literal string interpolation where you should be using the Express syntax for route parameters. See the "route parameters" section in the docs.
Instead of:
app.post(`/users/${nameUrl}/course/${courseIdUrl}`, (req, res) => {
res.json(result.ops)
})
Try:
app.post(`/users/:name/course/:courseId`, (req, res) => {
res.json(result.ops)
})

Resources