Node.js RESTful API validation error on array schema - javascript

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!

Related

How to sync data with MongoDb and know the status while a document is being created/updated

I'm developing a MERN chat app and trying to replicate the feature which WhatsApp provides -> message sent/seen status, so for that I need to check whether the message I/user sent is successfully synced/updated/created in my MongoDB, I know similar functionality can be achieved in AWS Amplify using the event outboxMutationEnqueued which says Dispatched when a local change has been newly staged for synchronization with the cloud, so it works like whenever we are trying to push something to synchronize with the cloud, this event is going to be fired, and once it is finished, outboxMutationProcessed is going to be triggered which says Dispatched when a local change has finished syncrhonization with the cloud and is updated locally.
So we can listen to these events whenever we are trying to send a message, and once our message mutation is processed we are going to receive outboxMutationProcessed, and then we can update the status of the message to sent or single tick or delivered.
import Amplify, {Hub} from 'aws-amplify';
useEffect(() => {
const listener = Hub.listen('datastore', async (hubData) => {
const {event, data} = hubData.payload;
if (event === 'networkStatus') {
console.log('User has a network connection: ', data.active);
}
if (event === 'outboxMutationProcessed') {
if (data.model === Message)
console.log('Mutation has been synced with the cloud: ', data);
// set the message status to delivered.
}
})
return () => listener();
}, []);
So, the question, do we have something similar in MongoDB? I'm currently using React Native, Node, Express, Sockets, Mongoose, MongoDB.
Currently, my API end point and collections (for creating a new message and saving in to db):
I have 3 collections: users, 'messages', 'chatRooms'.
const mongoose = require('mongoose');
const MessageSchema = mongoose.Schema({
roomId: String,
senderId: String,
text: String,
status: String, // INQUEUE, SENT, DELIVERED, READ
}, {
timestamps: true,
});
module.exports = mongoose.model('Message', MessageSchema);
router.post('/create_message', checkAuth, async (req, res) => {
const {chatRoomId, senderId, text} = req.body;
try {
const message = new Message({
chatRoomId,
senderId,
text,
});
const result = await message.save();
return res.status(200).json({
type: 'success',
data: result,
});
} catch (error) {
return res.status(422).send({error: `${error.message}`});
}
});
// SOCKET IMPLEMENTATION FOR REALTIME FEATURE
socket.on('listener', async data => {
io.to(id).emit('new_message', data);
const message = new Message({
chatRoomId: data.roomId,
senderId: data.senderId,
text: data.text,
status: data.status, // data.status = 'INQUEUE'
});
await message.save();
// maybe something here...? not sure
// data.status = 'SENT' after successful creation of document.
});
Maybe an event which we can fire, during the await message.save(...something here...), and if it is successfully saved in our DB, we can send it to our frontend or using socket?
If anyone could provide an example, it would be really helpful!
Edit: I changed this up a bit.
schemas:
const UserSchema = new mongoose.Schema({
name: String,
online: Boolean,
isActive: Boolean,
})
const RoomSchema = new mongoose.Schema({
name: String,
members: [{ type: mongoose.Schema.Types.ObjectId, ref: 'users' }],
})
const MessageSchema = new mongoose.Schema({
roomId: { type: mongoose.Schema.Types.ObjectId, ref: 'rooms' },
senderId: { type: mongoose.Schema.Types.ObjectId, ref: 'users' },
text: String,
deliveredTo: [{ user: { type: mongoose.Schema.Types.ObjectId, ref: 'users' }, timestamp: Date }],
readBy: [{ user: { type: mongoose.Schema.Types.ObjectId, ref: 'users' }, timestamp: Date }],
})
app.js
Mongo.connect(uri, (err, client) => {
mongoose.connect(uri)
socketClient.on('connection', socket => {
//
//on load all users for login select
User.find().then(users => socket.emit('users', users))
....
//
//setting pipeline for readStream to only watch for changes in the messages collection
//where the roomId field is the current room the user has loaded
//setting fulldocument : 'updateLookup' option will always return the fulldocuemnt with any updates
//without this, the fulldocument will only be returned on our insert change
const pipeline = [{ $match: { 'fullDocument.roomId': ObjectId(room.id) } }]
const collection = client.db('chat').collection('messages')
const messageStream = collection.watch(pipeline, { fullDocument: 'updateLookup' })
messageStream.on('change', async function (change) {
const updates = change.updateDescription?.updatedFields
//
//if a new document is created in messages, then push that message to the client
if (change.operationType.match(/insert/i)) {
const doc = await Message.findById(change.fullDocument._id).populate(
'deliveredTo.user readBy.user'
)
const status = await getStatus(doc, room.id)
socket.emit('push message', { doc, status: status })
//
//if delivered to has been updated on a message then send to client
} else if (updates?.deliveredTo) {
const status = await getStatus(change.fullDocument, room.id)
socket.emit('status change', {
id: change.fullDocument._id,
status: status,
user: change.fullDocument.senderId,
})
} else if (updates?.readBy) {
//
//if readby has been updated on a message then send to client
const status = await getStatus(change.fullDocument, room.id)
socket.emit('status change', {
id: change.fullDocument._id,
status: status,
user: change.fullDocument.senderId,
})
}
})
...
})
})

