Create a new mongoose object with optional parameters from a request - node.js

I have a fairly long and complicated nested objected with optional objects inside (see below). I have a request coming in that could have some fields/objects missing - how can I handle that? Can I just use the "?" operand - I have tried but can't seem to get it working. Is there another method I could try?
const product = new Product({
prodID: req.body.prodID,
prodComponents: {
textComponent :{
item: req.body.prodComponents.textComponent.item ? req.body.prodComponents.textComponent.item : null
}
imageComponent:{
item: req.body.prodComponents.imageComponent.item ? req.body.prodComponents.imageComponent.item : null
}
}
)};

There are plenty of ways to resolve your problem. The variable: req.body is just another object as almost everything in javascript.
I'd recommend taking that object and parse it into variables or other object that your program or data model will handle with no exceptions of access into empty or undefined fields.
function createProductDtoToProduct(data) {
const model = {
prodID: data.prodID,
prodComponents: {
textComponent: {
item: null
},
imageComponent: {
item: null
}
}
};
if (data.prodComponents && data.prodComponents.textComponentItem)
model.prodComponents.textComponent.item = {
id: data.textComponentItem.id,
title: prodComponents.textComponentItem.title || "defaultTitle",
}
if (data.prodComponents && data.prodComponents.imageComponentItem)
model.prodComponents.imageComponent.item = {
id: data.imageComponentItem.id,
title: prodComponents.imageComponentItem.title || "defaultTitle",
}
return model;
}
router.post("/createProduct", async (req, res) => {
const product = new Product(createProductDtoToProduct(req.body));
await db.save(product);
return "OK";
});
Also you can validate the body of the request with many tools such as:
https://express-validator.github.io/docs/

Related

Check if the body parameter is not null and update on MongoDB

I'm trying to update a document in MongoDB using NodeJS (NextJS). My current code is:
import connect from "../../util/mongodb";
async function api(req, res) {
if (req.method === "POST") {
const { id } = req.body;
const { name } = req.body;
const { email} = req.body;
const { anything1 } = req.body;
const { anything2 } = req.body;
if (!id) {
res.status(400).json({ "error": "missing id param" });
return;
}
const { db } = await connect();
const update = await db.collection("records_collection").findOneAndUpdate(
{ id },
{
$set: {
name,
email,
anything1,
anything2
}
},
{ returnOriginal: false }
);
res.status(200).json(update);
} else {
res.status(400).json({ "error": "wrong request method" });
}
}
export default api;
Everything is working. But I would like to request only the ID as mandatory, and for the other information, leave optional.
In this code, passing the id and name for example, the other three fields (email, anything1 and anything2) will be null in the document.
It is possible to implement the update without requiring all document information and ignore when body fields are null? (As a beginner in NodeJS and MongoDB, the only way to do that that comes to my head now is to surround it all by a lot of if...)
If I've understood your question correctly you can achieve your requirement using the body object in $set stage.
If there is a field which not exists in the object, mongo will not update that field.
As an example check this example where only name field is updated and the rest of fields are not set null.
Another example with 2 fields updated and 3 fields.
You can see how only is updated the field into object in the $set.
So you only need to pass the object received into the query. Something like this:
const update = await db.collection("records_collection").findOneAndUpdate(
{ id },
{
$set: req.body
},
{ returnOriginal: false }
);

How to use MongoDB $ne on nested object property

