How to automatically create a required field in mongoose - node.js

I have a mongoose schema that looks like this:
var userSchema = new Schema({
username: {type: String, required: true, index: {unique: true}},
usernameCanonical: {type: String, required: true, index: {unique: true}}
});
userSchema.pre("save", function () {
this.usernameCanonical = this.username.toLowerCase();
return next();
});
I want to be able to create new users by only entering a username, and let usernameCanonical get generated by the model automatically.
var user = new User({
username: "EXAMPLE_USERNAME"
});
user.save()
When I try to do this I get a validation error from mongoose saying that usernameCanonical is required.
Path `usernameCanonical` is required.
The problem seems to be that the pre-save hooks get called after validation. I don't want to have to manually add a canonical username every time I save a new user. I also don't want to remove the required option from the schema.
Is there some way to get a mongoose model to automatically generate a required field? Adding a default value to the usernameCanonical field in the schema seems to prevent the validation error, but it feels like a hack.

As levi mentioned, you should use the validate() hook:
Save/Validate Hooks
Check this working example based on your code:
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
const userSchema = new Schema({
username: {type: String, required: true, index: {unique: true}},
usernameCanonical: {type: String, required: true, index: {unique: true}}
});
userSchema.pre('validate', function () {
if ((this.isNew || this.isModified) && this.username) {
this.usernameCanonical = this.username.toLowerCase();
}
});
const User = mongoose.model('user', userSchema);
mongoose.connect('mongodb://localhost:27017/uniqueTest')
.then(() => {
// create two users
const user1 = new User({
username: 'EXAMPLE_USERNAME-1'
});
const user2 = new User({
username: 'EXAMPLE_USERNAME-2'
});
return Promise.all([
user1.save(),
user2.save()
]);
})
.then(() => {
// update username
return User.findOne({ username: 'EXAMPLE_USERNAME-1' })
.then((user) => {
user.username = 'EXAMPLE_USERNAME_UPDATED-1';
return user.save();
});
})
.then(() => mongoose.connection.close())
.then(() => console.log('script finished successfully'))
.catch((err) => {
console.error(err);
mongoose.connection.close()
.then(() => process.exit(1));
});
I hope this helps.

Related

Can't save data into MongoDB server using Mongoose