Is there a way to use callback functions in joi validation?

Their Guru's
I'm doin a research on a code I saw on the internet, but if I see the syntax, they have used Hapi/Joi which is depricated.
My question is how can I use this syntax in Joi?
app.post('/test', (req, res, next) => {
const id = Math.ceil(Math.random() * 9999999);
Joi.validate(data, schema, (err, value) => {
if (err) {
res.status(400).json({
status: 'error',
message: 'Invalid request data',
data: data
});
} else {
res.status(200).json({
status: 'success',
message: 'User created successfully',
data: Object.assign({id}, value)
});
}
});
});
By inspecting the types (index.ds.ts) file in Github indeed there is no callback style validate function, only those two methods:
/**
* Validates a value using the schema and options.
*/
validate(value: any, options?: ValidationOptions): ValidationResult;
/**
* Validates a value using the schema and options.
*/
validateAsync(value: any, options?: AsyncValidationOptions): Promise<any>;
However you can use util.callbackify - callbackify examples to transform validateAsync into a callback node style if you really want to:
const Joi = require('joi');
const { callbackify } = require('util');
// A very simple data object
const obj = { a: 23 };
// A very simple joi schema which checks if schema is an object with the `a` property being a string
const schema = Joi.object({
a: Joi.string(),
});
// Callback style (which i don't recommend since its not available in `joi` package)
callbackify(() => schema.validateAsync(obj))((err, ret) => {
if (err) {
return res.status(400).json({
status: 'error',
message: 'Invalid request data',
data: data,
});
}
res.status(200).json({
status: 'success',
message: 'User created successfully',
data: Object.assign({ id }, value),
});
});
I would use what Joi provide, as in the following 2 examples:
// using async/await - you need to mark you controller as async, like:
// app.post('/test', async (req, res, next) => ...
try {
await schema.validateAsync(obj);
res.status(200).json({
status: 'success',
message: 'User created successfully',
data: Object.assign({ id }, value),
});
} catch (err) {
res.status(400).json({
status: 'error',
message: 'Invalid request data',
data: data,
});
}
Or even with just promises:
schema
.validateAsync(obj)
.then(() =>
res.status(200).json({
status: 'success',
message: 'User created successfully',
data: Object.assign({ id }, value),
})
)
.catch(() =>
res.status(400).json({
status: 'error',
message: 'Invalid request data',
data: data,
})
);

TypeError: Cannot use 'in' operator to search for 'userId'