I have a node API which connects to a mongoDB through mongoose. I am creating an advanced results middleware that enabled selecting, filtering, sorting, pagination etc. based on a Brad Traversy course Node.js API Masterclass With Express & MongoDB. This is all good.
I am adapting the code from the course to be able to use the $ne (not equal) operator and I want to be able to get a model that is not equal to a nested property (user id) of the model. I am using this for an explore feature to see a list of things, but I don't want to show the user their own things. I am having trouble figuring out how to access the id property.
********************* UPDATE *********************
It seems all the documentation I've read recommends writing const injected like this:
const injected = {
'user._id': { "$ne": req.user.id }
};
but for some reason it is not working. I can query top level properties that are just a plain string value like this:
const injected = {
access: { "$ne": "public" }
};
but not a property on an object. Does anyone know why? Is it because the property I want to query is an id? I've also tried:
const injected = {
'user._id': { "$ne": mongoose.Types.ObjectId(req.user.id) }
};
which also does not work...
So the model looks like this:
{
name: 'Awesome post',
access: 'public',
user: {
_id: '2425635463456241345', // property I want to access
}
}
then the actual advanced results middleware looks like this and it's the 'injected' object where I am trying to access id. In the course brad uses this syntax to use lte (/?averageCost[lte]=10000) but I do not get any results with my ne. Can anyone help me here?
const advancedResults = (model, populate) => async (req, res, next) => {
let query;
const injected = {
access: 'public',
'user._id[ne]': req.user.id, // I don't think user._id[ne] is correct
};
}
// Copy req.query
const reqQuery = { ...req.query, ...injected };
console.log('injected: ', injected);
// Fields to exclude
const removeFields = ['select', 'sort', 'page', 'limit'];
// Loop over removeFields and delete them from reqQuery
removeFields.forEach(param => delete reqQuery[param]);
// Create query string
let queryStr = JSON.stringify(reqQuery);
// Create operators ($gt, $gte, etc)
queryStr = queryStr.replace(/\b(gt|gte|lt|lte|in|ne)\b/g, match => `$${match}`);
// Finding resource and remove version
query = model.find(JSON.parse(queryStr)).select('-__v');
// Select Fields
if (req.query.select) {
const fields = req.query.select.split(',').join(' ');
query = query.select(fields);
}
// Sort
if (req.query.sort) {
const sortBy = req.query.sort.split(',').join(' ');
query = query.sort(sortBy);
} else {
query = query.sort('-createdAt');
}
// Pagination
const page = parseInt(req.query.page, 10) || 1;
const limit = parseInt(req.query.limit, 10) || 25;
const startIndex = (page - 1) * limit;
const endIndex = page * limit;
const total = await model.countDocuments(JSON.parse(queryStr));
query = query.skip(startIndex).limit(limit);
if (populate) {
query = query.populate(populate);
}
// Executing query
const results = await query;
// Pagination result
const pagination = {};
if (endIndex < total) {
pagination.next = {
page: page + 1,
limit,
};
}
if (startIndex > 0) {
pagination.prev = {
page: page - 1,
limit,
};
}
res.advancedResults = {
success: true,
count: results.length,
pagination,
data: results,
};
next();
};
module.exports = advancedResults;
Answering your question about how to use $ne:
The use of $ne is as follows:
"field":{
"$ne": yourValue
}
Into your query should be like:
"user._id": {
"$ne": req.user.id
}
Example here
$ne operator will return all document where the field value don't match with the given value.
As you have done, to acces the nested field is necessary use the dot notation.
Also, to ensure it works, if your schema defines _id as ObjectId maybe is necessary parse req.user.id to ObjectId.
But if in your schema is a string then should works.
So try (not tested at all):
const injected = {
'user._id': { "$ne": req.user.id }
};

mongoose filter by multiple conditions and execute to update data

I am wondering what would be the best approach to make schema functions using mongoose. I have never used this so the way I think is somewhat limited, same goes for looking for docs, without knowing what's available, is not very efficient.
Through docs I found that either using findOneAndUpdate might solve the problem; but there are some constraints.
Here is the code I am planning to run:
models/Bookmark.js
const mongoose = require('mongoose')
const bookmarkItemSchema = new mongoose.Schema({
restaurantId: String,
cachedAttr: {
name: String,
latitude: Number,
longitude: Number,
},
})
const bookmarkListSchema = new mongoose.Schema({
listName: String,
items: [bookmarkItemSchema],
})
const bookmarkSchema = new mongoose.Schema({
userId: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User',
},
lists: [bookmarkListSchema],
})
// const add = (lists, userId) => {
// let bookmark = Bookmark.findOne({userId})
// bookmark.lists.listName === lists.listName //current, new
// ? bookmark.lists.items.push(lists.items)
// : bookmark.lists.push(lists)
// return bookmark
// }
mongoose.model('Bookmark', bookmarkSchema)
Routes/bookmark.js
router.post('/bookmarks', async (req, res) => {
const {lists} = req.body
console.log(lists)
if (!lists) {
return res.status(422).send({error: 'You must provide lists'})
}
let bookmark = Bookmark.findOne({"userId": req.user._id})
if (bookmark.lists.listName === lists.listName){
let item = lists.items
bookmark.lists.items.push(item)
await bookmark.save()
res.send(bookmark)
}
try {
// const bookmark = Bookmark.add(lists, req.user._id, obj)
// await bookmark.save()
// res.send(bookmark)
let bookmark = Bookmark.findOne({"userId": req.user._id})
if (bookmark.lists.listName === lists.listName){ // THIS IS UNDEFINED. How to get this object?
let item = lists.items
bookmark.lists.items.push(item)
await bookmark.save()
res.send(bookmark)
}
} catch (e) {
res.status(422).send({error: e.message})
}
})
The req.body looks like this:
{
"lists": {
"listName": "My Saved List",
"items": {
"restaurantId": "abcdefg",
"cachedAttr": {
"name": "abcdefg",
"latitude": 200,
"longitude": 200
}
}
}
}
Basically what I commented out in the models/Bookmark.js file is what I would really like to do.
If the userId's list name already exists, then I would like to just add an item to the list.
Otherwise, I would like to add a new list to the object.
What is the best approach for doing this? Is there a straight forward mongoose api that I could use for this problem? or do I need to make two separated function that would handle each case and make that as schema methods and handle it in the routes file?

