omit empty strings fields mongoose - mongodb

I have the following schema:
const mySchema = new mongoose.Schema({
x: String,
y: String
})
when a user from the front-end requests in his body:
req.body = {
'x' : '',
'y': ''
}
this results in creating a field in MongoDB, but with an empty string.
I need a way to prevent this behavior by setting the empty strings to be undefined somehow.
Is there such an option in Mongoose? or do I have to predict my own middlewares for that?

You could use the set method for Mongoose Schemas:
const mySchema = new mongoose.Schema(
{
myAttribute: {
type: String,
set: (attribute: string) => attribute === '' ? undefined : attribute,
},
},
{ strict: 'throw' },
);
This will unset the field if the string equals ''.
Use this to trim the strings:
set: (a: string) => a?.trim() === '' ? undefined : a

You don't need mongoose, or a middleware to handle this. You can just write a quick few lines to check for empty values and exclude them from the MongoDB write operation.
Ex:
const newEntry = Object.entries(req.body).reduce((obj, [key, value]) => {
if (value) obj[key] = value
return obj
}, {})
In this example, I convert the req.body into an array using Object.entries and iterate over it with the Array.reduce method, wherein I add key:value pairs to a new object if there is a value to add. Since an empty string value is falsey I can do a simple if check on the value. I then assign the return of the reduce method to the variable newEntry. Then I would then take the new entry and create the MongoDB document with it.
This could be extracted into a helper method and reused in any of your routes that need to check remove empty values from an object.
Docs on Array.reduce
Docs on Object.entries

Related

Yup conditionally extend Object Schema with other Schema

For a Project I use yup and I have some trouble extending a Schema when a property is set.
I build a Codesandbox to show my Problem but the basics are the following.
// Schema I want to extend by, I use it in other places which is why I want to spread it into the baseSchema
const baseSchema = yup.object({
firstName: yup.string().required()
}).required();
const extendedSchema = yup
.object({
hasOtherFields: yup
.mixed()
.required()
.oneOf([...TRUE_FLASE])
})
.when("hasOtherFields", {
is: (value?: typeof TRUE_FLASE[number]) =>
value != null && value === "true",
then: (schema) =>
schema
.shape({
...baseSchema.fields
})
.required()
})
.required();
So the property hasOtherFields needs to be set and needs to be either "true" or "false". If it is "true", I extend the extendedSchema with shape and pass in the fields from another the baseSchema which has a required string field.
Sadly this doesn't work ... If I create a nested object property inside the object declaration like so
const extendedSchema = yup
.object({
hasOtherFields: yup
.mixed()
.required()
.oneOf([...TRUE_FLASE]),
extendedFields: yup.object({}).when("hasOtherFields", {
is: (value?: typeof TRUE_FLASE[number]) =>
value != null && value === "true",
then: (schema) =>
schema
.shape({
...baseSchema.fields
})
.required()
})
})
it works, but that doesn't really work for my use case.
So my question is, is there a way ? I basically want the type to look like this
type Schema = {hasCoApplicant: "false"} | {hasCoApplicant: "true"; firstName: string};

MongoDB field only accepts 3 special values

slider_value: {
type: Number,
required: false,
},
This is the Mongoose schema for one of the fields in my MongoDB model.
It may only accept the integer values of 1, 4, and 10.
How can this validator be specified in the schema?
If you only need to store either one of these three values, storing them as a string, and validating using the enum key would be reasonable. For example that could look like this:
{
slider_value: {
type: String,
enum: ["1", "4", "10"],
},
}
Alternatively, if it is a requirement to store them in form of an int, you could use a custom validator to check a value before it's saved. That would look like this:
{
slider_value: {
type: Number,
validate: {
validator: value => value === 1 || value === 4 || value === 10,
message: props => `${props.value} is invalid for slider_value`,
},
},
}
For more details on custom validators and validation in mongoose in generell, here are the mongoose validation docs.

Argument passed in must be a single String of 12 bytes or a string of 24 hex characters, Mongoose ObjectId err