So i was implementing a users model in my mvc and then i get a weird error saying
MongoClient constructor.
D:\node\node_modules\mongoose\lib\document.js:2022
if (path in this.$__.selected) {
^
TypeError: Cannot use 'in' operator to search for 'email' in saifkhan501721#gmail.com
at model.isSelected (D:\node\node_modules\←[4mmongoose←[24m\lib\document.js:2022:14)
at D:\node\node_modules\←[4mmongoose←[24m\lib\document.js:2195:14
at Array.filter (<anonymous>)
at _getPathsToValidate (D:\node\node_modules\←[4mmongoose←[24m\lib\document.js:2194:71)
at model.Document.$__validate (D:\node\node_modules\←[4mmongoose←[24m\lib\document.js:2365:23)
at D:\node\node_modules\←[4mkareem←[24m\index.js:369:33
←[90m at processTicksAndRejections (internal/process/task_queues.js:79:11)←[39m
i have no idea as to what is the reason behind the error is, is it a syntax error , logical error connection error or mispelling of a variable,well anyway here's my app.js
mongoose
.connect('mongodb+srv://turd_waffle:SaifKhan#cluster0.lltqs.mongodb.net/shop?retryWrites=true&w=majority')
.then((result) => {
User.findOne().then(user=>{
if(!user){
const user=new User({
name:'Saif',
email:'saifkhan501721#gmail.com',
cart:{
items:[]
}
})
user.save()
}
})//save() saves the documents(mostly used as a reference to generate a sample id in order to start a cluster working)
app.listen(3000)
})
.catch(err => {
console.log(err)
})
here's my user.js model to store users data in mongodb database
const mongoose = require('mongoose')
const Schema = mongoose.Schema;
const userSchema = new Schema({
name: {
type: String,
required: true
},
email: {
type: String,
required: true
},
cart: {
items: [{
productId: {
type: Schema.Types.ObjectId,
ref: 'Product',
required: true
},
quantity: {
type: Number,
required: true
}
}]
},
})
userSchema.methods.addToCart = function(product) {
const cartProductIndex = this.cart.items.findIndex(cp => {
return cp.productId.toString() === product._id.toString();
})
let newQuantity = 1;
const updatedCartItems = [...this.cart.items];
if (cartProductIndex >= 0) {
newQuantity = this.cart.items[cartProductIndex].quantity + 1;
updatedCartItems[cartProductIndex].quantity = newQuantity
} else {
updatedCartItems.push({
productId: product._id,
quantity: newQuantity
})
}
const updatedCart = {
items: updatedCartItems
}
this.cart=updatedCart
return this.save()
}
module.exports = mongoose.model('User', userSchema)
can anyone please be kind enough to atleast tell me what the error above is trying to say i used app.use to create a user so i can store his id, email and name
app.use((req, res, next) => {
User.findById('5f788c080ba19e0f8c642202')
.then(user => {
req.user = new User(user.name, user.email, user.cart, user._id);
next();
})
.catch(err => console.log(err));
});
Strange issue. From the code you provided, the issue should not arise.
When I look at the code in mongoose, the only way that this could happen is if you would do something like:
new User("saifkhan501721#gmail.com")
Then this.$__.selected would be a string instead of an object (e.g. {email: "saifkhan501721#gmail.com"}) and path in this.$__.selected would cause your received type error.
Not knowing enough about your app, I would assume that there maybe is a bad User object created somewhere else / cached / or already in database. Maybe it would help to verify this using a clean database?
See the source code for reference. When I take a look at the code it seems like an oversight that it is not checked if this.$__.selected is a string, because in this case it does not fail early (e.g. Object.keys(this.$__.selected) will not cause an error).

How do I proper create Array Schema in Mongoose and filter by given criteria?

I'm quite newb to mongodb and mongoose, so I ask you to help me.
I've an API, which works and now I want to extend it with filtering for given params.
I've a Order model, which points to two different collections of documents Material & User Schemas and have a quantity element.
let Order = new Schema({
materials:
{
type: Array,
material: {
type: mongoose.Schema.Types.ObjectId,
ref: 'Material'
},
qty: {
type: Number
}
},
userId: {
type: Schema.Types.ObjectId,
ref: 'User'
}
}, {
collection: 'orders'
})
Also I've method to create an order:
exports.createOrder = (req, res) => {
if (!req.body.user) {
res.status(400).send({message: 'Content can not be empty!'});
}
const order = new Order({
materials: req.body.materials,
userId: req.body.user
});
order
.save(order)
.then(data => {
res.send(data);
})
.catch(err => {
res.status(500).send({
message:
err.message || "Some error occurred while creating the Order."
});
});
}
If I create Order filling only Material ID, it creates and filtering by given material ID in filter request.
post request
filter request
But If I trying to point qty it isn't present in response.
post request with qty
filter request ending with previous document id
There is my question: How can I create Order exact way I need (Material ID and qty number must persist) and How can I perform a filtering operations on them?
Any help appriciated.
My mistake was in method how I create order as well as I make a filtering request.
Correct method to create order with data storing in array type is following
exports.createOrder = (req, res) => {
if (!req.body.user) {
res.status(400).send({message: 'Content can not be empty!'});
}
const order = new Order({
materials: {material: req.body.materials, qty: req.body.qty},
userId: req.body.user
});
order
.save(order)
.then(data => {
res.send(data);
})
.catch(err => {
res.status(500).send({
message:
err.message || "Some error occurred while creating the Order."
});
});
}
as you can see, difference is how I form materials array.
next thing is in filter request
exports.filterOrder = (req, res) => {
Order.find({"materials.material": req.body.material})
.then(data => {
console.log(data);
res.send(data);
})
.catch(err => {
res.status(500).send({
message:
err.message || "Some error occurred while retrieving Orders."
});
});
}
If I need to filter orders contain necessary material I need to place subelement of array in quotes with dot notation. This will work also with "material.qty" parameter if needed.

Problem inserting data into a nodejs and mongodb backend with reactjs frontend

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.

Categories