Gatsby: Inject a field in a graphql query before executing it within a plugin

So my use case seems very simple yet I'm struggling to figure out how can I do it.
In essence, I want to develop a gatsby-plugin that modifies all contentful-related graphQL queries to always insert the contentful_id so the returned data always contains that field. This way consumers of my plugin won't have to add contenful_id field in all of their grapqhQL queries.
Is this even doable? I'm not interested in creating fields as I believe they won't be part of the returned data unless you add that field explicitly.
The way to do it:
Use graphql SDK to visit the nodes. Define a visitor like this:
const { print, visit, parse } = require('graphql');
const visitor = {
SelectionSet(node, key, parent) {
if (!isQuery(parent) && !isFragment(parent)) {
node.selections.push({
kind: 'Field',
name: { kind: 'Name', value: 'yourFieldName' },
});
}
},
};
function isQuery(node) {
return node.kind === 'OperationDefinition' && node.operation === 'query';
}
function isFragment(node) {
return node.kind === 'FragmentDefinition';
}
Then you visit and print it back to a string:
const result = visit(parse(queryAST), { enter: visitor });
return print(result);
Last step is to add the desired field to all nodes (otherwise the field will not exist and your tampered queries won't work). You can achieve this attaching to this event:
exports.setFieldsOnGraphQLNodeType = () => {
return {
yourFieldName: {
type: GraphQLString,
resolve: (source) => {
return source.contentful_id || '';
},
},
};
};

express-validator to validate parameter which is an array

I am using express-validator to validate POST data in my express application. I have a form which has a select where in user can select multiple options:
<select name="category" multiple id="category">
<option value="1">category 1 </option>
.......
</select>
The payload after submitting form shows me this if I select multiple values:
...&category=1&category=2&....
Now, in my Express application I try to validate it like this:
req.checkBody('category', 'category cannot be empty').notEmpty();
But, even after I send multiple values I always get the error - category cannot be empty. If I print my variable as req.body.category[0] - I get the data. But, somehow not able to understand the way I need to pass this to my validator.
You may need to create your own custom validator;
expressValidator = require('express-validator');
validator = require('validator');
app.use(expressValidator({
customValidators: {
isArray: function(value) {
return Array.isArray(value);
},
notEmpty: function(array) {
return array.length > 0;
}
gte: function(param, num) {
return param >= num;
}
}
}));
req.checkBody('category', 'category cannot be empty').isArray().notEmpty();
Try this:
router.post('/your-url',
[
check('category').custom((options, { req, location, path }) => {
if (typeof category === 'object' && category && Array.isArray(category) && category.length) {
return true;
} else {
return false;
}
})
],
controller.fn);
Answering a bit late but hope this might help somebody.
If you are using express-validator, You can pass minimum / maximum length option to check whether Array is empty or exceeds a certain limit
req.checkBody('category', 'category cannot be empty').isArray({ min: 1, max: 10 });
If you are using schema, you can do it this way:
const schema = {
someArray: { isArray: true, notEmpty: true }
};
This maybe a bit late but for others who wants a clean solution without using the customValidator, I created a validator that can dig up to the deepest array and validate its content.
https://github.com/edgracilla/wallter
In your case validation would be:
const halter = require('wallter').halter
const Builder = require('wallter').builder // validation schema builder
const builder = new Builder()
server.use(halter())
server.post('/test', function (req, res, next) {
let validationSchema = builder.fresh()
.addRule('category.*', 'required')
.build()
// validationSchema output:
// { 'category.*': { required: { msg: 'Value for field \'category.*\' is required' } } }
req.halt(validationSchema).then(result => {
if (result.length) {
res.send(400, result)
} else {
res.send(200)
}
return next()
})
})
Error message can be override in builder initialization. Check the repo README.md for an in depth usage.

Resources