I have created a rest api and I am trying to add multiple users to atlas mongodb I use this schema
const mongoose = require('mongoose');
const { v1: uuidv1 } = require('uuid');
const crypto = require('crypto')
const userSchema = new mongoose.Schema({
// _id: mongoose.Types.ObjectId,
name: {
type: String,
// trim: true,
unique: true,
required: true,
index: true
},
email: {
type: String,
// trim: true,
required: true,
unique: true,
},
hashed_password: {
type: String,
trim: true,
required: true
},
salt: String,
created: {
type: Date,
default: Date.now
},
updated: Date,
})
// VIRTUAL FIELD
userSchema.virtual('password')
.set(function(password){
//create temporary variable called _password
this._password = password
//generate a timestamp
this.salt = uuidv1();
//encryptPassword
this.hashed_password = this.encryptPassword(password)
})
.get(function(){
return this._password
})
///methods
userSchema.methods = {
authenticate: function(plainText){
return this.encryptPassword(plainText) === this.hashed_password
},
encryptPassword : function(password){
if(!password) return "";
try{
return crypto.createHmac('sha256', this.salt)
.update(password)
.digest('hex');
} catch(err){
return ""
}
}
}
module.exports = mongoose.model('User', userSchema);
I use this function to sign up :
exports.signup = async (req, res) => {
const userExists = await User.findOne({email : req.body.email})
if(userExists) return res.status(403).json({
error: "EMAIL is TAKEN"
})
const user = await new User(req.body)
await user.save()
.then(result => {res.json({result: result})})
.catch(err => res.json({err : err}))
}
I validate :
exports.userSignupValidator = (req, res, next) => {
//name is not null and its between 4 and 10 characters
req.check('name', 'name is required').notEmpty();
//email is not null, valid and NORMALIZED -> we will use method chaining
req.check('email', 'please enter valid email')
.matches(/.+\#.+\..+/)
.withMessage('email must contain #')
.isLength({
min: 4,
max: 2000
})
//check for password
req.check('password', 'Password is required').notEmpty();
req.check('password').isLength({
min: 6,
}).withMessage('password must be minimum 6 char long').matches(/\d/).withMessage('must contain a number')
//check for errors
const error = req.validationErrors()
////////if error apears show the first one as they appear
if(error){
const firstError = error.map((error) => error.msg)[0]
return res.status(400).json({error: firstError})
}
////proceed to next middleware
next()
}
and I use the route :
const express = require('express'); //bring in express
const postController = require('../controlers/postControler') //brings everything that is exported from the postControler FILE and becomes a OBJECT
const router = express.Router();
const validator = require('../validator');
const signup = require('../controlers/authControler');
const userById = require('../controlers/userControler');
router.get('/', postController.getPosts)
router.post('/post', signup.requireSignIn, validator.createPostValidator, postController.createPost)
router.get('/test' , postController.test)
router.post('/signup', validator.userSignupValidator, signup.signup)
router.post('/signin', signup.signin)
router.get('/signout', signup.signout)
router.get('/lahoha', userById.getUsers)
////find the user by id with params
////any routes containing :userId our app will first execute userById()
router.param('userId', userById.userById);
///////////////////////////////////////////////
module.exports = router
the problem is when I try to create a second user with postman with :
{
"name": "petru",
"email": "petru#gmail.com",
"password": "notazece10"
}
I get the error :
{
"err": {
"driver": true,
"name": "MongoError",
"index": 0,
"code": 11000,
"keyPattern": {
"username": 1
},
"keyValue": {
"username": null
}
}
}
Please help !!!!! this error is driving me crazy, I don't know what I'm doing wrong
after running thru my code multiple times line by line i found out the code is fine , the problem was in my atlas mongodb database.
So i am new to nodejs and mongo , and i try to learn , when i created my first mongodb database in atlas i did not pay attention to naming my database so it had the default name of .
I went back to atlas mongodb and i made a new database ( cluster) , named it TEST , copied the link, went into my dotenv file paste the link to my MONGO_URI restarted the server and then all code worked fine now i can add as many users as i want.
I hope other newcomers to mongodb and nodejs learn from my mistake and if someone ever repeats my STUPID mistake i hope they find this and fix it.
Related
I'm trying to follow the MVC architectural pattern and do all of my validation in my Mongoose model, rather than my controller.
I'm wondering how I can set error codes and truly custom error messages in my model (I.E. without the part that mongoose adds to the beginning of the message.)
At the moment my error message for the name field is: "message": "User validation failed: email: Please enter a valid email address", where it should be "Please enter a valid email address".
The response code from the server was 200 until I changed it in my errorHandlerMiddleware file, which is not ideal as it should be a 400 not the general 500.
So, somebody please help me to set the status code in my model and also make a custom error message.
Many thanks in advance!
const mongoose = require("mongoose");
const bcrypt = require("bcryptjs");
const jwt = require("jsonwebtoken");
const validator = require("validator");
const Schema = mongoose.Schema;
const UserSchema = new Schema(
{
name: {
type: String,
required: [true, "Please add a name"],
minLength: [3, "Name must be at least 3 characters"],
},
email: {
type: String,
required: [true, "Please add an email address"],
unique: [true, "It looks like you already have an account!"],
validate: {
validator: (value) => {
if (!validator.isEmail(value)) {
throw new Error("Please enter a valid email address");
}
},
},
},
password: {
type: String,
required: [true, "Please add a password"],
},
tokens: [
{
token: {
type: String,
required: true,
},
},
],
},
{ timestamps: true }
);
UserSchema.methods.toJSON = function () {
const user = this;
const userObject = user.toObject();
delete userObject.password;
delete userObject.tokens;
return userObject;
};
UserSchema.methods.generateAuthToken = async function () {
const user = this;
const token = jwt.sign({ _id: user._id.toString() }, process.env.JWT_SECRET, {
expiresIn: "7 days",
});
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) {
statusCode(401);
throw new Error("Unable to login");
}
const isMatch = await bcrypt.compare(password, user.password);
if (!isMatch) {
statusCode(401);
throw new Error("Unable to login");
}
return user;
};
UserSchema.pre("save", function (next) {
if (this.password.length < 6) {
throw new Error("Password must be at least 6 characters");
}
if (!this.isModified("password")) {
return next();
}
this.password = bcrypt.hashSync(this.password, 10);
return next();
});
module.exports = User = mongoose.model("User", UserSchema);
i need a real custom error code and message from mongoose
I decided to catch the errors in the try/catch block on the controller, as so:
try {
await user.save();
} catch (err) {
// Error handling for duplicate email address
if (err.code === 11000) {
return res.status(400).send("It looks like you already have an account.");
}
// Error handling for misc validation errors
if (err.name === "ValidationError") {
res.status(400);
return res.send(Object.values(err.errors)[0].message);
}
}
I tryed myself on a little NextJS App with Mongoose and JWT (json web token).
Everything works and I can put user into my database and other stuff. Just when I create a JWT I can't put it into my database. Here is my code, grateful for every help :D
First my schema for mongoose:
const mongoose = require('mongoose');
const UserSchema = new mongoose.Schema({
username: {
type: String,
required: [true, 'Please add a title'],
unique: true,
maxlength: [15, 'Username cannot be more than 15 characters']
},
password: {
type: String,
required: true,
maxlength: [100, 'Password cannot be more than 100 characters']
},
jwt: {
type: String,
required: true,
maxlength: [1000, 'JWT cannot be more than 200 characters']
}
})
module.exports = mongoose.models.User || mongoose.model('User', UserSchema);
I don't know if type: String for jwt is correct, but JSON looks weird too and doesn't work.
Now my backend API Code:
import dbConnect from '../../../utils/dbConnect';
import User from '../../../models/User';
import sjcl from 'sjcl';
var jwt = require('jsonwebtoken');
const hashword = "censored"
dbConnect();
export default async (req, res) => {
var passwordtohash = req.body.password + hashword
const BitArrayHash = sjcl.hash.sha256.hash(passwordtohash);
const passwordhashed = sjcl.codec.hex.fromBits(BitArrayHash)
var bodywithhash = req.body
bodywithhash.password = passwordhashed
const { method } = req;
switch(method) {
case 'POST':
try {
const user = await User.find({"username": bodywithhash.username, "password": bodywithhash.password});
if (user.length > 0) {
createJWT(user);
res.status(200).json({ success: true })
} else (res.status(200).json({ success: false}))
} catch (error) {
res.status(400).json({ success: false });
}
break;
default:
res.status(400).json({ success: false });
}
}
async function createJWT(user) {
jwt.sign({
exp: Math.floor(Date.now() / 1000) + (60 * 60),
data: 'foobar'
}, 'secret')
const iduser = { "_id" : user[0]._id.toString()}
const updateuser = await User.findByIdAndUpdate(iduser, jwt);
}
All my users in the database have a default value for JWT called "default"
here when I try to insert the data with proper validation it works. and when I post with wrong validation it also throws error as expected but when I use the email that was already present in my database and i had set the unique:true but it stores the document with same email.
What am i doing wrong
My model file:
const mongoose = require("mongoose")
const Userschema = new mongoose.Schema({
name:{
type:String,
required:true
},
email:{
type:String,
required:true,
unique:true,
},
password:{
type:String,
required:true
},
date:{
type:Date,
default:Date.now
}
})
const User = new mongoose.model("user", Userschema)
module.exports = User
main file:
const express = require("express");
const router = express.Router();
const User = require("../models/User");
const { body, validationResult } = require("express-validator");
router.post(
"/createuser",
[
body("name", "please enter valid name").isLength({ min: 5, max: 15 }),
body("password", "your password must be larger or equalt to 8 characters").isLength({ min: 8 }),
],
async (req, res) => {
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(400).json({ errors: errors.array() });
}
try {
const data = await User.create(req.body);
res.json(data);
} catch (error) {
console.log(error);
res.send("some error occured")
}
}
);
The code is correct, If you add the unique after running the API to save the user in DB. It will not work. Because unique indexing runs the first time while creating collections. No worry, drop the data and run API. It should be working. If you don't want to drop the DB. The other solution is to write a migration task to create a unique index.
const { MongoClient } = require('mongodb');
const createIndex = async () => {
try {
const url = 'paste your db url';
const client = new MongoClient(url);
const dbName = 'paste your db name';
await client.connect();
const db = client.db(dbName);
await db.collection('users').createIndex(
{email: 1 },
{ unique: true },
);
process.exit(1);
} catch (err) {
console.log(err.stack);
}
};
createIndex();
Run the above code with 'node'.
I have a user I can save in MongoDB, when I enter correct data, the save works.
But when I enter wrong data, I can't catch the errors to be seen for the user. All I can see is this on the code editor:
...UnhandledPromiseRejectionWarning: ValidationError: User validation
failed: username: username is not there!...
This error "kills" the server, and so I'm not rendering home-guest template.
The question is how I can catch the errors and show them to the user?
Schema:
const mongoose = require("mongoose")
const userSchema = new mongoose.Schema({
username: {
type: String,
required: [true, "username is not there!"],
minlength: 3,
maxlength: 20,
},
email: {
type: String,
required: true,
},
password: {
type: String,
required: true,
minlength: 6,
maxlength: 20,
},
})
module.exports = mongoose.model("User", userSchema)
Controller:
const mongoose = require("mongoose")
const userModel = require("../models/userModel")
exports.signUp = async (req, res) => {
const { username, email, password } = req.body
try {
const user = await new userModel({
username,
email,
password,
})
user.save()
} catch (error) {
res.render("home-guest", { error })
}
}
You just need to add an await to the save operation, since that's also async:
const mongoose = require("mongoose")
const userModel = require("../models/userModel")
exports.signUp = async (req, res) => {
const { username, email, password } = req.body
try {
const user = await new userModel({
username,
email,
password,
})
// Wait for the save to complete, also allowing you to catch errors
await user.save()
} catch (error) {
res.render("home-guest", { error })
}
}
EDIT: And note that you do not need an async in front of new userModel(). new cannot return a promise, it is always synchronous.
I'm facing this problem since couple of days where I'm trying to insert the data into mongodb using mongoose but not able to get the data in mongodb. Below is the schema that I have created
const mongoose = require('mongoose')
const db = require('../db/db')
const crypto = require('crypto')
const { v4 : uuidv4 } = require('uuid');
const validator = require('validator')
// const { stringify } = require('querystring')
const schema = mongoose.Schema
const userSchema = new schema({
ObjId: schema.Types.ObjectId,
name : {
type : String,
trim: true,
required : true,
maxlength: 32
},
email : {
type : String,
trim: true,
required : true,
validate(value) {
if(!validator.isEmail(value)){
throw new Error ('The Email you have entered is not correct. Please enter the correct Email ID')
}
}
},
hashed_password : {
type : String,
required : true,
},
about : {
type : String,
trim: true,
required: true
},
salt : String,
user_roles: {
type: Number,
default: 0,
required: true
},
history : {
type: Array,
default: []
},
// timestamps: {
// createdAt : '',
// updatedAt : {type : Date, default : Date.now()},
// },
}, {timestamps: true})
// added virtual field
userSchema.virtual('password')
.set((password) =>{
this.password = password,
this.salt = uuidv4()
this.hashed_password = this.encryptPassword(password)
})
.get(() => {
return this._password
})
userSchema.methods = {
encryptPassword : (password) => {
if(!password) return ''
try {
return crypto.createHmac('sha256', this.salt)
.update(password)
.digest("hex")
}
catch(error) {
if(error) {
console.log('Found an Error in Line 70 in User.Js', error.message)
}
}
}
}
module.exports = mongoose.model("User", userSchema);
This is how I'm connecting the db
const mongoose = require('mongoose')
require('dotenv').config
// const connectionURL = 'mongodb://127.0.0.1:27017'
//const databaseName = 'ecommerce_db'
mongoose.connect(
// connectionURL,
process.env.MONGOURI,
{
useNewUrlParser: true,
useCreateIndex: true,
useUnifiedTopology: true
}
)
// .then((result) => { console.log('Mongo DataBase Connected', result)})
.then(() => { console.log('Mongo DataBase Connected')})
.catch((err) => {console.log('Mongoose Connection Failed', err)})
and this is where I'm saving the data
const User = require('../models/user')
const { response } = require('express')
const mongoose = require('mongoose')
exports.signUp = (req, res) => {
console.log('[Logging]', req.body)
// const user = new User({
// _id: mongoose.Schema.Types.ObjectId(),
// name: req.body.name,
// email: req.body.email,
// password: req.body.hashed_password
// })
const user = new User(req.body)
user.save((error, response) => {
if(error) {
return res.status(400).json({
error
})
}
res.json({
user
})
})
}
I'm getting all the correct input and success messages but still I'm not able to get the data inside mongodb, am I doing something wrong