I am having trouble getting connect flash to work in the sequelize callback function.
router.route('/')
.post(function(aRequest, aResponse) {
var data = aRequest.body;
models.users.findOne({
where: {
email: data.email
}
}).then(function (aUser) {
if (!aUser) {
bcrypt.hash(data.password, null, null, function(err, hash) {
if(err) {
return next(err);
}
models.users.create({
firstname : data.firstname,
lastname : data.lastname,
email : data.email,
password : hash
}).then(function () {
aRequest.flash('success', 'User successfully created.');
}).done(function () {
aResponse.redirect('/login');
});
});
}
else {
// aRequest.flash('error', 'This email address is already registered');
aResponse.redirect('/login');
}
});
});
Above is my current code, have tried a few variations on it, calling both flash and redirect in the .then(), tried 2 .then()'s and now the .done().
I am getting the following error:
Unhandled rejection TypeError: undefined is not a function
at Instance.set (/home/node/shared/Heroku/landbou/node_modules/sequelize/lib/instance.js:348:68)
Which is easily resolved by removing aRequest.flash(...).
And yes, 'router.use(flash());' is being called higher up.
Everything continues so its not an app breaking error, but I do need the messages to flash up, otherwise I have to create additional wasted routes to handle the success/fail for user registrations.
Related
In short I am trying to create a simple api that would return the user with the matching id. I use postman to send requests to my localhost created using node.js with express. It works fine when I request the first user but throws in an error when requesting "John". I am coding along a udemy course and can't figure out what the issue is other than the material is outdated. The error is "Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client"
users: [
{
id: "123",
name: "Sally",
email: "sally#gmail.com",
password: "bananas",
entries: 0,
joined: new Date(),
},
{
id: "124",
name: "John",
email: "john#gmail.com",
password: "apples",
entries: 0,
joined: new Date(),
},
],
};
app.get("/profile/:id", (req, res) => {
const { id } = req.params;
let found = false;
database.users.forEach((user) => {
if (user.id === id) {
found = true;
return res.json(user);
}
if (!found) {
res.json("User not found");
}
});
});
From the MDN Web Docs:
There is no way to stop or break a forEach() loop other than by throwing an exception. If you need such behavior, the forEach() method is the wrong tool.
Early termination may be accomplished with:
A simple loop
A for...of
loop
[Array.prototype.every()][every]
[Array.prototype.some()][some]
[Array.prototype.find()][find]
[Array.prototype.findIndex()][findIndex]
This means that your loop will run through all elements and in fact call res.json multiple times resulting in the ERR_HTTP_HEADERS_SENT error . There are many ways to fix this, here's an one example:
app.get("/profile/:id", (req, res) => {
const {id} = req.params;
for (const user of database.users) {
if (user.id === id) {
return res.json(user);
}
}
res.json("User not found");
});
I'm trying to send an email and password server-side and check if a document with those values exists (which it does), but when I console log the results from the query it's null.
Here's the document in the users collection:
{
"_id" : ObjectId("580bcf9874ae28934705c0fc"),
"email" : "johndoe#gmail.com",
"password" : "pass"
}
Here's what I'm sending server-side:
{"email":"johndoe#gmail.com","password":"pass"}
Here's my code (updated):
mongo.connect('mongodb://localhost:27017', function (err, db) {
if (err) {
console.log("error: " + err); // logs nothing
} else {
var users = db.collection("users");
var tasks = db.collection("tasks");
app.post("/login", function(req, res) {
var emailRegex = /^(([^<>()\[\]\\.,;:\s#"]+(\.[^<>()\[\]\\.,;:\s#"]+)*)|(".+"))#((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
var userInDb;
var userEmail = req.body.email;
var userPassword = req.body.password;
console.log(req.body.email); // logs "johndoe#gmail.com"
console.log(req.body.password); // logs "pass"
if (!userEmail || !userPassword) {
return res.sendStatus(403);
} else if ( !emailRegex.test(userEmail)) {
return res.sendStatus(403);
} else {
users.findOne( { "email": userEmail, "password": userPassword }, function(err, results) {
console.log(results); // logs "null"
if(err) {
console.log("error: " + err); // logs nothing
res.sendStatus(403);
} else {
console.log("here"); // logs "here"
res.sendStatus(200);
}
});
}
});
}
});
each time you pass a callback that has an error parameter, it's your responsibility to check if an error was passed, and if so, deal with it.
in your code, you have two such callbacks:
mongo.connect('mongodb://localhost:27017', function (err, db)
users.findOne( { "email": userEmail, "password": userPassword }, function(err, results)
either one of them can return an error object that might explain the issue.
add the following to the first line of each callback:
if (err) {
return console.log("error: " + err);
}
This one worked for me.
I had to call toArray() method.
I don't remember how I found that solution, cuz in MongoDB manuals they don't call to array method
users.findOne( { "email": userEmail, "password": userPassword }).toArray()
I faced a simular problem in one of my project. It is all because I stored the collection and the document in a database which is different from which my app is connected to. Check that once.
It is really mysterious, I think MongoDB client should make a fix on it.
MongoDB is not very reliable. Often get lose connection in 1/10 of requests. But the very annoying is, it returns an empty array instead of an error in connection, that makes us impossible to catch connection error.
Because I use the existence of documents in DB to reinitialize the project, I really get annoyed of it. CouchDB will not have this problem.
users.findOne({'email' : userEmail , 'password':userPassword }, function(err, result) {
console.log("result:"+result);
});
I'm very new to node and sequelize and I'm trying to follow this short introduction.
I've worked through the parts to connect to my database (postgres). I've also defined a model:
var User = sequelize.define('User', {
username: Sequelize.STRING,
password: Sequelize.STRING
});
I have succesfully synchronized the scheme and created instances. But when I attempt to read from the database using this:
User
.find({ where: { username: 'john-doe' } })
.then(function(err, johnDoe) {
if (!johnDoe) {
console.log('No user with the username "john-doe" has been found.');
} else {
console.log('Hello ' + johnDoe.username + '!');
console.log('All attributes of john:', johnDoe.get());
}
});
That instance does exist, but I only ever see the 'No user with...' message. The query it generates seems correct and when I try it manually, the returned results are what I would expect to see.
Using the same query I can do this, which also works:
sequelize.query("SELECT * FROM my_user_table where username='john-doe'", { type: sequelize.QueryTypes.SELECT})
.then(function(items) {
// We don't need spread here, since only the results will be returned for select queries
console.log(items);
});
What am I missing here?
You're mixing up promises and node-style callbacks. Typically you only expect (err, results) when you pass a callback to the original function. If you call then, you are working with promises and should only expect results. You should call catch to get any errors.
User
.find({ where: { username: 'john-doe' } })
.then(function(johnDoe) {
if (!johnDoe) {
console.log('No user with the username "john-doe" has been found.');
} else {
console.log('Hello ' + johnDoe.username + '!');
console.log('All attributes of john:', johnDoe.get());
}
})
.catch(function(err) {
// Error handling here
});
Actually, you was too close. But you must not use an argument for error handling on then method.
So you must use like the following;
User
.findOne({ where: { username: 'john-doe' } })
.then(function(johnDoe) {
if (!johnDoe) {
console.log('No user with the username "john-doe" has been found.');
} else {
console.log('Hello ' + johnDoe.username + '!');
console.log('All attributes of john:', johnDoe.get());
}
});
I am building a REST api with nodejs, using mongoose and mochajs to run some tests. I have the following scheme:
var subscriptionTypeSchema = new mongoose.Schema({
typeId : { type: Number, required: true, unique: true },
name : { type: String, required: true},
active : { type: Boolean, required: true }
});
Express route:
app.post('/1.0/subscriptiontype', subscriptiontype.create);
Controller:
exports.create = function(req, res) {
validation.subscriptionTypeValidator(req);
var errors = req.validationErrors();
if (errors) {
res.status(400).json({
errors: errors
});
} else {
var subscriptionType = new SubscriptionType();
subscriptionType.typeId = parseInt(req.body.typeId);
subscriptionType.name = req.body.name;
subscriptionType.active = req.body.active;
subscriptionType.save(function(err) {
if (err) {
var parsedError = mongooseutility.parseMongooseError(err);
res.status(400).json({
errors: [parsedError]
});
} else {
res.json({identifier: subscriptionType._id});
}
});
}
};
The mongoose utility maps the error codes to a more API friendly output (error codes 11001 and 11000 are mapped to a 'duplicate' error, as can be seen in the test).
Mocha before method:
before(function(done) {
db.connection.on('open', function() {
db.connection.db.dropDatabase(function(err) {
done();
});
});
});
I've verified that the database is dropped successfully.
The test itself makes a request using supertest. Before this test, I have a test that creates a subscription type with typeId 4 successfully, so this one should fail:
it('Should not create subscription with taken type id', function (done) {
request(app.privateapi)
.post('/1.0/subscriptiontype')
.set('Authorization', authorizationHeader)
.send({
typeId: 4,
name: 'New package',
active: 1
})
.expect(function (res) {
if (res.status !== 400) {
throw new Error('Status code was not 400');
}
var expectedResponse = { errors: [ { param: 'typeId', msg: 'duplicate' } ] };
if (JSON.stringify(res.body) !== JSON.stringify(expectedResponse)) {
throw new Error('Output was not was as expected');
}
})
.end(done);
});
Tests are invoked using grunt-simple-mocha.
This test works the first time, however when I run it a 2nd time it fails on the unique validation. A third time it works again. I've done some searching and found that it probably has something to do with a race condition while recreating indexes, so I've tried restarting mongodb before running the tests again, but that doesn't work. I've found a solution here: http://grokbase.com/t/gg/mongoose-orm/138qe75dvr/mongoose-unique-index-test-fail but I am not sure how to implement this. Any ideas?
Edit: for now I fixed it by dropping the database in an 'after' method (instead of 'before'). All the tests run fine, but it would be nice to keep the test data after the tests are done, for inspection etc...
You are not testing the creation of your tables so you can just empty your collections instead of creating the db.
Something along those lines (not tested):
beforeEach(function(done){
var models = Object.keys(mongoose.models);
var expects = models.length;
if(expects == 0) return done();
var removeCount = 1;
//maybe use async or something else but whatever
models.forEach(function(model){
model.remove({}, function(){
if(removeCount == expects){
done();
}
removeCount++;
})
});
});
I'm using this schema with mongoose 3.0.3 from npm:
var schema = new Schema({
_id: Schema.ObjectId,
email: {type: String, required: true, unique: true}
});
If I try to save a email that is already in db, I expect to get a ValidationError like if a required field is omitted. However this is not the case, I get a MongoError: E11000 duplicate key error index.
Which is not a validation error (happens even if I remove the unique:true).
Any idea why?
I prefer putting it in path validation mechanisms, like
UserSchema.path('email').validate(function(value, done) {
this.model('User').count({ email: value }, function(err, count) {
if (err) {
return done(err);
}
// If `count` is greater than zero, "invalidate"
done(!count);
});
}, 'Email already exists');
Then it'll just get wrapped into ValidationError and will return as first argument when you call validate or save .
I had some issues with the approved answer. Namely:
this.model('User') didn't work for me.
the callback done wasn't working properly.
I resolved those issues by:
UserSchema.path('email').validate(async (value) => {
const emailCount = await mongoose.models.User.countDocuments({email: value });
return !emailCount;
}, 'Email already exists');
I use async/await which is a personal preference because it is much neater: https://javascript.info/async-await.
Let me know if I got something wrong.
This is expected behavior
The unique: true is equivalent to setting an index in mongodb like this:
db.myCollection.ensureIndex( { "email": 1 }, { unique: true } )
To do this type of validation using Mongoose (Mongoose calls this complex validation- ie- you are not just asserting the value is a number for example), you will need to wire in to the pre-save event:
mySchema.pre("save",function(next, done) {
var self = this;
mongoose.models["User"].findOne({email : self.email},function(err, results) {
if(err) {
done(err);
} else if(results) { //there was a result found, so the email address exists
self.invalidate("email","email must be unique");
done(new Error("email must be unique"));
} else {
done();
}
});
next();
});
Simply response to json
try {
let end_study_year = new EndStudyYear(req.body);
await end_study_year.save();
res.json({
status: true,
message: 'បានរក្សាទុក!'
})
}catch (e) {
res.json({
status: false,
message: e.message.toString().includes('duplicate') ? 'ទិន្នន័យមានរួចហើយ' : e.message.split(':')[0] // check if duplicate message exist
})
}
Sorry for answering an old question. After testing I feel good to have find these answers, so I will give my experience. Both top answers are great and right, just remember that:
if your document is new, you can just validate if count is higher than 0, thats the common situation;
if your document is NOT new and has modified the unique field, you need to validate with 0 too;
if your document is NOT new and has NOT being modified, just go ahead;
Here is what I made in my code:
UserSchema.path('email').validate(async function validateDuplicatedEmail(value) {
if (!this.isNew && !this.isModified('email')) return true;
try {
const User = mongoose.model("User");
const count = await User.countDocuments({ email: value });
if (count > 0) return false;
return true;
}
catch (error) {
return false;
}
}, "Email already exists");