I actually searched a ton and I saw a ton of mentions of my problem here but none of the things I tried helped me fix the issue i'm having.
I have a Room Scheme that looks like this:
const ObjectId = mongoose.Schema.ObjectId;
const roomSchema = mongoose.Schema({
users: [{
type: ObjectId,
ref: 'User'
}],
messages: [{
type: ObjectId,
ref: 'Message',
}],
post: {
type: ObjectId,
ref: 'Post'
}
});
As you can see I have an array of users with ref to another schema Users
I'm trying to query all the Rooms that has a User ObjectId in it (search ObjectId in an array).
while I can easily get this with querying mongo from cmd using this:
db.users.find({users:ObjectId('THE_OBJECT_ID')});
when I try to get the same while using mongoose it fails with:
Error: Argument passed in must be a single String of 12 bytes or a string of 24 hex characters
Here is how my route and find looks like:
app.route('/rooms/list/:user_id')
.get((req, res) => {
var query = { users: "USER_ID" };
Room.find(query ).populate('messages').then((data) => {
res.status(200).json(data);
}).catch((err) => {
console.log(err);
});
})
I tried to create type of object ID and use it but it still doesn't work.
var mongoose = require('mongoose'),
userId = 'THE_USER_ID';
var id = mongoose.Types.ObjectId(userId);
and than
Rooms.find({'users': id });
but it still doesn't work.
I also tried altering my query search using $in, $elemmatch it worked on cmd but failed when querying using mongoose.
Any help would be appreciated.
Issue :
If you check this :
var query = { users: "USER_ID" };
(Or)
userId = 'THE_USER_ID';
var id = mongoose.Types.ObjectId(userId);
What are you trying to do here ? You are passing in string USER_ID or THE_USER_ID as input and trying to convert it to type of ObjectId(). But string inside ObjectId() has certain restrictions which is why mongoose is failing to convert passed in string value to ObjectId() and getting error'd out.
Try this code :
Code :
const mongoose = require('mongoose');
app.route('/rooms/list/:user_id')
.get((req, res) => {
var query = { users: mongoose.Types.ObjectId(req.params.user_id) };
Room.find(query).populate('messages').then((data) => {
res.status(200).json(data);
}).catch((err) => {
console.log(err);
});
})
Your input should be value of user_id (Which will be string) - Convert it to ObjectId() and then query DB. So value of user_id should be a string that obeys ObjectId()'s restrictions, You can take string from one of existing doc's ObjectId() & test your get api.

Transform JSON Response Field Name In Mongoose Model

My JSON response contains a field first_name but I want my Mongoose model to represent this field as firstName. Is this possible and if so then how?
You can create a new object with different property names from the one Mongoose returns. A nice way of doing this is to create a transform function. For example, let's say this is your schema:
{
firstName: { type: String, required: true },
lastName: { type: String, required: true }
}
Then you can use this function to create a new object with the desired property names:
function transformDocument (doc) {
return {
first_name: doc.firstName,
last_name: doc.lastName
}
}
Then, when you query the DB, you apply this function to the response:
Person.findOne({ firstName: 'John', lastName: 'Smith' })
.then(transformDocument)
Doug W has a good solution, but if you don't want to be using Promises and chaining .thens, then you can simply add options to the schema like this:
const mongoose = require ('mongoose'); // I am using v5.9.1 at the moment
const { Schema } = mongoose.Schema;
// Specify an options object
const options = {
toJSON: {
versionKey: false
}
// If you ever send the query result as an object,
// you may remove it from there, too, if you wish
// toObject: {
// versionKey: false
// }
};
// Attach the options object to the schema by
// passing it into Schema as the second argument
const mySchema = new Schema({
/** define your schema */
}, options);
This will still save __v to the document in the database. But it will not appear on the json/object when it is the result of a mongoose query.
Besides setting versionKey: false in the options, you may also specify a transform function:
/* ... */
// Specify an options object
const options = {
toJSON: {
// versionKey: false,
transform: function(doc, ret) {
// ret is the object that will be returned as the result
// (and then stringified before being sent)
delete ret.__v;
return ret;
}
}
};
/* ... */
I know this question is nearly two years old, but I needed an answer to this question, and google was not kind to me at the time. I figured it out, and now I'm hoping someone else will be looking for an answer here and find that they have options. Pun not originally intended.

Mongoose, does this model already exist in the collection

I'm using Mongoose on a Node.js server to save data into MongoDB. What I want to do is to check and see if a model object exists in the collection already.
For example heres my model:
var ApiRequest = new Schema({
route: String,
priority: String,
maxResponseAge: String,
status: String,
request: Schema.Types.Mixed,
timestamp: { type: Date, default: Date.now }
});
And here's what I would like to do:
var Request = mongoose.model('api-request', ApiRequest);
function newRequest(req, type) {
return new Request({
'route' : req.route.path,
'priority' : req.body.priority,
'maxResponseAge' : req.body.maxResponseAge,
'request' : getRequestByType(req, type)
});
}
function main(req, type, callback) {
var tempReq = newRequest(req, type);
Request.findOne(tempReq, '', function (err, foundRequest) {
// Bla bla whatever
callback(err, foundRequest);
});
}
The big issues I'm finding are that the tempReq which is a model has an _id variable and a timestamp that is going to be different from what is saved in the database. So I would like to ignore those fields and compare by everything else.
As a note my actual models have more variables than this hence the reason I don't want to use .find({ param : val, ....})..... and instead would like to use the existing model for comparison.
Any ideas? Thanks!
You need to use plain JS objects instead of Mongoose model instances as query objects (the first parameter to find).
So either:
Change newRequest to return a plain object and later pass that into a new Request() call if you need to add it to the database.
OR
In your main function turn tempReq into a query object like this:
var query = tempReq.toObject();
delete query._id;
Request.findOne(query, ...