Objectives |
---|
To review and apply sessions to application with one relationship |
To handle routing for muliple resources and pages |
To implement sign up, sign in, and logout features |
To practice sessions and managing resources we will create a book style application, THE LIBRARY APP.
Let's get started setting up our standard application structure.
mkdir library_app;
cd library_app;
touch index.js;
echo {} > package.json;
mkdir -p public/{javascripts,stylesheets,images};
mkdir views;
touch views/home.html
mkdir models;
touch models/{index.js,book.js,user.js};
bower init
bower install --save jquery
You should have some idea of why each of these groups of directories and files are being created. Take a second to discuss it.
library_app/
is our root app dirindex.js
the main application file`.public/
the assets directory for the applicationjavascripts/
,stylesheets/
, etc are all assets.
views
contains all the html files we will send to the client. You can optionally add subfolders here to break up theviews
into different concerns likeviews/users/
,views/books
,views/sessions
, etc.models/
, contains all our application model logic that we use to interact with our DB.bower_components
is an optional directory that we have only because we are usingbower
.
Let's create a basic home.html
page in our views/
views/home.html
<!DOCTYPE html>
<html>
<head>
<title>Lib App</title>
</head>
<body>
<h2>Welcome</h2>
</body>
</html>
Now we should install express
and get a basic application file up and running. Then let's setup our application index.js
to use the home.html
in our root route.
npm install --save express body-parser
index.js
var express = require("express"),
bodyParser = require("body-parser"),
path = require("path");
var app = express();
app.use(bodyParser.urlencoded({extended: true }));
var views = path.join(__dirname, "views");
app.get("/", function (req, res) {
var homePath = path.join(views, "home.html");
res.sendFile(homePath);
});
app.listen(3000, function () {
console.log("Running!");
})
Just check that it's working correctly on localhost:3000/.
Let's create a simple user model.
First install mongoose
.
npm install --save mongoose
Then setup up your user.js
.
models/user.js
var mongoose = require("mongoose");
var userSchema = new mongoose.Schema({
email: {
type: String,
lowercase: true,
required: true,
index: {
unique: true
}
},
passwordDigest: {
type: String,
required: true
},
first_name: {
type: String,
default: ""
},
last_name: {
type: String,
default: ""
}
});
var User = mongoose.model("User", userSchema);
module.exports = User;
see also terminology in mongo
Then setup the models index
file to connect to our DB.
var mongoose = require("mongoose");
mongoose.connect("mongodb://localhost/library_app");
module.exports.User = require("./user");
module.exports.Book = require("./book");
You should then verify that you can create a User
model in the node console.
var db = require("./models")
db.User.create({
email: "foo",
passwordDigest: "foo"
}, function (err, user) {
console.log(user);
});
That should work. Otherwise, you should begin meticulously debugging.
Let's add a statics
method for securely creating a user using bcrypt
to encrypt a new users password.
npm install --save bcrypt
var bcrypt = require("bcrypt");
var confirm = function (pswrd, pswrdCon) {
return pswrd === pswrdCon;
};
userSchema.statics.createSecure = function (params, cb) {
var isConfirmed;
isConfirmed = confirm(params.password, params.password_confirmation);
if (!isConfirmed) {
return cb("Passwords Should Match", null);
}
var that = this;
bcrypt.hash(params.password, 12, function (err, hash) {
params.passwordDigest = hash;
that.create(params, cb);
});
};
Then we want to use the createSecure
static method to add Users
to our application.
NOTE: you could save some backend time here and user frustration by doing validations on the frontend also.
Then we can add a User sign up page.
touch views/signup.html
After we create the signup.html
we should add a form.
signup.html
<form method="POST" action="/users">
<div>
<input type="text" name="user[email]">
</div>
<div>
<input type="password" name="user[password]">
</div>
<div>
<input type="password" name="user[password_confirmation]">
</div>
<button>Sign Up</button>
</form>
NOTE: the method and action match the name of the route we need to create after we finish with this route.
index.js
app.get("/signup", function (req, res) {
var signupPath = path.join(views, "signup.html");
res.sendFile(signupPath);
});
Now we can also make a route to recieve the post from the signup page. However, before we can do that we need to require our ./models
directory.
index.js
var db = require("./models");
index.js
app.post("/users", function (req, res) {
var newUser = req.body.user;
db.User.
createSecure(newUser, function (err, user) {
if (user) {
res.send(user);
} else {
res.redirect("/signup");
}
});
});
When we go to authenticate a User we want to both verify their email and password combination matches our DB and then log them in. That means we are getting close to implementing sessions.
models/user.js
userSchema.statics.authenticate = function (params, cb) {
this.findOne({
email: params.email
},
function (err, user) {
bcrypt.compare(params.password,
user.passwordDigest, function (err, isMatch) {
if (isMatch) {
cb(null, user);
} else {
cb("OOPS", null);
}
})
});
};
We could clean this code up though. Let's make a method on the instance to allow us to check the password.
userSchema.statics.authenticate = function (params, cb) {
this.findOne({
email: params.email
},
function (err, user) {
user.checkPswrd(params.password, cb);
});
};
userSchema.methods.checkPswrd = function(password, cb) {
var user = this;
bcrypt.compare(password,
this.passwordDigest, function (err, isMatch) {
if (isMatch) {
cb(null, user);
} else {
cb("OOPS", null);
}
});
};
- Validate that it works.
Now that we have an authenticate method let's make a login page work.
views/login.html
<form method="POST" action="/login">
<div>
<input type="text" name="user[email]">
</div>
<div>
<input type="password" name="user[password]">
</div>
<button>Sign Up</button>
</form>
And looking at the above method
and action
we can start to guess what we'll be working on next.
index.js
app.get("/login", function (req, res) {
var loginPath = path.join(views, "login.html");
res.sendFile(loginPath);
})
Then as we noted earlier we need a place to submit this form.
index.js
app.post("/login", function (req, res) {
var user = req.body.user;
db.User.
authenticate(user,
function (err, user) {
if (!err) {
res.redirect("/profile");
} else {
res.redirect("/login");
}
})
});
WE DON'T HAVE A PROFILE PAGE YET
app.get("/profile", function (req, res) {
res.send("COMING SOON");
});
Now, there's a problem with everything we've done. WE'VE LEFT OUT SESSIONS. Let's get those started.
npm install --save express-session
Then get it setup quickly.
index.js
// add this to requires
var session = require("express-session");
// somewhere
app.use(session({
secret: "SUPER STUFF",
resave: false,
saveUninitialized: true
}));
Then we want to create our own little middle-ware for express.
index.js
var loginHelpers = function (req, res, next) {
req.login = function (user) {
req.session.userId = user._id;
req.user = user;
return user;
};
req.logout = function () {
req.session.userId = null;
req.user = null;
};
req.currentUser = function (cb) {
var userId = req.session.userId;
db.User.
findOne({
_id: userId
}, cb);
};
// careful to have this
next(); // real important
};
app.use(loginHelpers)
Then we have to go back through our routes to actually login
users.
index.js
app.post("/users", function (req, res) {
var newUser = req.body.user;
db.User.
createSecure(newUser, function (err, user) {
if (user) {
req.login(user); // <--- see here
res.redirect("/profile"); // <--- also here
} else {
res.redirect("/signup");
}
});
});
Let's do the same for the login
.
index.js
app.post("/login", function (req, res) {
var user = req.body.user;
db.User.
authenticate(user,
function (err, user) {
if (!err) {
req.login(user); // <--- login
res.redirect("/profile");
} else {
res.redirect("/login");
}
})
});
If you users can login then they should also have a logout
route.
index.js
app.get("/logout", function (req, res) {
req.logout();
res.redirect("/");
});
However, now we should also make sure our profile
page shows something about our user.
app.get("/profile", function (req, res) {
req.currentUser(function (err, user) {
if (!err) {
res.send(user.email);
} else {
res.redirect("/login");
}
});
});
- Add a real
profile.html
page that users can view.