I'm trying to integrate a database into my DiscordBot app and currently having a problem with inputting new user into MongoDB using Mongoose.
The console also show no err at all.
const mongoose = require('mongoose')
const User = require('../models/userModels')
module.exports = message => {
User.findOne({userID:message.author.id}, (err,user)=>{
if(user == null){ //Create new db collections if none was found
const user = new User({
_id: mongoose.Types.ObjectId(),
userID: message.author.id,
username: message.author.username,
role: ["default"],
balance: 0,
level: 1,
exp: 0,
lastAttendance: message.createdAt
})
user.save()
.then(result => {
console.log(result)
message.reply("Information saved")
})
.catch(err => console.log.err)
}
}
This is my model code:
const mongoose = require('mongoose')
const Schema = mongoose.Schema
var userSchema = new Schema({
_id: Schema.Types.ObjectId,
userID:{type: String, unique:true, required:true},
username: String,
balance:{type: Number, require:true},
role: {type:String, enum: ['default','admin']},
level:{type: Number, require:true},
exp:{type: Number, require:true},
lastAttendance: Date
})
module.exports = mongoose.model('Users', userSchema)
The reason why you are not getting error logs is because of the console.log syntax in the catch statement is wrong. Change console.log.err to console.log(err)
The type defined for role in the userSchema is String and you are passing it as array role: ["default"]. You need to change this to role: "default".
Also on success, if you are trying to set the key reply to the message object with value "Information saved" then you should replace message.reply("Information saved") with message.reply = "Information saved"

Validation with MongooseJS causing NodeJS app to crash

I'm trying to find a better way of performing this validation. I have the user schmea setup and I'm trying to get the age validation working properly as to not cause the app to crash. You'll have to forgive me as I'm still relatively new to the language, so I may not be explaining it 100%. However, here is the User schema I created.
const mongoose = require('mongoose')
const validator = require('validator')
const bcrypt = require('bcryptjs')
const jwt = require('jsonwebtoken')
const userSchema = new mongoose.Schema({
name: {
type: String,
required: true,
trim: true
},
age: {
type: Number,
default: 0,
validate(value) {
if(value < 13){
throw new Error('You must be over the age of 13 to register for this site!')
}
}
},
email: {
type: String,
unique: true,
required: true,
trim: true,
lowercase: true,
validate(value){
if (!validator.isEmail(value)) {
throw new Error('Email is invalid')
}
}
},
password: {
type: String,
required: true,
trim: true,
minlength: 7,
validate(value){
if (value.toLowerCase().includes('password')) {
throw new Error('Password cannot contain "password"')
}
}
},
tokens: [{
token: {
type: String,
required: true
}
}]
})
userSchema.virtual('tasks', {
ref: 'Task',
localField: '_id',
foreignField: 'owner'
})
userSchema.methods.generateAuthToken = async function () {
const user = this
const token = jwt.sign({ _id: user._id.toString() }, 'thisismynewcourse')
user.tokens = user.tokens.concat({ token })
await user.save()
return token
}
userSchema.statics.findByCredentials = async (email, password) => {
const user = await User.findOne({ email })
if (!user) {
throw new Error('Unable to login')
}
const isMatch = await bcrypt.compare(password, user.password)
if (!isMatch) {
throw new Error('Unable to login')
}
return user
}
//Hash the plain text password before saving
userSchema.pre('save', async function(next) {
const user = this
if (user.isModified('password')) {
user.password = await bcrypt.hash(user.password, 8)
}
next()
})
userSchema.methods.toJSON = function () {
const user = this
const userObject = user.toObject()
delete userObject.password
delete userObject.tokens
return userObject
}
const User = mongoose.model('User', userSchema)
module.exports = User
The exact area that I'm trying to hone in on is in the age section, I'm trying to validate ages 13 or older, and when I run a test user creation through post man it performs the validation correctly, but it stops the application with the following:
UnhandledPromiseRejectionWarning: ValidationError: User validation failed: age: You must be over the age of 13 to register
Is there a way that I can prevent the application from crashing or should I perform the validation else where? Thanks in advance.
Normally the validation is performed in another file. This can be considered to be a service. But it should pass through a controller first if you want to do it properly. Here is an example of a simple blog post schema I made. You can see the function at the bottom runs every time before I send it to the database.
This is how it looks like in my schema file looks like which is located in folder called models.
// Requiring modules
const mongoose = require('mongoose');
// Initializing Schema
var Schema = mongoose.Schema;
// Creating a data model
const schema = new Schema({
shopname : {type: String, required:true},
address : {type: String, required:true},
review : {type: String, required:false},
image : {type: String, required:false},
originalname: {type: String, required:false},
filename: {type: String, required:false},
mimetype: {type: String, required:false},
size : {type: String, required:false},
updatedAt: {type: Date, required:false},
createdAt: {type: Date, required:false}
})
// Settings up the process before the data is sent to mongoDB.
// This process is call everytime 'save' is called.
// it sets the data for createdAt and updatedAt.
schema.pre('save', function(next){
if (!this.createdAt){
this.createdAt = new Date();
}else{
this.updatedAt = new Date();
}
next();
})
// Exports module
module.exports = mongoose.model("Blog", schema);

schema.methods is not a function

I have been trying to create a method on my user schema in mongoose, however it keeps saying method is not a function and I have no idea why. I am fairly new to mongoose and express, and I'm pretty sure I have my files set up currently so I don't know what could be causing this issue. As a last attempt, I tried switching to arrow functions , but that didn't work either.
user routes file
const router = require("express").Router();
let user = require("../models/user_model");
const Joi = require("#hapi/joi");
// GET dreams
// POST dreams
// DELETE dreams
// UPDATE dreams
router.route("/").get((req, res) => {
console.log(user.addType());
res.send("hello this is a users page");
});
user model file
const mongoose = require("mongoose");
const Schema = mongoose.Schema;
const userSchema = new Schema(
{
username: {
type: String,
required: true,
unique: true,
trim: true,
min: 3
},
password: {
type: String,
trim: true,
required: true,
min: 6
}
},
{
timestamps: true
}
);
userSchema.methods.addTypes = function() {
console.log("woof");
};
userSchema.methods.joiValidate = data => {
let Joi = require("#hapi/joi");
const schema = {
username: Joi.string()
.min(6)
.required(),
password: Joi.string()
.min(6)
.required()
};
return schema.validate(data);
};
module.exports = mongoose.model("User", userSchema);
UPDATE! Other than having typo on your code, you also need to create an instance of your model ('user'). You cannot just call the function of the model.
let user = new user({ // Create an instance first
username: 'Tester',
password: '12345678'
})
console.log(user.addType())
you declared
addTypes()
Cheers

mongoose Model.create function returns undefined

The above query returns a 200 when I try to create a User, but whenever I log into MongoDB there is no collections created. Can anyone help ?
//user model
const userSchema = mongoose.Schema({
name: {
type : String,
required : true,
trim : true
},
email: {
type: String,
required: true,
unique: true,
lowercase: true,
validate: value => {
if(!validator.isEmail(value)){
throw new Error({error : 'Invalid email address'})
}
}
},
password: {
type: String,
required: true,
minLength: 5
},
// a user can have multiple jobs
jobs : [{
type: mongoose.Schema.Types.ObjectId,
ref: 'Job'
}],
tokens: [{
token: {
type: String,
required: true
}
}]
})
const User = mongoose.model('User', userSchema)
module.exports = User
// user functions written
createUser(name, email, password){
return User.create({name: name, email: email, password : password}, (err, docs) => {
if(err){
throw err.message;
}
});
}
//routes.js
// user create
router.post('/users', async(req, res) => {
try{
const {name, email, password } = req.body
const user = userManager.createUser(name, email, password); [1]
res.status(200).json(user)
}
catch(error) {
res.status(400).send({error : error.message})
}
})
The line[1] returns undefined. Why ?
note : all module requirements are fulfilled
After you create the schema you need to create a Model FROM that schema.
Example from MDN:
// Define schema
var Schema = mongoose.Schema;
var SomeModelSchema = new Schema({
a_string: String,
a_date: Date
});
// Compile model from schema
var SomeModel = mongoose.model('SomeModel', SomeModelSchema );
Now after you create the model you can use SomeModel.create
EDIT:
line[1] will always return undefined because you are using callbacks and only way to get value out of callback is either push another callback(I would really discourage that). But best way is to use Promises now mongoose by default supports `Promises. So, basically for promises it will be,
// user functions written
async function createUser(name, email, password){
try {
return await User.create({ name: name, email: email, password: password });
} catch (err) {
throw err.message;
}
}
In the router adda await:
const user = await userManager.createUser(name, email, password);
The problem is you call an asynchronous function synchronously. It returned undefined because the function hasn't been resolved yet.
A solution could be to use promises or async/await.
Example:
async createUser(name, email, password) {
const createdUser = await User.create({name,email,password});
return creaatedUser;
}
Something I ran into was you need to pass in an empty object if your not setting any fields - i.e.
Good: Model.create({})
Bad: Model.create()

Error while saving data in MongoDB atlas with mongoose

I'm trying to save data in the MongoDB atlas with node.js and mongoose.
Every time I use MySchema.save(), Data is inserting But I'm also getting the error:
UnhandledPromiseRejectionWarning: MongoWriteConcernError: No write concern mode named 'majority;' found in replica set configuration
Also, there is no duplicate entry, Data is also inserting But I'm also getting the error
let User = require('../models/users.models');
const username = req.body.username;
const newUser = new User({username});
newUser.save()
.then(() => res.json('user added!'))
.catch(err => res.status(400).json('Error: ' + err));
User model
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
var userSchema = new Schema({
username: {
type: String,
required: true,
unique: true,
trim: true,
minlength: 3
},
},
{
timestamps: true
});
const User = mongoose.model('User', userSchema);
module.exports = User;
I know it was asked 2 months ago, but for those who will encounter the same issue.
You are mistakenly entering a wrong char at the end of the URI string:
mongodb+srv://${ user }:${ password }#track-mkahl.mongodb.net/test?retryWrites=true&w=majority;
You need to delete the ; after the word majority.
This helped me.
const schema = new Schema({ name: String }, {
writeConcern: {
w: 'majority',
j: true,
wtimeout: 1000
}
});
https://mongoosejs.com/docs/guide.html#writeConcern
"mongoURI" : "mongodb+srv://${ user }:${ password }#cluster0.mde0j.mongodb.net/cluster0?retryWrites=true&w=majority "
I get the same error with this in default.json its simple error just delete the &w=majority part at the end and it will be solved
for me it was also in the URI string like #Yossi Saadi has suggested, it's just that I had majoritys written there instead of majority
I think there's something wrong with this line.
let User = require('../models/users.models');
I have created a solution for you.
/models/user.js
const mongoose = require('mongoose')
const Schema = mongoose.Schema
mongoose.connect("mongodb://localhost/stack-overflow", { useNewUrlParser: true })
var userSchema = new Schema({
username: {
type: String,
required: true,
unique: true,
trim: true,
minlength: 3
},
},
{
timestamps: true
});
const User = mongoose.model('User', userSchema);
module.exports = User
/routes/userroute.js
const User = require("../models/user")
// Imagine run() as an asynchronous request handler
async function run() {
try {
const user1 = new User({ username: "lidafafnus" })
user1.save((err,result) => {
console.log(err, result)
})
} catch(error) {
console.log(error)
}
}
run()

Resources