Server: NodeJS, Express, Mongoose/MongoDB
Cloud provider: MongoDB
Client: React, mobx, axios
Tested with: body-parser, express.json()
Short question:
Controller requests data(string) - userId on certain URL from client, but req.body.userId is always undefined. I've checked every component and console logged every step and every react functional component prop - it's okay.
Everything else works fine, such as: find user on sign in/create on sign up, finding posts by id, adding comments to certain post using is id and so all.
Already looked up 10-15 questions about this issue, nothing helped. I need to know in which of these files I've been dumb. Thanks in advance!
Additional info:
Part of server/index.js file:
const app = express()
app.use(corsMiddleware)
app.use(express.json()) //also the body-parser returns the same result
app.use('/api/user', userRouter)
CORS middleware:
function cors(req, res, next) {
res.header("Access-Control-Allow-Origin", "*")
res.header("Access-Control-Allow-Methods", "GET, PUT, PATCH, POST, DELETE, OPTIONS")
res.header("Access-Control-Allow-Headers", "Content-Type, Authorization, Origin, X-Requested-With, Accept, X-Auth-Token")
if (req.method === "OPTIONS") {
res.status(200)
}
next()
}
User model:
const {Schema, model} = require('mongoose')
const User = new Schema({
email: {type: String, required: true, unique: true},
login: {type: String, required: true},
password: {type: String, required: true},
link: {type: String, required: true},
avatar: {type: String},
bio: {type: String},
status: {type: String, default: 'offline'},
friends: {type: Array, default: []},
communities: {type: Array, default: []},
requests: {type: Object, default: {
to: [],
from: []
}},
posts: {type: Array, default: []},
settings: {type: Array, default: []},
registrationDate: {type: String, default: Date.now()}
})
module.exports = model('User', User )
Post model:
const {Schema, model} = require('mongoose')
const moment = require('moment')
const Post = new Schema({
title: {type: String, required: true},
content: {type: String, required: true},
userId: {type: ObjectId, required: true, ref: 'User'}, //problem here
login: {type: String, ref: 'User'},
likes: {type: Array, default: []},
dislikes: {type: Array, default: []},
comments: {type: Array, default: []},
date: {type: String, default: () => {
return moment().format('D/MM/YYYY hh:mm')
}}
})
module.exports = model('Post', Post )
Part of userController that gets request:
async getUserById(req, res) {
try {
const {userId} = req.body //tried also const userId = req.body.userId
console.log(userId) //for debugging, output undefined
const user = await User.findById(userId)
res.status(200).json(user)
console.log(user) //for debugging, output null
} catch (e) {
console.log(e) //no errors
}
}
}
Part of user.routes:
router.get('/get', userController.getUserById)
//also deleted the authMiddleware for now, just to make things clean
Client components relations: Feed -> Post -> UserModel
Part of Feed.jsx:
const renderPosts = () => {
return(
posts.map((post) => {
return (
<Post
key={post._id}
username={post.login}
title={post.title}
text={post.content}
likes={post.likes}
dislikes={post.dislikes}
comments={post.comments}
date={post.date}
id={post._id}
userId={post.userId} //problem here
/>
)
})
)
}
Part of Post.jsx:
const [userId, setUserId] = useState('')
useEffect(() => {
console.log(`user id passed from Feed ${props.userId}`) //output OK
setUserId(props.userId)
console.log(`user id as prop in Post ${userId}`) //output OK
}, [props.userId]) //I know I don't need to put it to dependencies, just testing
return (
<div className={styles.Post}>
<div className={styles['post-user']}>
<UserModel userId={userId} username={props.username} link='#'/>
<p className={styles['post-user-title']}>{props.title}</p>
</div>
</div>
)
Part of UserModel.jsx:
export default function UserModel(
{
username,
link,
reverse,
mainUser,
onClick,
comment,
isFriend,
userId
}) {
const handleUsernameClick = async () => {
userPage.fetchUserData(userId)
console.log(`model user id ${userId}`) //output is OK
}
return (
<div className={styles['user-model']}
{//some unnecessary data here with button that calls handleUsernameClick}
</div>
)
}
Part of mobx file that holds fetchUserData method:
class UserPage {
constructor() {
makeAutoObservable(this)
}
//some methods here
fetchUserData = async(userId) => {
try {
const user = await axios.get('http://localhost:5000/api/user/get',
{userId},
{
headers:{Authorization: `Bearer ${localStorage.getItem('token')}`},
})
this.user = user
this.setUserPage(
user.login,
user._id,
user.link,
user.friends,
user.communities
)
console.log(user)
console.log(`user id in store ${userId}`) //output OK
} catch (e) {
console.log(e)
}
}
}
}
export default new UserPage()
to send data, you need to use POST method
change it throughout:
router.post('/get', userController.getUserById)
await axios.post('http://localhost:5000/api/user/get',
Related
I am making a social media backend.
I save post added by the used in a Post model and user data in a User model.
GITHUB_REPO_LINK_AT_END
NOTE: UserSchema have a Schema.TypesOf.ObjectId Reference To POST Model. User_Model_&_Post_Model_are_provided_in_the_end
To get all posts of a particular user, I make a GET request to the route "/post" with body:
{ "id" : "6399d54c00308a2fe0bdf9fc"} //sending user id to fetct all the ID of the post from USER model, so i can then query the POST model for the posts
This the function I am getting problem with:
const getPost = async(req, res)=>{
if(req.body.id){
try {
const user = await User.findById(req.body.id).select('-_id post');
//THIS IS THE PART I NEED HELP WITH-------------------------------------------
const posts = await user.post.map(async(postID) => {
const result = await Post.findById(postID).select('-_id title body');
//console.log(result) THIS PRINTS THE CORRECT OBJ FROM DB
return result; //THIS RETURNS AN EMPTY OBJECT HERE
});
//----------------------------------------------------------------------------
res.status(200).json(posts);
} catch (error) {
console.log(error);
res.status(500).json({message: error.message});
}
}
};
when sending a GET request it returns an empty array with empty objects.//PS: no. of empty obj = actual no. of obj in DB
//This is the response
[{},{},{},{},{},{},{},{},{},{},{}]
{
//This is the user object
"_id": "6399d54c00308a2fe0bdf9fc",
"createdAt": "2022-12-14T13:52:40.483Z",
"name": "ShivamUttam",
"username": "Fadedrifleman",
"post": [
"6399d57200308a2fe0bdfa00",
"6399d5c400308a2fe0bdfa06",
"6399d5ca00308a2fe0bdfa0a",
"6399d5d600308a2fe0bdfa0e",
"6399de29e8aa8697299941c5",
"6399dec6e9b79ac66c59cd7a",
"6399df0dbea937f8b3365979",
"6399df31bea937f8b336597d",
"6399df31bea937f8b3365981",
"6399df32bea937f8b3365985",
"6399df33bea937f8b3365989"
],
"__v": 5
}
Model for USER and POST:
User:
const mongoose = require('mongoose');
const userSchema = new mongoose.Schema({
createdAt: {
type: Date,
default: Date.now()
},
name: {
type: String,
required: [true, 'name must be provided'],
},
username : {
type: String,
required: [true, 'Username must be provided'],
},
post:[{
type: mongoose.Schema.Types.ObjectId,
ref: 'Post',
}],
});
module.exports = mongoose.model('User', userSchema)
Post:
const mongoose = require('mongoose')
const postSchema = new mongoose.Schema({
createdAt: {
type: Date,
default: Date.now()
},
title:{
type: String,
required: [true, "title cannot be empty"],
max: [20, "title cannot exceed 20 character"]
},
body: {
type: String,
max: [145, "body cannot exceed 145 character"],
},
tags:{
type: String,
},
});
module.exports = mongoose.model('Post', postSchema);
https://github.com/Fadedrifleman/socialMediaAppBackend/tree/master
Since you have used async callback function in the map method, a async function always return a promise, whatever the entity is returned by the function is wrapped inside a promise and that promise is returned.
If you want to use map function with async js code, you can try the following
const posts = await Promise.all(user.post.map(async(id)=>{
const result = await Post.findById(postID).select('-_id title body');
return result;
}));
and if you want to straightaway send the posts, you can also use .lean() method on posts, as in
await Post.findById(postID).select('-_id title body').lean()
You had some bugs that probably would interfere, I did a pull request to fix them: https://github.com/Fadedrifleman/socialMediaAppBackend/pull/1
But the main part would be this:
const getPost = async (req, res) => {
try {
if (req.body.id) {
const user = await User.findById(req.body.id);
await user.populate("post");
res.status(200).json(user.post);
return;
}
const posts = await Post.find({ access: 'public' }).select('-access');
res.status(200).json(posts);
} catch (error) {
res.status(500).json({ message: error.message });
}
};
I'm making a post API on a dummy node project; it worked fine there, but implemented the same API on the main project I got the 400 Bad request error while running this API on a post.
Route js
router.post("/add",(req, res) => {
const planName = req.body.planName;
const planPrice = req.body.planPrice;
const planMode = req.body.planMode;
const planStatus = req.body.planStatus;
const newAddon = new Addon({
planName,
planPrice,
planMode,
planStatus,
});
console.log("newAddon", newAddon)
newAddon.save()
.then(() => res.json("Addon added"))
.catch(err =>
res.status(400).json("Error in Add Addon route")
);
});
Schema
const mongoose = require("mongoose");
const Schema = mongoose.Schema;
const addonsSchema = new Schema(
{
planId: {type: Number,required: true},
planName: {type: String,required: true},
planPrice: {type: Number,required: true},
planMode: {type: String, required:true},
planStatus: {type: String, required:true}, //active or inactive
},
{timestamps: true},
);
const Addon = mongoose.model("Addons", addonsSchema);
module.exports = Addon
index.js
const addonRouter = require('./routes/addon')
app.use('/addons',addonRouter)
Postman Screenshot
You'll need to provide planId as well in the payload, since you've defined required as true.
You've 2 ways to solve this:
Set the value of planId equal to _id field that mongodb creates automatically before inserting. You can do this with a preSave hook on the model.
addonsSchema.pre("save", (next) => {
// only triggered when creating new records
if (this.isNew) {
this.planId = this._id;
}
next();
});
And your type for planId should be ObjectId.
planId: {type: ObjectId,required: true},
Won't recommend but you can remove required attribute from planId.
I have a REST API built with Node JS and I'm currently using MongoDB as my database. I want to prevent the users from deleting another user's products and for this I checked if the userId from the decoded token is the same as the product userId.
Product schema
const mongoose = require("mongoose");
const productSchema = mongoose.Schema(
{
_id: mongoose.Schema.Types.ObjectId,
userId: mongoose.Schema.Types.ObjectId,
name: { type: String, required: true },
price: { type: Number, required: true },
productImage: { type: String, required: false },
category: {
type: mongoose.Schema.Types.ObjectId,
ref: "Category",
required: true
},
gender: { type: String, required: true }
},
{ timestamps: { createdAt: "created_at" } }
);
module.exports = mongoose.model("Product", productSchema);
The delete product method:
const id = req.params.productId;
Product.findById({ _id: id }).then((product) => {
if (product.userId != req.user._id) {
return res.status(401).json("Not authorized");
} else {
Product.deleteOne({ _id: id })
.exec()
.then(() => {
return res.status(200).json({
message: "Product deleted succesfully",
});
})
.catch((err) => {
console.log(err);
return res.status(500).json({
error: err,
});
});
}
});
};
As you guys see first I'm searching executing the findByID method to access the userId property of the product, then I'm comparing the userId from the response with the userId from the decoded token.
I don't think my method is very efficient since it's running both findById and deleteOne methods.
Can you help me with finding a better solution for this?
as Guy Incognito mentioned, what you are trying to do is an OK thing and you may want to keep it this way in case you want to send a 404 status stating the product they are trying to remove does not exist.
however, if you are trying to do it with only one request
Product.deleteOne({ _id: id, userId: req.user._id })
hope it helps!
I have an API running on nodejs with mongodb, and I am trying to insert data from the reactjs frontend.
This data comes from the user's Token and the id of a book that the user is interested in
Model:
let BookInterested = new Schema({
book: {
type: Schema.Types.ObjectId,
ref: "Book",
required: true
},
user: { type: Schema.Types.ObjectId, ref: "User", required: true },
timestamp: { type: Date, default: Date.now, required: true },
completed: { type: Boolean, default: false }
});
module.exports = mongoose.model("BookInterested", BookInterested);
The user and the book are coming from other models with are Book and User.
I have a Route with a simple post method:
Route:
router.post(
"/book/:bookId/interesteds",
auth.authenticate(),
(req, res) => {
meService.createInterested(req, res);
}
);
The Service has all the logic behind the route.
meService:
createInterested: function(req, res) {
let interested = {
user: req.user.id, //User Id from token
book: req.params.bookId, //book Id from route
};
let record = new BookInterested(interested);
record.save(err => {
if (err) return helper.error(res, "Error while creating " + err);
if (!record) return helper.error(res, "Record not found");
return res.json({ success: true });
});
},
In my frontend with is in reactjs I have the functions to fetch the books and the users
componentWillMount() {
this.retrieveBooks(this.props.params.id);
this.retrieveUser();
}
with is working just fine, my problem now is that I need a button where, when clicked, the user demonstrates that he or she has an interest in the book, like so:
<Button
onClick={() => this.setState({ open: !open, disabled: true })}
className="btn btn-big btn-tenho-interesse"
aria-expanded={open}
disabled={this.state.disabled}
>
Im Interested
</Button>
I create the handleclick function, but it is not working
handleclick:
handleClick = (req, res) => {
console.log(req);
Request.post(
`/me/books/${this.props.params.id}/interesteds`,
req,
res,
res => {
if (res.success) {
Alert.success(
"success"
);
} else
Alert.error(
"error"
);
}
);
};
I'm getting all sorts of errors, like "TypeError: Converting circular structure to JSON" and "res.json is not a function" and many others.
Any idea how I can do this?
Thanks.
I am creating a RESTful API using Node.js and mongoose by following the tutorial by Acedemind. I have got it working just fine and am now expanding it to allow the client to post several products in the same order. Basically I am modifying a simple "POST" request to be an array instead of some variables. My problem is that I run into a long validation error that hinders the array from being created. Here is the code for the post request:
router.post("/", async (req, res, next) => {
const productsMaybeFalses = await Promise.all(req.body.products.map(async ({ productId })=> {
const product = await Product.findById(productId);
if (!product) {
return false;
}
return {
...product,
productId,
}
}));
const errors = productsMaybeFalses
.map((productMaybeFalse, index) => {
return {
productMaybeFalse, index
}
})
.filter(({ productMaybeFalse }) => !productMaybeFalse)
if (errors.length) {
console.log(error);
return;
}
console.log(productsMaybeFalses);
const products = productsMaybeFalses
.filter((productMaybeFalse) => productMaybeFalse);
const order = new Order({
_id: mongoose.Types.ObjectId(),
products: products
});
return order.save().then(results => {
console.log(results);
res.status(201).json(results.map((result) => ({
message: "order stored",
createdOrder: {
_id: result._id
},
request: {
type: "GET",
url: "http://localhost:3000/orders/" + result._id
}
})));
})
.catch(err => {
console.log(err);
res.status(500).json({
error: err
})
})
})
And here is the Schema for the Order:
const mongoose = require("mongoose");
const pSchema = mongoose.Schema({
productId: { type: mongoose.Schema.Types.ObjectId, ref: "Product", required: true},
quantity: { type: Number, default: 1}
});
const orderSchema = mongoose.Schema({
_id: mongoose.Schema.Types.ObjectId,
products: [pSchema]
});
module.exports = mongoose.model("Order", orderSchema)
To keep this question from being too long I will only post the end part of the error. The part that I feel tells the most information abut the problem. If anyone wants the whole error message to better understand the problem and maybe come up with a solution for me I will be very willing to post it as well. Here it is:
kind: 'Array',
value: [Array],
path: 'products',
reason: TypeError: value[i].toObject is not a function
at DocumentArray.cast (/Users/axelhagman/Documents/Jacobs/node_modules/mongoose/lib/schema/documentarray.js:309:27)
at DocumentArray.SchemaType.applySetters (/Users/axelhagman/Documents/Jacobs/node_modules/mongoose/lib/schematype.js:755:12)
at model.$set (/Users/axelhagman/Documents/Jacobs/node_modules/mongoose/lib/document.js:922:18)
at model._handleIndex (/Users/axelhagman/Documents/Jacobs/node_modules/mongoose/lib/document.js:740:14)
at model.$set (/Users/axelhagman/Documents/Jacobs/node_modules/mongoose/lib/document.js:697:22)
at model.Document (/Users/axelhagman/Documents/Jacobs/node_modules/mongoose/lib/document.js:114:12)
at model.Model (/Users/axelhagman/Documents/Jacobs/node_modules/mongoose/lib/model.js:73:12)
at new model (/Users/axelhagman/Documents/Jacobs/node_modules/mongoose/lib/model.js:4324:13)
at router.post (/Users/axelhagman/Documents/Jacobs/api/routes/orders.js:70:17)
at <anonymous>
at process._tickCallback (internal/process/next_tick.js:189:7) } },
_message: 'Order validation failed',
name: 'ValidationError' }
POST /orders/ 500 440.085 ms - 7622
I am very new to using node.js and creating API overall so any help would be very much appreciated. Thanks!