This section will cover creating a user from a form submission. When committing user submitted data to the database it is import to verify that the content is valid, and return an appropriate error message to the user if it is not. To perform the validation we will you be using node-validator.
node-validator - String validation and sanitization in JavaScript
Add node-validator and express-validator to your package.json
package.json
"express-validator": "0.3.x",
"validator": "0.4.x"
Updated your dependencies
$ npm install
Add the express-validator middleware:
app.js
//... imports
var expressValidator = require('express-validator');
var app = exports.app = express();
app.configure(function () {
//... existing settings
// add the express-validator middleware.
// Important: This must come before the app.router middleware
app.use(expressValidator);
app.use(app.router);
app.use(express.static(path.join(__dirname, 'public')));
});
//... additional code
Write a test for the form submission:
test/users/routes.js
'use strict';
// import the moongoose helper utilities
var utils = require('../utils');
var request = require('supertest');
var should = require('should');
var app = require('../../app').app;
describe('Users: routes', function () {
describe('POST /signup', function () {
it('should redirect to "/account" if the form is valid', function (done) {
var post = {
givenName: 'Barrack',
familyName: 'Obama',
email: 'berry@example.com',
password: 'secret'
};
request(app)
.post('/signup')
.send(post)
.expect(302)
.end(function (err, res) {
should.not.exist(err);
// confirm the redirect
res.header.location.should.include('/account');
done();
});
});
});
});
Check the test runner -- you will see:
✖ 1 of 7 tests failed:
1) Users: routes POST /signup should redirect to "/account" if the form is valid:
AssertionError: expected [Error: expected 302 "Moved Temporarily", got 404 "Not Found"] to not exist
Write the code to make the test pass:
user/routes.js
'use strict';
var User = require('./models').User;
// POST /users
exports.signup = function (req, res) {
res.redirect('/account');
};
Now that we have the handler we need to map the POST
/signup
route to it.
Edit your app.js
file:
app.js
//... imports
var users = require('./users/routes');
//... routes
app.post('/signup', users.signup);
Check the test runner -- you will see:
Users: routes
POST /signup
◦ should redirect to "/account" if the form is valid: POST /signup 302 2ms - 58
✓ should redirect to "/account" if the form is valid
✔ 7 tests complete (111 ms)
Write tests to check validation:
test/users/routes.js
//... previous tests
it('should redirect to "/login" if the form is invalid', function (done) {
var post = {
givenName: 'Barrack',
familyName: '',
email: 'fakeemail',
password: 'se'
};
request(app)
.post('/signup')
.send(post)
.expect(302)
.end(function (err, res) {
should.not.exist(err);
// confirm the redirect
res.header.location.should.include('/signup');
done();
});
});
Test should fail:
✖ 1 of 8 tests failed:
1) Users: routes POST /signup should redirect to "/login" if the form is invalid:
AssertionError: expected '//127.0.0.1:3461/account' to include '/signup'
Write code to make it pass:
users/routes.js
exports.signup = function (req, res) {
req.onValidationError(function (msg) {
//Redirect to `/signup` if validation fails
return res.redirect('/signup');
});
req.check('email', 'Please enter a valid email').len(1).isEmail();
req.check('password', 'Please enter a password with a length between 4 and 34 digits').len(4, 34);
req.check('givenName', 'Please enter your first name').len(1);
req.check('familyName', 'Please enter your last name').len(1);
res.redirect('/account');
};
Tests are now passing:
Users: routes
POST /signup
◦ should redirect to "/account" if the form is valid: POST /signup 302 2ms - 58
✓ should redirect to "/account" if the form is valid
◦ should redirect to "/login" if the form is invalid: POST /signup 302 2ms - 57
✓ should redirect to "/login" if the form is invalid
✔ 8 tests complete (186 ms)
Ok, so our application is redirecting correctly, but is it actually creating a user? Let's create a test to check:
test/users/routes.js
//... previous imports
var User = require('../../users/models').User;
describe('Users: routes', function () {
describe('POST /signup', function () {
//... previous tests
it('should create a new User if the form is valid', function (done) {
var post = {
givenName: 'Barrack',
familyName: 'Obama',
email: 'berry@example.com',
password: 'secret'
};
request(app)
.post('/signup')
.send(post)
.expect(302)
.end(function (err, res) {
should.not.exist(err);
User.find(function (err, users) {
users.length.should.equal(1);
var u = users[0];
// Make sure the user values match up.
u.name.givenName.should.equal(post.givenName);
u.name.familyName.should.equal(post.familyName);
u.emails[0].value.should.equal(post.email);
should.exist(u.passwordHash);
done();
});
});
});
});
});
It appears not.
✖ 1 of 9 tests failed:
1) Users: routes POST /signup should create a new User if the form is valid:
AssertionError: expected 0 to equal 1
So lets make the test pass. Update user/routes.js
so that it looks like this:
user/routes.js
'use strict';
var User = require('./models').User;
// POST /signup
exports.signup = function (req, res) {
req.onValidationError(function (msg) {
//Redirect to `/signup` if validation fails
return res.redirect('/signup');
});
req.check('email', 'Please enter a valid email').len(1).isEmail();
req.check('password', 'Please enter a password with a length between 4 and 34 digits').len(4, 34);
req.check('givenName', 'Please enter your first name').len(1);
req.check('familyName', 'Please enter your last name').len(1);
// If the form is valid create a new user
var newUser = {
name: {
givenName: req.body.givenName,
familyName: req.body.familyName
},
emails: [
{
value: req.body.email
}
]
};
// hash password
User.hashPassword(req.body.password, function (err, passwordHash) {
// update attributes
newUser.passwordHash = passwordHash;
// Create new user
User.create(newUser, function (err, user) {
return res.redirect('/account');
});
});
};
Add a passwordHash
String
to our userSchema
:
users/models.js
//... previous code
// define the userSchema
var userSchema = new Schema({
name : {
givenName : String,
familyName : String
},
emails: [emailSchema],
passwordHash: String
});
//... previous code
All test are now passing:
addition
✓ should add 1+1 correctly
◦ should return 2 given the url /add/1/1: GET /add/1/1 200 1ms - 1
✓ should return 2 given the url /add/1/1
Users: models
#create()
✓ should create a new User
#hashPassoword()
✓ should return a hashed password asynchronously
#comparePasswordAndHash()
✓ should return true if password is valid
✓ should return false if password is invalid
Users: routes
POST /signup
◦ should redirect to "/account" if the form is valid: POST /signup 302 4ms - 58
✓ should redirect to "/account" if the form is valid
◦ should redirect to "/login" if the form is invalid: POST /signup 302 3ms - 57
✓ should redirect to "/login" if the form is invalid
◦ should create a new User if the form is valid: POST /signup 302 2ms - 58
✓ should create a new User if the form is valid
✔ 9 tests complete (287 ms)
While our current solution works -- we have a lot of logic the could probably be moved to the model. How would you refactor the signup
function and the userSchema
to transfer that logic?
Hint: mongoose statics