Learn Nodejs Building 6 Projects PDF
Learn Nodejs Building 6 Projects PDF
BIRMINGHAM - MUMBAI
Learn Node.js by Building 6
Projects
Copyright © 2018 Packt Publishing All rights reserved. No part of this book may be reproduced, stored in
a retrieval system, or transmitted in any form or by any means, without the prior written permission of
the publisher, except in the case of brief quotations embedded in critical articles or reviews.
Every effort has been made in the preparation of this book to ensure the accuracy of the information
presented. However, the information contained in this book is sold without warranty, either express or
implied. Neither the author, nor Packt Publishing or its dealers and distributors, will be held liable for
any damages caused or alleged to have been caused directly or indirectly by this book.
Packt Publishing has endeavored to provide trademark information about all of the companies and
products mentioned in this book by the appropriate use of capitals. However, Packt Publishing cannot
guarantee the accuracy of this information.
ISBN 978-1-78829-363-1
www.packtpub.com
mapt.io
Contributors
About the author
Eduonix Learning Solutions creates and distributes high-
quality technology training content. Our team of industry
professionals have been training manpower for more than a
decade. We aim to teach technology the way it is used in
industry and professional world. We have professional team
of trainers for technologies ranging from Mobility, Web to
Enterprise and Database and Server Administration.
Packt is searching for authors
like you
If you're interested in becoming an author for Packt, please
visit authors.packtpub.com and apply today. We have worked with
thousands of developers and tech professionals, just like you,
to help them share their insight with the global tech
community. You can make a general application, apply for a
specific hot topic that we are recruiting an author for, or
submit your own idea.
Preface
Welcome to Learn Node.js by building 6 Projects.
For this book, you should know HTML and CSS, and you
should have a basic understanding of JavaScript. You should
also a basic understanding of programming fundamentals, so
things like variables, arrays, conditionals, loops, all that good
stuff. If you know that, then that's really going to help you out.
Knowing HTTP requests and RESTful interface is not required
but will definitely help you out if you do have an idea of what
some of that stuff is.Who this book is for If you are a web
developer or a student who wants to learn about Node.js in a
hands-on manner, this book will be perfect for you. A basic
understanding of HTML, JavaScript, and some front-end
programming experience is required.
What this book covers
, A Simple Web Server, will be very basic project. We're
Chapter 1
going to take the sample code from the Node.js website and
expand on that.
Once the file is downloaded, please make sure that you unzip
or extract the folder using the latest version of:
The code bundle for the book is also hosted on GitHub at https:/
/github.com/PacktPublishing/Learn-Node.js-by-Building-6-Projects. We also have
other code bundles from our rich catalog of books and videos
available at https://github.com/PacktPublishing/. Check them out!
Download the color images
We also provide a PDF file that has color images of the
screenshots/diagrams used in this book. You can download it
here: http://www.packtpub.com/sites/default/files/downloads/LearnNodejsbyBuilding
6Projects_ColorImages.pdf.
Conventions used
There are a number of text conventions used throughout this
book.
http.createServer(function(req, res) {
var uri = url.parse(req.url).pathname;
var fileName = path.join(process.cwd(), unescape(uri));
console.log('Loading'+uri);
var stats;
});
$ mkdir css
$ cd css
Bold: Indicates a new term, an important word, or words that
you see onscreen. For example, words in menus or dialog
boxes appear in the text like this. Here is an example: "Select
System info from the Administration panel."
Installing Node.js
A basic website
Installing Node.js
We will set up our environment for our first project. We will
install Node.js. We will also install a tool called Git Bash,
which is a command-line tool for Windows that gives us a
couple of other features that the standard Command Prompt
window doesn't have (this is in no way required; you can use
the standard Windows Command Prompt or Terminal in
Linux or macOS or whatever you'd like to use).
2. We will put the Program Files folder and leave the default
values as they are. I actually will choose the last option;
this will allow us to use some extra Unix tools from
Windows Command Prompt:
We'll bring it down into our taskbar so that we can now access
it from there. Let's look at another cool thing that this does; if
we want to create a folder called Projects in our C: drive, and if
we want to open the command line in this folder, all we have
to do is to right-click and click on Git Bash from the context
menus we get. This will open up the command line for us in
that folder! Also, we can use commands such as ls and other
Unix-based commands.
Now, just to make sure that Node was installed, we can enter
node -v, which will tell us our version number; also let's try npm -v
to check:
We'll get into npm later; it stands for node package modules, and it's
basically just a package manager so that we can add and remove
plugins or modules to and from our application. This will be it for
now. In the next section, we'll look into npm more and also look at a
basic HTTP server using Node.js.
Introduction to NPM and a
basic HTTP server
Now that we have Node.js installed on our system, we will take
a look at some very simple code, and we'll also will talk a little
bit about npm and what that is.
1. The first line that we have is const http; here, we're just
defining a variable called http, and we're defining this
variable as a constant because it not will change.
2. We're then setting the variable to require('http') to include
the http module. Whenever you see require, remember
that it's either including a module or another file.
3. Next, we have the hostname and port constants (our hostname
will be our localhost), and then, port is 1337.
4. We'll take that http module and use the createServer method
or the createServer function; this takes in a request (req) and
response (res) parameter. Also, we'll use the writeHead
method of the response and give it a status of 200, which
means that everything is okay and we're also setting the
to text/plain. Then, we will end the response and
Content-Type
So, this piece of code is extremely simple and we'll actually run
it and see how it works. Before we do this, I just want to go
over to npmjs.com and show you this website:
This is basically a repository for all of the Node package
modules. You have modules that are extremely simple all the
way up to a module such as Express, which is an entire HTTP
server and framework (which we'll get into later on). You can
install these modules from your command line if you have
Node installed; you also have npm, which we'll get into a little
later.
To create this file, we'll run the npm init command, which will
initialize a series of questions. For the app name, whatever is
in parentheses is the default, since this is fine, we'll keep it;
version 1.0.0 is good. For description, we'll just say Very simple
server; for entry point, you could call this what you want; I like
Now, you can see that we have the package.json file. Let's open up
this file and take a look at it. I'm using Sublime Text, but of
course, you can use whatever editor you want. You can see
that we have all that information, such as name and version, all of
that:
Now if we were using something such as Express or some
other kind of Node module, we'd have to put that in our
dependencies here; however, we'll get into that a little later.
Since we have our package.json file, let's now create the server.js
file and we will open up that up; I'll just make Sublime my
default application, real quick. Now, we'll go to that About page
and just copy the following block of code:
We'll just paste this code in the server.js file, save it, and go back
into our command line. We'll type node and then the name of
the file; you can type either server or server.js:
node server.js
We'll bring in these modules. Now, we will set the type of files
that we want to be able to serve. So, we will use a constant, say
const mimeTypes, and this will be set to an object. We'll say html, so
this will be text/html; we'll use jpeg, which will be image/jpeg; also,
for jpg, we'll say image/jpg followed by image/png for png. We want to
be able to serve JavaScript files, so js, which will be
text/javascript, and finally, css, which will be text/css:
const mimeTypes = {
"html": "text/html",
"jpeg": "image/jpeg",
"jpg": "image/jpg",
"png": "image/png"
"js": "text/javascript",
"css": "text/css"
};
Next, we'll use the createServer function that we used before, so
we'll enter http.createServer. Then, inside this, we will have function,
which will take in a request and response, and in here, we will
set a variable called uri—we will use the url module for this;
we'll also enter url.parse.
http.createServer(function(req, res) {
var uri = url.parse(req.url).pathname;
var fileName = path.join(process.cwd(), unescape(uri));
console.log('Loading'+uri);
var stats;
});
Now, we'll check for the file that is entered. For instance, let's
go to the browser, and inside the address bar, let's enter
localhost:1337/page.html. We need to make sure that the page.html is
actually there. So we will go right under the stats variable and
do a try catch block. We'll enter try and catch and pass in an e.
Here, we want to take that stats variable and set that to fs,
which is the filesystem module, and then we want lstatSync and
we can just pass in fileName. Now in the catch block, we will add a
code that we want to send a 404 error if this doesn't work. So
we'll say res.writeHead, and then the status we want is 404; I will
send along Content-type. So Content-type actually has to be in quotes,
and that will be text/plain. Then, we will enter res.write; I'll just
put in some text here, such as 404 Not Found. Then we will say
res.end and then, return:
try{
stats = fs.lstatSync(fileName);
} catch(e) {
res.writeHead(404, {'Content-type': 'text/plain'});
res.write('404 Not Found\n');
res.end();
return;
}
So if it doesn't find the file we'll just send a 404. Then, down
below, we'll say if(stats.isFile()); so if there is a file, then let's set
a variable called mimeType to mimeTypes[], which is our array. In
there, we will say path.extname(fileName) and .split.
if(stats.isFile()){
var mimeType = mimeType[path.extname(fileName).split(".").reverse()[0]];
res.writeHead(200, {'Content-type': mimeType});
var fileStream = fs.createReadStream(fileName);
fileStream.pipe(res);
}
Now, we'll not have to do all this stuff here in other projects
because we'll use something that does all this on its own;
something like Express, which is a framework that also has an
underlying HTTP server. This is just an example to show you
what goes on under the hood of an HTTP server. So if any of
this is a little confusing don't worry about it; chances are that
you won't have to do this.
That's it! At the end, we will enter .listen and listen on port 1337:
if(stats.isFile()){
var mimeType = mimeType[path.extname(fileName).split(".").reverse()[0]];
res.writeHead(200, {'Content-type': mimeType});
var fileStream = fs.createReadStream(fileName);
fileStream.pipe(res);
} else if(stats.isDirectory()){
res.writeHead(302, {
'Location': 'index.html'
});
res.end();
} else {
res.writeHead(500, {'Content-type':'text/plain'});
res.write('500 Internal Error\n');
res.end();
}
}).listen(1337);
So this is a web server. This will serve HTML pages as long as
images and whatever else we have as the mimeTypes array or
object. Let's save it and restart the server. To do this, let's go to
our command line and do a Ctrl + C to stop it. Then, let's enter
npm start and see what happens. Remember that we don't have
Inside the browser, we'll get a 404 Not Found because we're
looking for index.html. So let's create an index.html file. We'll open
this up with the Sublime Text editor and just put in some tags
there. In body, we'll just put in an h1 tag to make sure that it
parses HTML correctly:
<!DOCTYPE html>
<html>
<head>
<title>Test Page</title>
</head>
<body>
<h1>Testing</h1>
</body>
</html>
Let's save this and reload. We'll get Testing, and now it's
reading our HTML page. Let's say we create another file,
page.html, and open it up. I will copy the code from the index.html
file and paste it inside the page.html file. I'll say Testing Page 2 and
save it. Now, if we change this to page.html, it will load up.
A basic website
Now we'll just make the website look a little better and create
a very simple website using our server. You can see in the
command line here as we'll run it; visiting pages will tell us
what pages are loading:
We will now rename the page.html file; we'll call it about.html. Then
we'll create another one and call this one services.html. Now if we
wanted to use something like Bootstrap, we can do that.
A basic website using
Bootstrap
Let's go to getbootstrap.com and then to Examples. We will be using
Bootstrap quite a bit through this section:
Here, this is just a sample Bootstrap template. I'll grab the code
from navbar down to the container div. We'll put the code in body of
index.html. We have our Home page and the link to it will be
index.html, the link to the About page will be about.html, and for Contacts
it will be services.html. We'll change Contacts to Services:
<ul class = "nav navbar-nav">
<li class = "active"><a href="index.html">Home</a>
</li>
<li><a href = "about.html">About</a></li>
<li><a href = "services.html">Services</a></li>
</ul>
Then, down below, we will get rid of the starter-template div and
put in a div tag with the class of row. Let's enter h1 and then,
Welcome. Next, we'll just put a paragraph and say This is the welcome
page; we'll change the title to Welcome. We will need to include
Bootstrap, so we'll download it. We'll then take the CSS folder
from the downloaded Bootstrap files and put it into our file
structure. We will create a folder called css, bring bootstrap.css
inside the folder we created, and then, inside head, we'll enter
link; this will go to css/bootstrap.css:
<head>
<title>Welcome</title>
<link rel="stylesheet" href="/css/bootstrap.css">
</head>
Inside the browser, let's go back to our index.html. We will get rid
of the class navbar-fixed-top and save, to push the navbar down:
This is our Home page.
name, we'll just say MyWebsite. We'll put this for the Home page too.
Understanding Express
Adding Layouts
Understanding Express
Express is a full web framework and also a web server. We'll
build a very, very simple site for computer repair and we'll
focus on routing, parsing data using the Pug template engine,
and also using a module called Nodemailer, which gives us a
contact form:
So this is the Home page, and then we have an About page and also a
Contact form that works using Nodemailer:
It is a very simple application but it's a nice introductory to Express,
which we'll be using throughout the book. So lets get started!
Installing Express
In this project we're going to be building a website using the
Express framework. Express is a Node.js module that gives us
a full framework, it gives us a routing system and an HTTP
server.
To install it, it's really simple we just need to say npm install express
although we're going to add the -g flag because we want to
install it globally so that we can access it from anywhere. From
the frontend this website is going to be very, very simple, it'll
just have a Home page, an About page, and we'll also use a module
called Nodemailer that will allow us to create a Contact form. On
the backend we'll be using Node.js with Express, and we'll be
using Express in quite a few projects in this book. We'll also
use Pug, which is a template engine that works really well with
Express.
So let's get started. Now I'll open up the Projects folder, and
create a new folder called express_website. I'll then open up my Git
Bash tool in too there and, of course, you can use Windows
Command Prompt, macOS, or Linux whatever you'd like:
We'll also need our package.json file, we can do that with npm
init. Now, we'll just answer these questions. You can put
3. So let's open that up. This time in this project, we'll need
a few dependencies including Express. So let's put a
comma after "license": "ISC", and we'll say dependencies:
4. We'll need body-parser that will help us parse different
types of data including form data. Now if we don't
know the latest version or the version we want, we can
use an asterisk (*) and that'll just say we want the latest
version. We also want express, we want pug, and also
nodemailer. I'm pretty sure that's it, but if we need more
"dependencies": {
"body-parser": "*",
"express": "*",
"pug": "*",
"nodemailer": "*"
}
This will get all that stuff installed so now lets go and
look at the folder.
1. We'll say var app = express and set our bodyParser middleware.
So we will use app.use and this will be bodyParser.json.
2. We want to be able to parse json if we want, and then we
want app.use(bodyParser.urlencoded). We then just want an
object with some options, so we add extended to it and
we'll set that to false:
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({extended: false}));
app.listen(3000);
console.log('Server is running on port 3000');
5. Let's go to localhost port 3000. You can see that now we get
the error Cannot GET /:
The reason for this is that we haven't created a route for
/, which is the Home page. So that's not going to work in the
browser. Let us go ahead and create a route.
6. We'll write app.get and then inside it, the route is going to
be just /, which will call a function. This function will get a
request and response object followed by console.log('Hello
World'):
9. We'll restart node app and now we get Hello World on the
client side or the browser:
We also need to set the view engine, so I'll use app.set('view engine')
and then the second parameter will be pug. Alright, now instead
of typing in res.send, we'll type in res.render and we'll render the
index view:
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({extended: false}));
We'll save this, go back to our folder, and create a new folder
called views. In there, we'll create a document; we'll call this
index.pug. We'll open that up and all we'll put in is an h3 tag and
we'll say Hello World:
h3 Hello World
Save that, we'll restart the server, and now you can see that if
we do a Ctrl + U and look at the source code, it's an h3; so we
know that it's coming from here:
So in the views folder we'll create a new document and call this
layout.pug. Let's open that up, grab the code right from the Basics
doctype html
html
head
title my jade template
body
h1 Hello World
extends layout
block content
h3 Hello World
This is what you should now see when you run your browser:
So now you can see we're getting the Hello World, which is in
our index, but if you look at the tab it says my jade template, that's
what's in the layout title. If we look at the source code you can
see we now have our doctype, we have our html, head, and body tags.
So we have this layout that we can wrap around any view we
want as long as we just include these two things here.
Using Bootstrap - Jumbotron
Now I would like to use Bootstrap for our layout, so I'll go to get
bootstrap.com and go to Examples and then Jumbotron:
Creating the Home page view
1. Let's do a Ctrl + U inside the Jumbotron page. Let's grab
all the code, and then let's open up another new file just
to paste this in for now so we can edit it. There will be a
lot of unnecessary code lines. We will have to get ride of
them. Also, for convenience, we'll use a Jade converter
that will give us the Pug code back once we paste the
HTML in.
2. Now, I'm going to do is just get rid of a bunch of the meta
and link tags that we don't need. We don't even need the
lang attribute. Now for the stylesheet, let's look at our
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({extended: false}));
app.use(express.static(path.join(__dirname, 'public')));
4. We'll save that and let's go back to our HTML. So for
this, I'll have just the css/bootstrap.css. Then we can get rid
of the rest of this stuff in the head tag:
5. Then for the navbar, let's change the project name and
let's say this is a computer repair shop, so we'll just type
in PC Repairs:
8. We can now get rid of the Jumbotron comment and I'll get
rid of all the JavaScript stuff present at the end of the
file as well. So now we have a very simple template. So
I'll just grab all of the code from this file and navigate to
html2jade.org:
9. We'll paste this over and it's going to give us the Pug
code. So we'll copy that and then let's go to our layout.pug
file. We'll just replace this, save, and check out our app:
So what's going on here is that it's not seeing the CSS file
if we do Ctrl + U, and you can see it's looking
css/bootstrap.css. If we try to open that, we get Cannot GET
/css/bootstrap.css:
This is wrong and I know why, because we didn't restart
the server. so let's do that:
block content
hr
footer.container
p(class='text-center') © Company 2017-2018
// /container
Remember using spaces for indents not tabs. I will have this
block content to be on the same level as the nav. Then I'll bring the
hr and the footer along the block content level. So now we have our
footer.
block content
h1 About Us
Then we'll just put h1 and we'll say About Us. We'll save that. We
then go to layout.pug and get rid of the navbar-fixed-top class. There
we go, so we get About Us:
block content
h1 About Us
p
| This is the about page.
We'll save, and now we get the about page:
Alright, so we have our Home page and our About page. We'll work on
the Contact page in the next section because we'll implement
Nodemailer, which is a module that will allow us to have a contact
form.
The Nodemailer contact form
We need to create our Contact form now!
2. Let's save that and then inside the views folder, we'll
create a new file and save it as contact.pug.
extends layout.pug
block content
.container
form(method='post', action='contact/send')
h1 Contact
.form-group
label Name
input.form-control(type='text')
extends layout.pug
block content
.container
form(method='post', action='contact/send')
h1 Contact
.form-group
label Name
input.form-control(type='text')
.form-group
label Name
input.form-control(type='email')
we'll just have the text Submit. The code should now look
like the following:
extends layout.pug
block content
.container
form(method='post', action='contact/send')
h1 Contact
.form-group
label Name
input.form-control(type='text', name='name',
placeholder='Enter Name')
.form-group
label Email
input.form-control(type='email', name='email',
placeholder='Enter Email')
.form-group
label Message
textarea.form-control(name='message',
placeholder='Enter Message')
button.btn.btn-default(type='submit')
Submit
We'll then just restart the server real quick. We'll be logging it,
so let's reload the page and Submit, and you can see we get Test:
Then we need to specify where we want the email to go. I'll use
another email address of mine. We now want to specify a
subject, OK we'll say Website Submission. Next thing is going to be the
plain text version. We'll just say You have a submission with the following
details.... Let's just say Name and then we need to concatenate the
submitted name. We can get that with req.body.name. We'll then
type in Email, and then use req.body.email and Message that will say
req.body.message with a comma.
var mailOptions = {
from: 'Gary Greig <garyngreig@gmail.com>',
to: 'gauravg@packtpub.com',
subject: 'Website Submission',
text: 'You have a submission with the following details... Name: '+req.body.name+'Email: '+req.bo
html: '<p>You have a submission with the following details...</p><ul>
transporter.sendMail(mailOptions, function()){}
It's going to take error and info. Then we're going to test for an
error. I'll use if(error) and then use console.log(error). We'll then
redirect we can do that with res.redirect and we want to go to the
Home page, which will be a /. We also want an else on this, so let's
just console.log and use console.log('Message Sent'). Let's then
concatenate info.response. Then finally we want to redirect to the
Home page so we'll copy res.redirect:
Now what we're going to do is Access for less secure apps, I'm
going to Turn on:
Gmail isn't the only service you can use with Nodemailer you can basically
use any SMTP server.
Let's go back to our application and I'm going to just restart the
server.
We'll go to the Contact page and let's go ahead and just enter John
Doe as Name, Test message field as Message:
Alright, so let's submit and we get redirected and you can see
Message Sent:
Let me just give you a quick idea. First we'll register for an
account, say email will be harry@gmail.com, username will be harry,
and you can also upload an avatar. Register and it'll say we're
registered, and we can log in. Let's log in, and it'll redirect us to
the Members Area. We'll not do anything further than this
because it's just the authentication system. You also see we
have a Logout button; if I click on that it logs us out. That's
what we'll be doing, pretty simple but flexible in terms of
using it with with another application.
cd mongodb
We can see we have the bin, data, and log folders. Now we
want to go into the bin folder so cd bin, and what we want
to do is we want to enter mongod and add a bunch of
different flags. This is because we need to tell it where
the data folder is and where the log file is going to be.
9. So, the first flag will be --directoryperdb and then we'll type
in --dbpath. That's going to be in our C:\mongodb\data\db. The
next thing will be the --logpath flag and this is going to be
in our C:\mongodb\log, and then the name of it will be
mongodb.log. Then we will specify --logappend and the reason
10. Let's run the command. Now we want to try to run the
service, so we'll enter net start MongoDB:
It says The MongoDB service was started successfully.
Now the reason you don't see customers or test is because there's
nothing in it. If there's nothing in it, it's not going to show it. So
what we want to do is create a collection. We'll say
db.createCollection and we'll put the collection name in collection;
db.createCollection('customer');
And it gives us this {"ok" : 1} result. Now if we say show dbs, you
can see we have a customers database:
We named the database and the collection customers, which we
probably shouldn't have done but it's fine. We can also use a
command called show collections and you can see we have one
collection called customers:
Data fetching from the shell
So let's create a new database:
1. To do that we can simply use use and then name it. I'm
going to call this nodeauth:
use nodeauth
db.createCollection('users');
6. So let's add one more. I'll change some stuff. For username,
we'll say john and change email and name:
db.users.find().pretty()
Now you'll notice that we have this _id and we didn't create
that. MongoDB creates ObjectId for us automatically, which is
really nice because it is unique. So we can use it in our
application as a primary key.
Create, read, update, and
delete using MongoDB
The next thing I'll show you is how we can do an update. So to
do that, we'll say db.users.update and then we'll pass in the first
parameter, which will go inside the curly braces, and let's use
username. Our username equals john. We'll put the next parameter in
curly braces and we'll say $set and then that will have its own
set of curly braces. We will change that to, let's say email. So
we'll say email and let's change it to jdoe@yahoo.com. Press Enter.
Now if we go and we say find, you will see that John Doe has a
Yahoo email: db.users.update({username: 'john'},{$set:
{email:'jdoe@yahoo.com'}});
And using that set you want to use that $set because if you don't
then it's just going to change the email. but it'll get rid of
everything else, and you don't want that usually. So that's how
we can update. Now let's go ahead and let's add one more.
I'm going to change username to mike and I'm going to keep that as
it is because what I'm going to do is show you how to remove a
record: db.users.insert({name: 'John Doe',
email:'jdoe@gmail.com', username: 'mike',
password:'1234'});
If we use find, you can see the name is still is John Doe but the user
name is mike:
So what we want to do is delete mike. To do that, we'll say
db.users.remove and then we'll pass where username is equal to mike:
db.users.remove({username:'mike'});
If we use find again, you can now see that mike is gone:
So that's how we can do create, read, update, and delete using
MongoDB. We're going to keep this database as is because
we'll be using it in our project.
App and middleware setup
In this section, we'll create our application. We'll be using
Express and we'll use something called the Express generator,
which will allow us to just type in a command and have it
generate a bunch of files and folders for us that we can use in
our app.
Let's create a folder, you can create it wherever you want, and
mine's going to be in my Projects folder. We'll call it nodeauth. Now
inside the folder, I'll open my command line in that folder
using Git Bash. So you want to make sure you have Express
installed globally so you can access it from anywhere and also
the Express generator. So we'll run the following command:
install -g express-generator
Now while we're in the nodeauth folder, let's just use express. It will
create a bunch of stuff for us. It gave us a bin folder, public, routes,
views, app.js, and our package.json. What we'll do is open up the
package.json file and I'll add this project to my editor. Inside
package.json you can see that it's actually added a bunch of
variables routes and users, and we're setting those to the index file
inside of the routes folder, and then the users file inside the routes
folder:
Now in the last project, we put our routes directly in the app.js
file. We had app.get, then the route, and then a function with
whatever we want to happen when we hit that route. In this
case, we have them in their own separate files, so you can see
in our index.js file, we have router.get and you know we have the
rest of the route. Now when doing this, we need to have the
module.exports = router line. That's what's going to allow us to access
it from a different file; in this case, the routes variable. If we
don't have the file, it's not going to work.
we have to make sure we use app.use. That way, you know the
route and then the name of the variable that holds the file.
That's what we don't really need to care about. Now there is a
bit of middleware we have to add; for instance, to use multer for
file uploads, let's say Handle File uploads, what we need to do is use
app.use(multer) and then that's going to be a function. In there, we
I'll put that right inside of app.js. It's quite a bit of middleware that
needs to be configured just because we'll be so many different
modules. So let's try to save and run the server.
We'll say npm start. Let's see, we have an error here, app.use requires
middleware. So we include multer. Let's say var upload = multer
and then we can put in our destination. Let's put the validator right
under passport. It looks like it's running. Let's go to localhost3000
and we get Express:
All this stuff will go in the public folder. We will put bootstrap.css in
the stylesheets folder, in the javascripts folder is where we'll put
bootstrap.js. Let's go back to our views folder and you can see we
have layout file, if we open that up. Basically, the generator gave
us these templates and it just has html, head, body tags, and things
like that:
doctype html
html
head
title= title
link(rel='stylesheet', href='/stylesheets/style.css')
body
block content
It includes the style.css sheet in the public folder. Now for the
layouts, in your project files, you'll have a file called _layout.pub
and that's going to include just a very basic boilerplate for a
Bootstrap page. This is basically just the navbar and the content
area. We have our doctype html, the head area. We'll Bootstrap
along with style.css. In our body, we have a navbar, we have the
unordered list right here with the list items, then we have a
container div and inside that container we have our content, and then
at the bottom we'll include jquery and our bootstrap.js:
.container
block content
script(src='https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js')
script(src='javascripts/bootstrap.js')
Let's save that and check it out. Actually that doesn't look right,
as the navbar should go all the way across. I think, the
problem is in the style.css file. So let's go ahead and open that
up, that's in public/stylesheets. We see that we have a padding: 50px
for the body. We don't want that, so let's just get rid of all that.
And there we go:
We have our navbar. Let's go ahead and change the project
name to NodeAuth, and for the link present, we'll say Register. That
will go to /users/register. The other one will be Login, so that one's
going to go to users/login:
a.navbar-brand(href='#') NodeAuth
.navbar-collapse.collapse
ul.nav.navbar-nav
li
a(href='/') Home
li
a(href='/users/register') Register
li
a(href='/users/login') Login
Let's also add a Logout. I want to put Logout on the right-hand side.
So let's go on the same level as ul, and we'll say ul.nav.navbar-nav
and with navbar-right:
ul.nav.navbar-nav.navbar-right
We'll put a space and paste the Login code, change this to Logout,
and there we go:
li
a(href='/users/login') Logout
extends layout
block content
h1 Members Area
p Welcome to the members area
We can actually change the home link. I'll change that to Members:
li
a(href='/') Members
li
extends layout
block content
h2.page-header Register
p Please register using the form below
Let's actually just make sure that this will load if we go to our
users/register. Let's restart the server:
We're getting it but for some reason the CSS isn't showing:
If we look at the source code, we have stylesheets/bootstrap and
that's not found. I think we just need to add a / in front of that.
Let's go to our layout.pug file and put a / before both our stylesheets:
link(href='/stylesheets/bootstrap.css', rel='stylesheet')
link(href='/stylesheets/style.css', rel='stylesheet')
extends layout
block content
h2.page-header Register
p Please register using the form below
form(method='post', action='/users/register', enctype='multipart/form-data')
.form-group
label Name
input.form-control(name='name', type='text', placeholder='Enter Name')
Let's give it a type of text. Then we'll give it a placeholder. We'll copy
the .form-group code, because we'll need a couple more of those.
After name will be email, so this will be name='email' and the type will
also be email. The next thing will be the username, that's going to be
text, and then we need a password. Don't forget to change the
labels as you type the code! We'll want the confirm password
field, so I'm going to copy this and we'll call this password2:
.form-group
label Email
input.form-control(name='email', type='email', placeholder='Enter Username')
.form-group
label Username
input.form-control(name='username', type='text', placeholder='Enter Email')
.form-group
label Password
input.form-control(name='password', type='password', placeholder='Enter Password')
.form-group
label Password
input.form-control(name='password2', type='password', placeholder='Confirm Password')
Finally, we want our profile image. So for this label, we'll say
Profile Image. Then we'll have our form control; the name will be
profileimage, type would be file, and we don't want a placeholder.
Alright, so those are the fields, now we'll also need a button so
let's go right in the code, and this will be input type we'll say submit
and we need to put this in parentheses. I also want to give it a
class, so we'll say .btn and .btn-primary. That'll make the button
blue. Then we need the type submit. Let's also give it a name of
submit and we also need a value, we'll say Register:
.form-group
label Profile Image
input.form-control(name='profileimage', type='file')
input.btn.btn-primary(type='submit', name='submit', value='Register')
extends layout
block content
h2.page-header Login
p Please login below
form(method='post', action='/users/login')
label Username
input.form-control(name='username', type='text', placeholder='Enter Email')
.form-group
label Password
input.form-control(name='password', type='password', placeholder='Enter Password')
input.btn.btn-primary(type='submit', name='submit', value='Login')
But we'll create a new view and let's save this as login.pub. We'll
paste the preceding code completely and we'll change a couple
of things: we'll use Login and say Please login below. It will go to
user/login and we don't need the enctype attribute. All we want is
the Username and Password, so we'll get rid of the rest and we'll
change Register to Login. Alright, so let's save that and then we
need to go back to our users.route file and restart the server:
module.exports = router;
module.exports = router;
Now if we go to our layout file where our links are, we can test
for those. So in the li tag, let's add parentheses and say class=.
Let's then add another set of parentheses and say title is equal
to Members, then the class will be active. If not, we can do that with
a colon; this is just a shorthand if statement . If not, then the
class will be nothing. Alright, so we want to just grab the li tag
and paste that again:
ul.nav.navbar-nav
li(class=(title == 'Members' ? 'active' : ''))
a(href='/') Members
li(class=(title == 'Register' ? 'active' : ''))
a(href='/users/register') Register
li(class=(title == 'Login' ? 'active' : ''))
a(href='/users/login') Login
For one of them we'll change to Register, and for the other one
we'll change to Login. Restart the server and you can see
Members is highlighted. If I click on Register, that gets
highlighted, and the same thing with Login:
So our views and layouts are all set up. In the next section,
we're going to take a look at registration and we'll create that
functionality.
The register form and
validation
Now that we have our views and layouts all set, we want to be
able to register a user. So we have our form and if we open up
routes/users, you can see we have a couple of requests but we
});
We'll save that. Make sure you restart, reload, and inside the
Name we'll just say Test. Let's submit and you can see we get
test. Now what I want to do when we submit is I want to put all
those values into their own variable.
For instance, let's say var name, I'll set that to req.body.name. We'll do
that to all of them, so I'll copy this line. The first one here will
be email, the next one will be username, then password, and then
password2. Now password2 isn't being submitted into the database;
we'll use it for validation, which we'll get to in a second:
This takes care of the text fields now, and now we have our
image field to take care of. So I will do kind of a little test and
say console.log and it should be under a req.file. If it was an array,
instead of using single like we are using here we would use
req.files. Alright, but let's just try that out:
console.log(req.file);
});
if(req.file) {
console.log('Uploading file...');
} else {
console.log('No File Uploaded...');
}
Let's try this out. If we upload it, we get Uploading File.... Now if
we go back and we don't choose to upload, we get No File
Uploaded.... Alright so that's what we want!
if(req.file) {
console.log('Uploading file...');
var profileimage = req.file.filename;
} else {
console.log('No File Uploaded...');
var profileimage = 'noimage.jpg';
}
// Form Validator
req.checkBody('name', 'Name field is required').notEmpty();
// Check Errors
var errors = req.validationErrors();
if(errors){
console.log('Errors');
} else {
console.log('No Errors');
}
});
We'll go to the Register page and let's just click on it. We can see
it says Errors. If I put a name in there and submit, we get No Errors.
Alright, so we know that's working.
// Form Validator
req.checkBody('name', 'Name field is required').notEmpty();
req.checkBody('email', 'Email field is required').notEmpty();
req.checkBody('email', 'Email is valid').notEmail();
req.checkBody('username', 'Username field is required').notEmpty();
req.checkBody('password', 'Passwordfield is required').notEmpty();
req.checkBody('password2', 'Passwords do not match').equals(req.body.password);
if(errors){
res.render('register', {
errors: errors
});
} else {
console.log('No Errors');
}
We'll render the register and pass along errors. So let's save
that and now let's restart. So we're at the Register page and it's
just re-rendering this for us, but what we want is we want it to
not only re-render, but also see what's the problem and what
we need to do.
Alright, so we'll save that, and there we go, it's giving us all of
our errors. Actually you know what let's not use a list item,
let's get rid of ul and then here we will just have a div. All this
needs to be indented. Alright, so Register, there we go. So if I
put a name in, Name Field is required doesn't show up
anymore. Let's just see if we can do a successful submission.
So we know that the validation is working.
Models and user registration
In the last section we created our Register form, where we have
some form validation and we could upload an image. Now we
still can't add a user to the database because we haven't added
our schema yet. So when you're working with Mongoose,
which is an ORM module for MongoDB, you need to create
what's called a schema and also a model; the schema is going
to be inside a model.
mongoose.connect('mongodb://localhost/nodeauth');
var db = mongoose.connection;
// User Schema
var UserSchema = mongoose.Schema({
username: {
type: String,
index: true
},
password: {
type: String
},
email: {
type: String
},
name: {
type: String
},
profileimage: {
type: String
}
});
We'll bring in Mongoose and then connect using mongoose.connect.
We put the URL of our database, so this will be mongodb://localhost,
followed by what you called it (I called mine nodeauth) and then
we need a variable that's going to hold the connection, so
mongoose.connection. We'll now define our user schema. So we'll
String. Next up will be email with name and type will be String. And
lastly, profileimage. So they're all strings. That's the schema:
if(errors){
res.render('register', {
errors: errors
});
} else {
var newUser = new User({
name: name;
email: email,
username: username,
password: password,
profileimage: profileimage
});
That will go ahead and create the new user. Then we want to
redirect to a location. So I'll put res.location and set that to just
the Home page. We also want to put res.redirect to redirect to the
Home page. Let's save it and see what happens:
res.location('/');
res.redirect('/');
Let's restart the server and go to Register. Let's say the name's
Mike Smith, we'll put the username as mike, enter the password
(password is not going to be encrypted yet), and for the image
we'll grab that balloons image. So let's see what happens; we
got redirected. Now if we want to check this out and actually
see if Mike was entered, we have to go to our command line
and go to our Mongo shell.
.container
!=messages()
block content
Let's save that and restart. We'll go to Register and put the name,
Tom Williams. You can then enter Email, Username as tom,
Password, put the Profile Image, and click on Register:
You can see we have a message, You are now registered and
can login. Now we can change the look of this a little bit. If we
go to our little element highlighter, we'll see that we have a <ul
class = "success"> and then an li. We'll go into our CSS; let's go to
public, stylesheets, style.css, and we'll put ul.success li and paste some
stuff in here as shown in the following code snippet—some
padding and margin, border, and then also some color:
ul.success li{
padding: 15px;
margin-bottom: 20px;
border: 1px solid transparent;
border-radius: 4px;
color: #3c763d;
background-color: #dff0d8;
border-color: #d6e9c6;
list-style: none;
}
Now for the error, we'll say ul.error and change the color, and
we'll leave that stuff:
ul.error li{
padding: 15px;
margin-bottom: 20px;
border: 1px solid transparent;
border-radius: 4px;
color: #a94442;
background-color: #f2dede;
border-color: #ebccd1;
list-style: none;
}
This will make it red. Let's save that. Now if we go and try
again, there we go! We get a nice looking alert:
So we now can interact with our database and can add data. In
the next section, we're going to get into logging in and
authentication and also password encryption.
Password hashing with bcrypt
In this section, we're going to learn to encrypt passwords
because right now we're storing passwords as plain text and
that is a horrible idea. So there are a few different things that
we can use to do it. We're going to use bcrypt, more
specifically we're going to use bcrypt.js. You could use bcrypt
but there's a ton of dependencies you need to have; for
instance, Visual Studio, OpenSSL, and a bunch of other stuff:
So this you can see is bcrypt in plain JavaScript with zero
dependencies. It gives you some of the code examples down as well.
We'll go ahead and install it.
Installing bcrypt
I'll stop the app from running and we'll run npm install bcryptjs
and save that in our package.json file. So just to check, if we go to
node_modules, you can see we have bcryptjs right there:
So let's open up app.js. We'll enter the bcrypt variable and we'll
require bcryptjs just like that:
Make sure that's saved and then we'll go to our users route, and
I'll copy what we just did and put that in the model as well
that's in models/users.js. Now down in createUser is where we'll
implement this. If we look at the documentation, as far as
usage is concerned you can do this synchronously or
asynchronously. In case of synchronous it's going to do line by
line, one at a time, which is going to be a little slower than
asynchronous; so we'll use asynchronous, which means we
have to use it through callbacks:
Now let's copy the preceding code from the documentation.
We'll go right into our createUser function and paste that in. Now
instead of the hash function, we'll replace it with newUser.password
because that's what we want to hash, and newUser will come from
the following code:
Next, we'll take this, cut it, and put that right in user.js, and the
hash is actually going to be passed in right here. So we put
newUser.password is going to equal hash. It will get saved. So it's just
hashing the password before it gets saved. Let's see if this
works. Make sure you save it. Let's go to our Git Bash console
and run npm start.
We'll open our application and go to Register. This will say Paul
Smith, Email, Username. We'll say paul, password as 12346. Choose
the profile image file; actually he doesn't need a profile image,
so let's register. That went good, and you can see in here
password is this:
Now have a safe and secure hashed password.
Passport login authentication
In the last section, we went ahead and hashed our passwords
using bcrypt. We'll now get into our login functionality. We'll
use Passport with it, which is an authentication module for
Node.js. What's great about Passport is that it's highly
customizable; it just gives you a simple layer that sits on top of
your application and you can kind of do what you want with
it. You can use the LocalStrategy, which is what we'll be doing.
This means we'll have a username, password, and a local
database. But you can also use things like Facebook login,
Twitter login, and a bunch of other types of logins:
Let's go to the Documentation page and then go to
Authenticate. This is going to show us that we need a post
route to our login and we also need to include this
passport.authenticate:
That will check for the username. After that, we'll call another
function that we'll create called comparePassword. In here, we'll pass
in password and the actual password, which will be user.password,
and then our callback. This will take in error and also isMatch, to
see if the password matched any. So in here, we'll check for
the error. If there is an error, then let's use return done and pass
in the error. Now we get a check for isMatch. So if if(isMatch), we'll
use return done(null, user). This is also going to have an else; that is,
if there isn't a match, then too we'll use return done, but we'll pass
in null, false, and then a message:
password. If it does match, then we'll use return done and pass
along the user; if it's not a match, we'll use return done and pass
along false. Let's save that.
Now let's take a look at the login view. Open up login.pub and
you can see that this is going to users/login. We now need to
create some functions here. We need to getUserById and
comparePassword. We'll put that inside the model. So let's go down to
the bottom and let's put getUserById; since we're using these
outside the file, we'll use module.exports and it will be getUserById.
This will take in an id and callback, and then we'll put user.findById
and pass in id and callback:
Notice that in the documentation they used findById right from the route,
but we want to have all of our user functions inside our models, so we're
doing it all through here.
have isMatch, and then we want our callback, and that will take in
null and isMatch:
Make sure we save that and our users route make sure that's
saved. Let's restart the server. We know that Paul has an
encrypted password, so let's use him. Rather, let's do a test
with something that's not a username, something like Invalid
username or password. So if we say Paul with the wrong
password, it will display this error. If we say Paul with the
correct password, we'll now be logged in. Awesome! It's
working now!
Let's save that and let's restart. Now the link you see goes to
user's Logout. So if I click on that it brings us to the Login page and
tells us we're logged out. Now if I click on Members, we'll still
able to see the page and we don't want that.
We'll then go into our layout.pub file again and go where we have
our links. Let's say if !user and then we just want to space these
out as shown in the following code snippet. So if not user, then
it's going to show Register and Login. Down at Logout, we want
to say if user and we'll just space that out. So let's save that and
restart: ul.nav.navbar-nav
li(class=(title == 'Members' ? 'active' : ''))
a(href='/') Members
if !user
li(class=(title == 'Register' ? 'active' : ''))
a(href='/users/register') Register
li(class=(title == 'Login' ? 'active' : ''))
a(href='/users/login') Login
ul.nav.navbar-nav.navbar-right
if user
li
a(href='/users/logout') Logout
We can also add categories; so let's say Test Category. Now when
we add a new post and go to Categories, you can see that we
have the post available. So this is what we'll build—pretty
simple! Let's get into it and get started.
In this chapter, we will also learn:
Adding posts
$ express nodeblog
This will create a bunch of files and folders; let's go into nodeblog:
I'll also open it up in Sublime Text. Let's open up package.json;
there are a bunch of dependencies that we will have to add here.
The first one we will add is monk, which is very similar to
Mongoose which we used in the previous project.
{
"name": "nodeblog",
"version": "0.0.0",
"private": true,
"scripts": {
"start": "node ./bin/www"
},
"dependencies": {
"body-parser": "~1.13.2",
"cookie-parser": "~1.4.3",
"debug": "~2.6.9",
"express": "^4.16.3",
"jade": "^1.11.0",
"morgan": "~1.9.0",
"serve-favicon":"~2.3.0",
"monk": "~1.0.1",
}
}
The next thing that we will want is connect-flash; actually, I'll just
paste these in, and they are pretty much the same things that
we used in the previous project. So we have connect-flash and
express-messages, and these work together to give us flash
messages:
{
"name": "nodeblog",
"version": "0.0.0",
"private": true,
"scripts": {
"start": "node ./bin/www"
},
"dependencies": {
"body-parser": "~1.13.2",
"cookie-parser": "~1.4.3",
"debug": "~2.6.9",
"express": "^4.16.3",
"jade": "^1.11.0",
"morgan": "~1.9.0",
"serve-favicon":"~2.3.0",
"monk": "~1.0.1",
"connect-flash": "*",
"express-validator": "*",
"express-session": "*",
"express-messages": "*",
"multer": "*",
"moment": "*",
}
$ npm install
Now let's try and run the package.json file; we'll enter npm start:
Let's go to http://localhost3000:
Next, let's open app.js; I have some stuff which I'll will paste in.
We want to include session management using express-session.
We also will need multer and moment:
var db = require('monk')('localhost/nodeblog');
app.use(flash());
app.use(function (req, res, next) {
res.locals.messages = require('express-messages')(req, res);
next();
});
I'll also put Express Validator here:
app.use(expressValidator({
errorFormatter: function(param, msg, value) {
var namespace = param.split('.')
, root = namespace.shift()
, formParam = root;
while(namespace.length) {
formParam += '[' + namespace.shift() + ']';
}
return {
param : formParam,
msg : msg,
value : value
};
}
}));
app.use(session({
secret: 'secret',
saveUninitialized: true,
resave: true
}));
},
"dependencies": {
"body-parser": "~1.13.2",
"cookie-parser": "~1.3.5",
"debug": "~2.2.0",
"express": "~4.13.1",
"jade": "~1.11.0",
"morgan": "~1.6.1",
"serve-favicon": "~2.3.0",
"mongodb":"*",
"monk": "^1.0.1",
"monk": "https://github.com/vccabral/monk.git",
$ npm install
doctype html
html
head
title= title
link(rel='stylesheet', href='/stylesheets/style.css')
body
.container
block content
In the container class, we will create an img tag using Jade, giving
it a class of logo (I'll give it an src attribute); we'll set this to
/images/nodebloglogo.png, and this will be in your files:
.container
img.logo(src='/images/nodebloglogo.png')
In this container, we will create a nav tag, and inside this, we'll
have a ul; next, we'll have li, and then, we'll have the a link, let's
give it an href attribute, which will go to /, and after this, we'll
say Home:
.container
img.logo(src='/images/nodebloglogo.png')
nav
ul
li
a(href='/') Home
li
Next, let's place another li and give it another link. We will set
this to /post/add and say Add Post:
li
a(href='/posts/add') Add Post
block content
Within the block content statement, let's put footer that'll contain a
paragraph, and we'll say NodeBlog © 2016 and save it:
block content
footer
p NodeBlog © 2016
This doesn't look like much; we actually have to put the image
in there, so let's open that next. To add images, go to projects,
nodeblog, public, it will go into public images and I will paste that
in, so there's our logo:
Now, in our public folder, we also have the style sheets, so we
will open up style.css. Let's get rid of the body padding. Also, let's
get rid of Lucida Grande because we don't need it, and then, let's
change the font size to 15. The body will also have a very light
gray background, but the color of the text will be a darker gray.
Now we will look at logo. Let's set text-align to center and margin to
auto, and we'll add display:block:
body {
font: 15px Helvetica, Arial, sans-serif;
background: #f4f4f4;
color:#666;
}
.logo{
text-align: center;
margin:auto;
display:block;
}
a {
color: #00B7FF;
}
Now for the container, we will give it width of 750px and border of 1px
with solid and gray. We will give margin of 20px on the top and
bottom and auto on the left and right; we'll do padding:20, and we'll
set border-top as 83cd39 and make this 3px and solid:
.container{
width:750px;
border: 1px solid #ccc;
margin: 20px auto;
padding:20px;
border-top: #83cd39 3px solid;
}
Next, let's create a class called clr for clearing the floats, so
we'll say clear: both. For ul, we will set padding to 0 and margin to 0 by
default:
.clr{
clear: both;
}
ul{
padding:0;
margin:0;
}
We'll use h1, h2, and h3 for headings and p for paragraphs; for
these, let's set padding:5px 0 and margin-bottom to 0, and then, for
paragraphs, we'll set all margins to 0:
h1,h2,h3,p{
padding:5px 0;
margin-bottom:0;
}
p{
margin:0;
}
a {
color: #00B7FF;
}
For the nav object we will set background of 404137, overflow to auto, height
to 40px, padding to 20px 0 0 10px, and font-size: 18px:
nav{
background:#404137;
color:#fff;
overflow:auto;
height:40px;
padding:20px 0 0 10px;
font-size: 18px;
}
Now, we will do nav li and use float:left; we will also say list-
style:none:
nav li{
float:left;
list-style:none;
}
Next, we'll do the nav links, giving them a padding of 10px, margin of 0
and 10px, and color will be white:
nav a{
padding:10px;
margin:0 10px;
color:#fff;
}
Let's now do the current nav link; for this, we'll say nav a.current,
also, for hover, let's use nav a:hover, giving it background of 83cd29 and
color as black:
This is not the best looking template in the world but it'll do.
Let's take those underlines out. To do this, in our nav a tag, we'll
say text-decoration and set it to none. So there's our template.
We will first enter db. I'll just only few posts, so enter
db.posts.insert; we will put a couple of things in here. We need to
have title, and we'll just say Blog Post One. We will also have
category, and for this, let's say Technology (I'll actually use double
quotes for these). The next thing will be author:
Next, we'll need body, so I'll say This is the body. We also want date,
and I want this to be an ISO date, so we will say ISODate and put
parentheses after it:
Let's go ahead and run this. We get "nInserted" : 1; let's create one
more - This is the body of the second post. Let's change author to John Doe
and category to Science, and then add Blog Post Two. Next, we'll enter
db.posts.find and then .pretty:
Here are our two posts; you can see that there's an ObjectId
that was automatically created:
Now we will go to routes and then index.js, and grab from app.js.
We'll need these two code from index.js and let's paste those in
app.js:
For our router.get, we'll create a variable called db, and set it to
req.db. Then we will create a variable called posts and set it to
db.get('posts'). Next we'll say posts.find and inside it, we will first
This function will pass an error and also our posts. Then we'll
take this render, cut it, and then, paste it right in as shown,
along the posts:
extends layout
block content
h1= title
p Welcome to #{title}
extends layout
block content
if posts
each post, i in posts
Then we'll create a div with the post class. Inside this, we'll have
an h1, which will have the a link, and we want this to go to
/posts/show/ and then whatever the ID. We can get this using this
syntax, post._id.
On the next line, we will say =post.title. Let's save it and restart
to see what this does:
You can see that we're actually getting our post titles. Now, I
don't like that color; so in our style sheet, so let's get rid of it:
Here, in post a, let's set color to dark gray and get rid of the
underline.
.post a{
color:#666;
text-decoration: none;
}
extends layout
block content
if posts
each post, i in posts
.post
h1
a(href='/posts/show/#{post._id}')
=post.title
p.meta Posted in #{post.category} by #{post.author}
extends layout
block content
if posts
each post, i in posts
.post
h1
a(href='/posts/show/#{post._id}')
=post.title
p.meta Posted in #{post.category} by #{post.author} on #
{moment(post.date).format("MM-DD-YYYY")}
=post.body
a.more(href='/')
So this will be posts/show/, and then, for the ID we say post._id. The
text for this will just be Read More. So now we will save this, let's
go and reload:
It looks like we're getting an error here, moment is not a
function. So we'll need to make the function global and include
moment globally.
app.locals.moment = require('moment');
Let's try the above code. We'll have to restart the server:
I just want to throw some CSS in our style sheet down at the
bottom and I paste the below code in.
.meta{
padding:7px;
border:1px solid #ccc;
background:#ccc;
}
we have some styles for the meta class as well as the Read
More link and the post itself. Let's save this and reload. The
Read More link should actually be white so lets make the
following changes in the code and save.
a.more{
display:block;
width:80px;
background:#404137;
color:#fff;
padding:10px;
margin-top:30px;
text-decoration: none;
}
This may not be the best-looking blog in the world, but it'll do;
the looks aren't really what's important, it's the functionality.
This may not be the best-looking blog in the world, but it'll do;
the looks aren't really what's important, it's the functionality.
Adding posts
Our application can now read posts from our MongoDB
database. Now we want to make the app in such a way that we
can actually add posts through the application, because up to
this point we've been adding them through the Mongo shell.
To do this, we will need a post route, so let's open up Sublime.
In our routes folder, we will create a new file and save it as
posts.js. Also, we can actually just get rid of users.js; first I'll copy
what's in it or cut it, paste it in post.js, and delete users.js file all
together:
module.exports = router;
Next, we will use GET Posts. Actually, we don't want this yet
because we're already doing it on the home. In the index view,
we'll create the add route; so this will be post/add, and this is
where we want the form to be. Now before we can do
anything here, we need to go to app.js and require the post route,
so I'll just change users to posts. Then, down, we also have to
change to posts and add the post variable:
app.use('/', routes);
app.use('/posts', users);
Now back in our posts route, in our add route, we will render
a form and the add template. So we'll say res.render and pass in
addpost (that's what we'll call it); the second parameter will be an
object, and let's just include title, which will just say Add Post:
The label will be a block, text fields will have some padding,
height, and width, select will have a height, and textarea will
have a heightened width:
Let's save this, reload, and now the form looks a lot better.
When we submit this page, it will go to posts/add, but it's a post
request; you have to create the route.
Now let's go back to our post route file and copy router.get
method that what we have. We will change this get to post, and
then, let's get rid of res.render method. Then, we want to get the
form values, so we want the title. Also, if you look at the
names, here we have name, title, category, body, main image,
and author. So we have to create all these; we will set them to
req.body.title. We'll just copy this.
Next we will have category, so we'll change the next one to body;
the next one will be author and date. Now this here we'll actually
use the date function, so I'll say new Date:
To do a little test, let's use console.log and say title. We'll restart
the server, reload our form here, and put in Test for the title
and then save. We'll get undefined:
Let's save, and now you can see that we getting Test in the
command prompt. Since this is working, let's get rid of
console.log:
Now we want to check to see whether a file was actually
uploaded, if the main image was uploaded. To do this, we will
say if(req.file.mainimage) and just to do a test with, say,
console.log('YES'), else, NO:
if(req.file.mainimage){
console.log('YES');
} else {
console.log('NO');
}
Let's go ahead and restart, and not submit. Now what's this?
Cannot read property 'mainimage' of undefined, req.file.mainimage.
Actually, we just want to say req.file. We'll save this and you
can see over here that we're getting NO:
Now, let's choose a file; let's grab a sample image from Google.
Lets grab an image of a bird. We'll grab this bird, save it in
folder. Now in the command prompt, you can see we're
Downloads
} else {
var mainimage = 'noimage.jpg';
}
I want to say if(err), then let's use res.send. Then, we'll say else we
want to set a message, so req.flash; this will be a success
message, so we'll just say Post Added, and then we just want to
redirect, so we will say res.location and res.redirect.
// Check Errors
var errors = req.validationErrors();
if(errors){
res.render('addpost',{
"errors": errors
});
} else {
var posts = db.get('posts');
posts.insert({
"title": title,
"body": body,
"category": category,
"date": date,
"author": author,
"mainimage": mainimage
}, function(err, post){
if(err){
res.send(err);
} else {
req.flash('success','Post Added');
res.location('/');
res.redirect('/');
}
});
}});
I didn't set this post variable yet, so we will go over here and
create a variable called posts, set it to db.get, and then pass in the
collection posts. Since we don't have db yet, let's go to app.js and
enter these two lines:
Let's save this and now restart the server. Go to Add Post and
let's say Test Post. Category is blank because we don't have
anything yet; we're not fetching them, so I'll leave it for now.
We'll choose an image.
So now you can see that we have our Test Post down here with
the author, date, and body:
Now the last thing I want to do in this section is that I want to
be able to see our categories here. So, I'll quickly add some
through the Mongo shell. Since we have categories, let's say
db.categories.insert, and for a name, we'll say name: 'Technology'. We'll
more, let's say Science and Business.
Now let's go back to posts route, and we want to put the code in
the get method we're getting the add form. So let's go in the
router.get method and create a variable called categories. We'll set
that to db.get('categories'), and then, we'll do a find on these
categories. We will pass in empty curly braces, and the third
parameter will be function. We'll take this render, cut it, and put
it inside the categories; this way we can actually include our
categories:
Let's save this and go to our add form. We'll go down to where
we have our select box for category, so we'll say each category, i in
categories. Then we need option(value= '#{cat}'), and then, we want
the title, so we will go and say category:
.form-group
label Category
select.form-control(name='category')
each category, i in categories
option(value='#{category.name}') #{category.name}
.form-group
label Body
textarea.form-control(name='body', id='body')
`
We need the standard version, not the basic one. So let's save
it, then open it up, and take the whole folder and put it into
our application. We'll take projects and nodeblog and will paste
them inside the public folder. Next we'll go to add, which is in views
and then addpost.jade. We need to include the script at the
bottom, so let's go down and enter script(src='/ckeditor/ckeditor.js'):
.form-group
label Author:
select.form-control(name='author')
option(value='Brad Traversy') Brad Traversy
option(value='John Doe') John Doe
input.btn.btn-default(name='submit',type='submit',value='Save')
script(src='/ckeditor/ckeditor.js')
Make sure that's saved and let's restart the server, and go back
to our app and go to Add Post:
Now you can see we have a text editor we can bold text, we
can make it italic, change the size, headings, cut, we can look
at the source, it's a really nice editor, and you can see how
simple it was to implement.
h1=title
ul.errors
if errors
each error, i in errors
li.alert.alert-danger #{error.msg}
form(method='post', action='/categories/add')
.form-group
label Name:
input.form-control(name='name', type='text')
input.btn.btn-default(name='submit',type='submit',value='Save')
So very simple we just have title, we have our errors ul, a form
that goes to categories/add and a title, actually I will change that to
name. So that should be good, we don't need the enctype though,
so we can remove it:
Add Category, it looks like we have a Jade error. It looks like there's
no ending parenthesis in this form, so we'll add that, there we
go. So there's our form. Now it not will do anything yet we
have to add the post. So let's go back to categories.js and I want to
add router.post('/add'), we can then get rid of the middle functions
and then all we need to get is the name:
For validation, let's do the name, get rid of the rest, and let's
change that to categories and all we have is a name, we'll change
this to Category Added and that's it:
} else {
req.flash('success','Post Added');
res.location('/');
res.redirect('/');
}
});
}
});
So what I'm will do first is I'm will get rid of all these posts and
we will start fresh. So I'm in the Mongo shell and I'm will say
db.posts.remove and then we're just will pass in some empty curly
braces, so that's will remove everything:
So now I just want to add a little bit of CSS to our style sheet:
ul.success li{
padding: 15px;
margin-top:10px;
margin-bottom: 20px;
border: 1px solid transparent;
border-radius: 4px;
color: #3c763d;
background-color: #dff0d8;
border-color: #d6e9c6;
list-style:none;
}
I will paste that in, that's just will make the success message,
give it some padding and background and so forth. So let's
save that and we'll add a new category, let's say Business, Save,
and we get Category Added:
Now let's go to Add Post, you can see we have our two
categories say a Blog Post One. For the body I will grab some
sample text from Lorem Ipsum and let's go ahead and paste
that in:
For an image I will grab that bird picture we have and save.
Now notice that you can actually see the paragraph tags and
it's not giving us any style as if it was a paragraph, so what we
can do is let's go into index.jade and where we have the body I
will add an ! to the front of it, and that's will make it so that it
will parse the HTML:
extends layout
block content
if posts
each post, i in posts
.post
h1
a(href='/posts/show/#{post._id}')
=post.title
p.meta Posted in
a(href='/categories/show/#{post.category}') #{post.category}
by #{post.author}
on #{moment(post.date).format("MM-DD-YYYY")}
img(src='/images/#{post.mainimage}')
!=truncateText(post.body,400)
a.more(href='/posts/show/#{post._id}') Read More
Now we'll go into index file and instead of just having post.body we
will say truncateText and then we will pass in post.body and then
however long you want it to be lets say 400:
!=truncateText(post.body,400)
a.more(href='/posts/show/#{post._id}') Read More
So let's save that and we will have to restart and reload, and
now you can see we're only getting 400.
So we'll add one more, let's change this to Business and there
we go there's our next post:
What I do want to do is make the width a hundred percent
because they should all be the same.
So let's go into our style sheet and I will say img, width:100%:
img{
width:100%;
}
.post img{
width:100%;
}
I will actually copy from the code from our posts.js and then
we'll paste that in categories. js
categories.find({},{},function(err, categories){
res.render('addpost',{
'title': 'Add Post',
'categories': categories
});
});
posts.find({},{},function(err, posts){
res.render('index',{
'title': req.params.category,
'posts': posts
});
Right now this is will get all posts, so we need to limit it, so we
will go into this first parameter here and we will say category:
req.params.category. Now this req.params this is just how you can get
a(href='/posts/show/#{post._id}')
=post.title
p.meta Posted in
a(href='/categories/show/#{post.category}') #{post.category}
by #{post.author}
on #{moment(post.date).format("MM-DD-YYYY")}
img(src='/images/#{post.mainimage}')
!=truncateText(post.body,400)
a.more(href='/posts/show/#{post._id}') Read More
In the next section we will take care of our single page, which
is the posts/show page. So we'll get into that next.
Single post and comments
In this section we will make it so that we can go to the Read
More link, click on it, it'll show us the entire post and we'll also
have comment functionality:
We want to do first is go in to route and then post.js and let's
copy this:
Then we will render 'show' and we need to pass along the post.
So that should do it for the route, let's save it and let's go to our
views folder.
extends layout
block content
.post
h1=post.title
p.meta Posted in
a(href='/categories/show/#{post.category}')
#{post.category}
by #{post.author}
on #{moment(post.date).format("MM-DD-YYYY")}
img(src='/images/#{post.mainimage}')
!=post.body
So what we will want now is under the post, we will have a list
of comments and also a comment form. Now remember we
don't have a special collection for comments it's just they will
be embedded inside of the post object. We're just will add some
stuff to this view, first of all let's put in a line break and then
we'll do a horizontal rule, and then we will test for comments,
so we'll say if post.comments and then under that let's put an h3,
we'll say comments and then we need to loop through them, we'll
use each comment, i in post.comments, add a class of comment and then a
paragraph, this will be comment-name Then we'll have body:
hr
if post.comments
h3 Comments
each comment, i in post.comments
.comment
p.comment-name #{comment.name}
p.comment-body #{comment.body}
Put a line break, and then we will have an h3 that will say Add
Comment. We will check for errors because we will do this just like
any other form. so we'll say each error, i in errors. Then we'll have
an li, let's give it a class of alert.alert-danger:
br
h3 Add Comment
if errors
ul.errors
each error, i in errors
li.alert.alert-danger #{error.msg}
h3 Add Comment
if errors
ul.errors
each error, i in errors
li.alert.alert-danger #{error.msg}
form.comment-form(method='post', action='/posts/addcomment')
input(name='postid', type='hidden', value='#{post._id}')
.form-group
So we will say label Name, input(type=), type is text, actually let's give
this input a class of form-control, type=('text') and name is will equal
name, so we can copy this:
.form-group
label Name
input.form-control(type='text', name='name')
the next one here is will be Email, change the name to email and
then the next one will be the Body and this is will be a textarea,
change the name to body. So after that let's put a br and then we
need an input, we need a submit button, so input.btn.btn-default and
let's see, we will say type='submit', name will also be submit and then
we need a value. For the value we're just will say Add Comment:
Let's save that, and let's see if we reload, now we have this Add
Comment form:
There's no comments showing because we don't have any.
What we want to do is make it so that we can add a comment
through that form. You can see that that's will posts/addcomment so
we want to go create that in our post file. So we'll go all the
way to the bottom here and I'm actually will copy what we
have here in this post:
// Form Validation
req.checkBody('title','Title field is required').notEmpty();
req.checkBody('body', 'Body field is required').notEmpty();
// Check Errors
var errors = req.validationErrors();
if(errors){
res.render('addpost',{
"errors": errors
});
} else {
var posts = db.get('posts');
posts.insert({
"title": title,
"body": body,
"category": category,
"date": date,
"author": author,
"mainimage": mainimage
}, function(err, post){
if(err){
res.send(err);
} else {
req.flash('success','Post Added');
res.location('/');
res.redirect('/');
}
if(errors){
var posts = db.get('posts');
posts.findById(postid, function(err, post){
res.render('show',{
"errors": errors,
"post": post
});
});
name, email, body, and commentdate, so that's our new comment object.
Now what we want to do is get the posts. and then we want to
do an update, so we'll say post.update and we will pass in the _id to
postid, actually that's it for that. In the next parameter this is,
we will say push, comments, and then after that we want function,
we'll say error and doc for a document, check for the error. If
there is one let's just throw, if there's not then we're just will
set a message so req.flash, this is will be a success message, and
we're just will say Comment Added and then we just want to redirect.
Actually you know what we want to redirect to the same page
to the post, so what we'll have to do is say posts/show/ and then
we need to concatenate on postid:
if(errors){
var posts = db.get('posts');
posts.findById(postid, function(err, post){
res.render('show',{
"errors": errors,
"post": post
});
});
} else {
var comment = {
"name": name,
"email": email,
"body": body,
"commentdate": commentdate
}
var posts = db.get('posts');
posts.update({
"_id": postid
},{
$push:{
"comments": comment
}
}, function(err, doc){
if(err){
throw err;
} else {
req.flash('success', 'Comment Added');
res.location('/posts/show/'+postid);
res.redirect('/posts/show/'+postid);
}
});
So let's save that and let's try to restart the server. we're
getting an error Unexpected token, after body:
var comment = {
"name": name,
"email": email,
"body": body,
"commentdate": commentdate
}
So now let's reload this page and let's try to add a comment.
We'll say Thanks for the post:
Actually we'll change the name let's say Jeff Smith. Comment
added and there it is:
User Functionality
The file structure is going to be very, very simple; it's not going to be even
near as many files and folders as we've been using.
Let's create a new folder here, and call this application chatio.
We then create our index.html; that's going to have our interface.
We want to create the interface first, and then we'll create our
server. So, let's create index.html and open this with Sublime
Text. We'll put in some base HTML code lines; and what we're
going to do is put a title in the title tag, say ChatIO. In the body,
we'll create a div; give it an ID of container. We're creating a very
simple user interface, and we won't be using Bootstrap etc.;
instead, we'll just have some custom CSS.
In the container, we'll have a few different areas. The first one
will be the names wrapper, and this is going to be where our
username form goes. So, let's create a div with the ID of
namesWrapper. Inside there, let's put an h2 that will say ChatIO. And
then, we'll have a paragraph, and we're just going to say Create
Username. Under that, we'll have our form. This will have an ID of
<!DOCTYPE html>
<html>
<head>
<title>ChatIO</title>
</head>
<body>
<div id="container">
<div id="namesWrapper">
<h2>ChatIO</h2>
<p>Create Username:</p>
<form id="usernameForm">
<input type="text" size="35" id="username">
<input type="submit" value="Submit">
</form>
</div>
<div id="mainWrapper">
<h2>ChatIO</h2>
That should be it! We save that and check out the result; it's
not going to look like much, just two forms:
What we'll do now is just add some CSS, and I'll put it inside
the head. I will give the body a light grey background. Then we'll
add container with a width of 700 and a margin of 0 auto, pushing
everything to the middle. Now, for the chatWindow, we will have a
fixed height of 300 pixels:
<head>
<title>ChatIO</title>
<style>
body{
background:#f9f9f9;
}
#container{
width:700px;
margin:0 auto;
}
#chatWindow{
height:300px;
}
#mainWrapper{
display:none;
}
#chatWrapper{
float:left;
border:1px #ccc solid;
border-radius: 10px;
background:#f4f4f4;
padding:10px;
}
#userWrapper{
float:left;
border:1px #ccc solid;
border-radius: 10px;
background:#f4f4f4;
padding:10px;
margin-Left:20px;
width:150px;
max-height:200px;
}
Then we need our namesWrapper, and for that we're actually going
to have a lot of the same stuff as well as the chatWrapper and
userWrapper. One last thing: I just want to make the inputs a little
#namesWrapper{
float:left;
border:1px #ccc solid;
border-radius: 10px;
background:#f4f4f4;
padding:10px;
margin-Left:20px;
}
input{
height:30px;
}
Node.js installed.
So, the name will be chatio, version will be 1.0.0, and description will
be simple chat app. We'll change entry point to server.js. That's good
and then put your own name so that should go ahead and
create it:
I will add it to Sublime Text. Let's go to the C:\Projects\chatio.
There's our package.json file. Now, we want to go in the code and
add a couple dependencies. So, we'll say dependencies and add
socket.io and express (latest versions) as shown in the following
code snippet:
"dependencies": {
"socket.io": "*",
"express": "*"
}
Let's save that and we'll go back to our command line and run
npm install. This will install socket.io and Express. Now that those
are installed, we'll create our server.js file. Let's open that up,
and the first thing we'll do is include Express and continue on
with the things we need to initialize. So, app will take in express
and server will take in require ('http'). Here, we'll say .createServer
and pass in app. We then include socket.io, and call .listen and put
in server. The next thing we do is ask it to listen on whatever
port we want to use, so, we'll say server.listen and pass in
process.env.port or, let's just say PORT 3000, but you could put
anything really. So we need one route, just for the index.html file.
Let's say app.get and this will have /. Then this will take in
request-response, and then say res.sendfile . We'll then say dirname,
and let's concatenate onto that / index.html. In essence, we're
saying that when you go to the home screen, or /, we want to
load this file, simple:
server.listen(process.env.PORT || 3000);
Let's actually test that out. So, we'll save and then go into our
command line and run node server. Let's put a console log, just so
we can tell if we're actually running the server. Let's say,
console.log ('Server Running...'). Now, let's go to localhost port 3000.
So it loads, and that's exactly what we want. I just want to fix
the indent. To fix that, let's go to our HTML and edit the CSS.
input{
height:30px;
border:solid 1px #ccc;
}
io.sockets.on('connection', function(socket){
console.log('Socket Connected..');
// Send Message
socket.on('send message', function(data){
io.sockets.emit( 'new message', {msg: data});
});
});
body tag. We'll include two things here: two scripts. The first one
is jQuery, we'll use the CDN; and then the second one is going
to be the socket.io.js file so that we can actually interact with the
server. We'll be using jQuery. Let's open up the script tag:
io.sockets.on('connection', function(socket)
console.log('Socket Connected...');
// Send Message
socket.on('send message', function(data){
io.sockets.emit('new message', {msg: data});
});
});
#namesWrapper{
display:none;
float:left;
border:1px #ccc solid;
border-radius: 10px;
padding:10px;
margin-Left:20px;
}
<scripts>
$(function(){
var socket = io.connect();
var $messageForm = $('messageForm');
var $message = $(#message);
var $chat = $('#chatWindow');
$messageForm.submit(function(e){
e.preventDefault();
console.log('Submitted');
});
});
</script>
$messageForm.submit(function(e){
e.preventDefault();
});
Let's save that; reload the server, refresh the page, and let's say
Hello.
#mainWrapper{
display:none;
}
usernames = []
We'll say if usernames.indexOf, pass in data, and say not equal to -1
then callback (false). So we'll check if the username is there. Then
we'll do an else and we'll set callback (true). We'll say socket.username
will be equal to the data that gets passed in. We'll add it to the
usernames array: usernames.push (socket.username). Then we'll call a
function called updateUsernames. We'll create that. Let's go under
the above code and let's say updateUsernames. What we want to do
here is emit an event; so use io.sockets.emit, we want to emit
usernames, and we also want to pass along the usernames:
if(usernames.indexOf(data) != -1){
callback(false);
} else {
callback(true);
socket.username = data;
usernames.push(socket.username);
updateUsernames();
}
});
// Update Usernames
function updateUsernames(){
io.sockets.emit('usernames', usernames);
}
We'll pass that so that we can actually list them on the sidebar
in the app. So this is the server and it's looking good. The last
thing I want to do here is write a disconnect event. So we'll say
socket.on 'disconnect', and pass in data. Then we'll say if not
socket.username, then we want to return. We'll use usernames.splice
because we want to take that user out of the usernames array. So
we use splice usernames.indexOf(socket.username), and then we pass in the
second parameter of 1. Post that we'll call updateUsernames again, so
let's grab that:
// Disconnect
socket.on('disconnect', function(data){
if(!socket.username){
}
usernames.splice(usernames.indexOf(socket.username), 1);
updateUsernames();
});
$usernameForm.submit(function(e){
e.preventDefault();
console.log('Submitted');
});
if(usernames.indexOf(data) != -1)
Let's test that so far. We'll save it, restart, reload, and if we put
something in ChatIO, it hides the username form and shows us
the mainWrapper. That's good!
Now, it will say else and error variable and say .html. Then take
whatever error we want to say, for example, username is taken:
} else{
$error.html('Username is taken');
}
socket.on('usernames', function(data){
var html = '';
for(i = 0;i < data.length;i++){
html += data[i] + '<br>';
}
$users.html(html);
});
We run git add, and then a .. This just means add everything
that's in the folder. We should now be able to commit locally;
so we'll run git commit, and I'll add the m tag and then a comment:
Let's continue with the guide, because there are some things
we didn't do. The application is deployed; we need to ensure
that at least one instance of the app is running. Let's go ahead
and copy this command in the command prompt:
Scaling dynos... Failed
No such process type web defined in Procfile.
node index.js
This example says index, I'll copy the above code, and then we
want to go to our project folder and create a new document,
Procfile. It should have a capital P. We'll open that up, and I'll
paste the above code in it, and change index.js to app.js and save
that. Now, we have to make the changes in the prompt. We'll
add git add, then commit using git commit -m, and we'll say "Added
Procfile". Now we should be able to run git push heroku master:
Let's go ahead and see if that worked. We're still getting the
error. We can show our log with heroku logs. We now need to
define our scale, our dynos scale, in the code:
This is now live on the internet; you can get to this from
anywhere, using this subdomain; of course, you can add your
own domain, but let's just make sure that this works.
Creating an account We can set the First Name field to Sam, Last Name to Smith,
Street Address to Something St, City to Boston MA 01221, and Email Address
can be something.com. Then, we can add the Username and the Password. The
Username will be sam. We can put the Password we like and click on Signup. So,
now we have the user. Let's try logging in with the set credentials. We'll add the
Username, sam, and its password:
Logged in as Sam So, now we're logged in as Sam. At present we don't have any
classes, so we can go ahead and register for some classes. For this, we can go to
our Classes.
Let's say we want HTML5 and we can go ahead and register for the
class:
Sam's classes Now it's added in our Classes. If there are lessons, we can view
those by clicking on the View Lessons button. So, this is how we can register of
the classes and explore them.
Now, let's log out and then log in as an instructor, Brad:
Brad's classes These are classes that I'm registered to teach. Now, we can view
the lessons just like we could with the students, but we can also add a lesson by
clicking on the Add Lesson button. Let's say we want to add a lesson under the
name Another Lesson.
For this, we'll fill the following form and add the lesson:
We'll add the lesson number, let's say 4, then lesson title, Another
Lesson, and we can set the lesson body to, Test and we'll add this
lesson.Now if we go to HTML5, View Lessons, you can see we have
Another Lesson:
Now, we can log out. So, the registering and exploring the classes is
very simple. As far as technologies are concerned, we'll be using
Passport and bcrypt for login. We'll also be using Mongoose and
MongoDB. We're going to use Express validator and Express
handlebars for our templating engine, which is a nice break from Pug.
Let's get started!
The app and HTML Kickstart
setup
In this section, we'll build a fairly simple online learning
system. So we'll use obviously Node.js along with some other
technologies. We have used some of them (not all). We are
going to use Passport and use an authentication system. We're
also going to use HTML KickStart for our frontend, which is
just something that's similar to Bootstrap. I just wanted to kind
of change it up a little bit. Now I want to be able to have
students and instructors both come and register and login.
Instructors will be able to create classes and lessons within
those classes, and then students will be able to register for
those classes.
Setting up an application using
Express Generator
First, we'll set up our application using Express Generator. So
let's go to the Projects folder and open up a command line in
Projects. I'm using my Git Bash tool and we're going to run our
$ express elearn
$ cd elearn
{
"name": "elearn",
"version": "0.0.0",
"private": true,
"scripts": {
"start": "node ./bin/www"
}
"dependencies": {
"body-parser" : "~1.13.2"
"cookie-parser": "~1.3.5",
"debug": "~2.2.0",
"express": "~4.13.1",
"jade": "~1.11.0",
"morgan": "~1.6.1",
"serve-favicon": "~2.3.0"
}
}
"dependencies": {
"body-parser" : "~1.13.2"
"cookie-parser": "~1.3.5",
"debug": "~2.2.0",
"express": "~4.13.1",
"express-handlebars": "*",
"morgan": "~1.6.1",
"serve-favicon": "~2.3.0"
}
Then we have a bunch of other stuff to put in here and I'm just
going to paste that in:
"dependencies": {
"body-parser" : "~1.13.2"
"cookie-parser": "~1.3.5",
"debug": "~2.2.0",
"express": "~4.13.1",
"express-handlebars": "*",
"morgan": "~1.6.1",
"serve-favicon": "~2.3.0"
"bcryptjs": "*",
"passport": "*",
"passport-http": "*",
"passport-local": "*",
"mongodb": "*",
"mongoose": "*",
"express-session": "*"
"connect-flash": "*"
"express-messages": "*"
"express-validator": "*"
}
}
$ npm install
Configuring the app.js file
Now what we'll do is open app.js and there's a bunch of stuff
that we have to include up there. So, I'll paste some code in
here, like this:
We're going to keep both of these. The index route is just for
the main dashboard area. The users will be the registration
and so forth. So we'll leave that as is. Now the view engine
setup is going to be a little different:
// view engine setup
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'jade')
We're going to keep the first line of the two, because it's just
telling us to use the folder called views. But for the second one,
we're going to change pug to handlebars and then there's one
additional line that we need to include here, which is app.engine,
and we're going to pass in handlebars. Then we want to take
that variable that we created above exphbs and just pass in an
object. We want to define the default layout. This will be the
name of the file that you want to use for your layout. We're
just going to call it layout. The view engine setup is going to look
like this:
app.use(express.static(path.join(__dirname, 'public')));
// Express Session
app.use(session({
secret: 'secret',
saveUninitialized: true,
resave: true
}));
It's just providing a secret. You can change this if you'd like.
The next thing is going to be for our Passport. We're going to
want to call initialize as well as session:
// Passport
app.use(passport.initialize());
app.use(passport.session());
The next thing is for Express Validator, and this is right from
the GitHub page. I haven't changed anything:
//Express Validator
app.use(expressValidator({
errorFormatter: function(param, msg, value) {
var namespace = param.split('.')
, root = namespace.shift()
, formParam = root;
while(namespace.length) {
formParam += '[' + namespace.shift() + ']';
}
return {
param : formParam,
msg : msg,
value : value
};
}
}));
// Connect-Flash
app.use(flash());
// Global Vars
app.use(function(req, res, next) {
res.locals.messages = require('express-messages')(req, res);
next();
});
<h1>Welcome</h1>
Then we delete the layout.pug file and create a folder called layouts
because that's where Express Handlebars is going to look.
Next, we're going to create a file called layout.handlebars in that
folder:
Folder structure
In layout.handlebars, let's put our base HTML and we'll add the title
as Elearn. Then, in the body, we just want to add triple curly braces
{{{}}} and then add body: <!DOCTYPE html>
<html>
<head>
<title>Elearn</title>
</head>
<body>
{{{body}}}
</body>
</html>
That will output whatever view we're at, at the current time. Let's save
that.
Running the setup in the
browser
Now, let's test out our setup and run the npm start command:
$ npm start
<!DOCTYPE html>
<html><head>
<title>HTML KickStart Elements</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<meta name="description" content="" />
<meta name="copyright" content="" />
<link rel="stylesheet" type="text/css" href="css/kickstart.css" media="all" /> <!-- KICKSTART -->
<link rel="stylesheet" type="text/css" href="style.css" media="all" /> <!-- CUSTOM STYLES -->
<script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.j
<script type="text/javascript" src="js/kickstart.js"></script> <!-- KICKSTART -->
</head><body>
well. We want it for the scripts as well. We want it for this one.
That stylesheet was actually in the root of the framework, so
we might have to bring that over. Then, we'll change the script
type, javascript, to /javascripts like this: <!DOCTYPE html>
<html><head>
<title>Elearn</title>
<link rel="stylesheet" type="text/css"
href="/stylesheets/kickstart.css" media="all" />
<link rel="stylesheet" type="text/css"
href="/stylesheets/style.css" media="all"
/>
<body>
Then we're going to edit the Item 1 to Home. In the href, we'll add /,
item 2 will be Classes, and the href will go to /classes:
<body>
Now you'll notice that this looks a little weird considering the
padding and background colors, and all that grey. The reason
for that is because if we look at the HTML template that we
have, there's a style.css file that's outside of the css folder for
some reason. So what we're going to do is just copy that and
then bring that over to our Projects | public | stylesheets folder and
then we'll paste it there. There might be already a style.css file
in this folder. In that case, we'll replace that file. Now, if we go
back to our app, it looks a lot better:
Now, I do want to add some buttons to the classes in <hr>. So if
we go in each of these columns in the HTML file, we're going to
put a link with a class of button, and for now, we'll just set this to
go nowhere and then that'll just say View Class:
<div class="col_4">
<h4>HTML 101</h4>
<p>Lorem ipsum dolor sit amet, consectetuer, adipiscing elit, sed diam nonummy
nibh euismod tincidunt ut laoreet dolore
magna aliquam erat volutpat. Ut wisi enim ad minim veniam, quis nostrud exerci
tation ullamcorper suscipit lobortis</p>
<a class="button" href="#">View Class</a>
</div>
<div class="col_4">
<h4>Intro To PHP</h4>
<p>Lorem ipsum dolor sit amet, consectetuer, adipiscing elit, sed diam nonummy
nibh euismod tincidunt ut laoreet dolore
magna aliquam erat volutpat. Ut wisi enim ad minim veniam, quis nostrud exerci
tation ullamcorper suscipit lobortis</p>
<a class="button" href="#">View Class</a>
</div>
<div class="col_4">
<h4>Learn Node.js</h4>
<p>Lorem ipsum dolor sit amet, consectetuer, adipiscing elit, sed diam nonummy
nibh euismod tincidunt ut laoreet dolore
magna aliquam erat volutpat. Ut wisi enim ad minim veniam, quis nostrud exerci
tation ullamcorper suscipit lobortis</p>
<a class="button" href="#">View Class</a>
</div>
Now, we can also put the paragraph part into into the index
template. Let's grab everything that's in the col_9 div, cut that,
and then we're going to put our {{{}}} tag thing in there:
<div class="col_12">
<div class="col_9">
{{{body}}}
</div>
<h3>Welcome To Elearn</h3>
<p>
Lorem ipsum dolor sit amet, consectetuer, <em>adipiscing elit</em>, sed diam
nonummy nibh euismod
tincidunt ut laoreet dolore magna aliquam erat volutpat. Ut wisi enim ad minim
veniam, quis
nostrud exerci tation <strong>ullamcorper suscipit lobortis</strong> nisl ut
aliquip ex ea commodo consequat.
Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie
consequat</p>
Setting up partials
We're going to create a folder called partials. For now, we're
going to have two files in here. We'll create one and let's save
it as classes.handlebars. We also want one called login.handlebars.
Let's start with the login. What we'll do is go to our layout and
we're going to grab from the heading down to where the form
ends, cut that out, paste it in the login.handlebars:
<div class="col_3">
{{>login}}
<div>
<hr />
{{>classes}}
</div>
<h4>Latest Classes</h4>
<div class="col_4">
<h4>HTML 101</h4>
<p>Lorem ipsum dolor sit amet, consectetuer, adipiscing elit, sed diam nonummy
nibh euismod tincidunt ut laoreet dolore
magna aliquam erat volutpat. Ut wisi enim ad minim veniam, quis nostrud
exerci tation ullamcorper suscipit lobortis</p>
<a class="button" href="#">View Class</a>
</div>
<div class="col_4">
<h4>Intro To PHP</h4>
<p>Lorem ipsum dolor sit amet, consectetuer, adipiscing elit, sed diam nonummy
nibh euismod tincidunt ut laoreet dolore
magna aliquam erat volutpat. Ut wisi enim ad minim veniam, quis nostrud
exerci tation ullamcorper suscipit lobortis</p>
<a class="button" href="#">View Class</a>
</div>
<div class="col_4">
<h4>Learn Node.js</h4>
<p>Lorem ipsum dolor sit amet, consectetuer, adipiscing elit, sed diam nonummy
nibh euismod tincidunt ut laoreet dolore
magna aliquam erat volutpat. Ut wisi enim ad minim veniam, quis nostrud
exerci tation ullamcorper suscipit lobortis</p>
<a class="button" href="#">View Class</a>
</div>
<h3>Welcome To eLearn</h3>
<p>
eLearn is a simple and free online learning platform. Instructors can create
courses and students can register for courses which include assignments,
quizzes and support forums.
</p>
<a class="button" href="/users/signup">Sign Up Free</a>
<p>You need to signup to register for free classes</p>
C:\Windows\system32>cd/
C:\>cd mongodb/bin
C:\mongodb\bin>mongo
...
Now we have one class, let's go ahead and add another one.
I'm going to change the name to John Doe and then we'll also
change the title. [Silence] Alright, so this one let's say
Advanced PHP:
sses.insert({title:'Advanced PHP', description: 'Lorem ipsum dolor sit amet, consectetuer, adipisci
WriteResult({ "nInserted" : 1 })
Then we'll do one more. So this one let's call Intro to Photoshop:
insert({title:'Intro to Photoshop', description: 'Lorem ipsum dolor sit amet, consectetuer, adipisc
WriteResult({ "nInserted" : 1 })
// Class Schema
var ClassSchema = mongoose.Schema({
title: {
type: String
},
description: {
type: String
},
instructor: {
type: String
},
lessons:[{
lesson_number: {type: Number},
lesson_title: {type: String},
lesson_body: {type: String}
}]
});
So it's getting the classes. Now these classes are being fetched
from the database. Now in the next part of this section, what I
want to do is take care of the details page so we have a single
class view. And then when we click on Classes, I want to list all
the classes, just like we do on the index but without a limit.
We'll do that in the next part.
Fetching classes – part B
In this section, we're going to set up the Classes page:
module.exports = router;
// Classes Page
router.get('/', function(req, res, next) {
Class.getClasses(function(err, classes){
res.render('classes/index', { classes: classes });
},3);
});
module.exports = router;
Let's save this file. Next, we're going to include the routes file
in app.js. So what we need to do is add a classes variable and
require it like this: var users = require('./routess/users');
var classes = require('./routes/classes');
app.use('/', routes);
app.use('/users', users);
app.use('/classes', classes);
Creating the index.handlebars
file for the classes page
Next, in our views folder we're going to create a new folder
called classes. Then in here, we're going to create a new file
called index.handlebars. We'll open that up and I'm going to paste
this in:
<h2>Classes</h2>
{{#each classes}}
<div class="block">
<h3>{{title}}</h3>
<p>
{{description}}
</p>
<p>Instructor: <strong>{{instructor}}</strong></p>
<a class="button" href="/classes/{{_id}}/details">Class Details</a>
</div>
{{/each}}
// Class Details
router.get('/:id/details', function(req, res, next) {
Class.getClassById([req.params.id],function(err, classes){
res.render('classes/details', { class: classes });
});
});
// Class Details
router.get('/:id/details', function(req, res, next) {
Class.getClassById([req.params.id],function(err, classes){
res.render('classes/details', { class: classes });
});
});
// Class Details
router.get('/:id/details', function(req, res, next) {
Class.getClassById([req.params.id],function(err, classname){
<h2>{{class.title}}</h2>
That's our h2. Now let's put in a paragraph and then this is
going to be the instructor. Let's put a <strong> tag and say
Instructor and then we can say class.instructor. Under that, let's put
in the description:
<h2>{{class.title}}</h2>
<p>
<strong>Instructor: </strong> {{class.instructor}}
</p>
<p>
{{class.description}}
</p>
Under the description, we're going to take the lessons and spit
them out in a list, so we'll say ul. I'm going to give this a class of
"alt" and then what we want to do is say #each class.lessons, which
we don't have any now. The instructors will be able to add the
lessons later. So let's end the each and in the each object, we want
just list items. We should be able to get lesson_title:
<ul class="alt">
{{#each class.lessons}}
<li>{{lesson_title}}</li>
{{/each}}
</ul>
Under the lesson, we're going to want a form to register for the
class. But in order to do that, we need the user to be logged in.
Now, we don't have the login functionality yet but we will
later. So, we'll add if user, then let's create a form, like this:
I'll now add an else statement. So just move this down and
we'll say {{else}} and then we'll put a paragraph, "You must be logged
in to register for this class":
Let's save that and restart the server. If I click on View Class, it
takes us to the details page:
Creating a user model
Under models, let's create a new file and save it as user.js. Now
this is where we're going to keep the basic user information
such as the username and password but we will also be
creating a student model and then an instructor model that
will hold things like their address and extended information.
Let's just do this one really quick. I'm going to paste in some
requirements. We need Mongoose. We need bcrypt for
password encryption and then I'm going to paste in the User
Schema:
// User Schema
var UserSchema = mongoose.Schema({
username: {
type: String
},
email: {
type: String
},
password:{
type:String,
bcrypt: true
},
type:{
type:String
}
});
So the next thing we want some functions for the user model.
Get User by Id
We want to get a single user. So, we're going to paste that in
getUserById. It's going to take in an id and a callback and then
we're just going to call User.findById:
// Get User By Id
module.exports.getUserById = function(id, callback){
User.findById(id, callback);
}
Get User by Username
The next function we want to add is Get User by Username. This is
going to be the same thing, except we need to just have a
query saying we want a certain username and then we're just
using findOne:
We're going to take the bcrypt.hash function and pass in the new
user's password, which comes as the first argument in the
function. You'll notice that we're also passing in newStudent. Then
down here, we're setting the hash and then we're going to call
this async.parallel because we want to save two different things.
We want to save in the users table with newUser.save or the users
collection and then also in the student collection with
newStudent.save, and async.parallel allows us to do that.
Create Instructor User
So now we want to do the same thing for instructors. Let me
just paste that in the code for instructor:
// Compare password
module.exports.comparePassword = function(candidatePassword, hash, callback){
bcrypt.compare(candidatePassword, hash, function(err, isMatch){
if(err) throw err;
callback(null, isMatch);
});
}
module.exports = router;
<h2>Create An Account</h2>
<form id="regForm" method="post" action="/users/register">
<div>
<label>Account Type</label>
<select name="type">
<option value="student">Student</option>
<option value="instructor">Instructor</option>
</select>
</div>
<br />
<div>
Now, this code is kind of a lot. It's a lot of different fields. So it's
a form. We have the id regForm. It's going to go to users/register:
<h2>Create An Account</h2>
<form id="regForm" method="post" action="/users/register">
<div>
<label>Account Type</label>
<select name="type">
<option value="student">Student</option>
<option value="instructor">Instructor</option>
</select>
</div>
OK, then we have first name, last name, street address, city,
state, zip, email, username, and password. So let's save that
and now we should be able to at least see the form. So let's go
ahead and restart our app:
$ npm start
<h3>Welcome To eLearn</h3>
<p>
eLearn is a simple and free online learning platform. Instructors can create
courses and students can register for courses which include assignments,
quizzes and support forums.
</p>
<a class="button" href="/users/register">Sign Up Free</a>
<P>You need to signup to register for free classes</p>
It doesn't look too good. I want to push all the inputs over a
little bit, so what we can do is go to our public stylesheets and
then style.css and we'll go all the way to the bottom. Let's just
say form label. We want to display as an inline-block and let's
set the width to 180:
form label{
display:inline-block;
width:180px;
}
We can close the style.css and I just want to put a line break in
between each one of the divs in the register.handlebars.
// Student Schema
var StudentSchema = mongoose.Schema({
first_name: {
type: String
},
last_name: {
type: String
},
address: [{
street_address:{type: String},
city:{type: String},
state:{type: String},
zip:{type: String}
}],
username: {
type: String
},
email: {
type: String
},
classes:[{
class_id:{type: [mongoose.Schema.Types.ObjectId]},
class_title: {type:String}
}]
});
// Instrucor Schema
var InstructorSchema = mongoose.Schema({
first_name: {
type: String
},
last_name: {
type: String
},
address: [{
street_address:{type: String},
city:{type: String},
state:{type: String},
zip:{type: String}
}],
username: {
type: String
},
email: {
type: String
},
classes:[{
class_id:{type: [mongoose.Schema.Types.ObjectId]},
class_title: {type:String}
}]
});
// Register User
router.post('/register', function(req, res, next) {
});
Now we'll grab all the values and put them into variables. So,
I'm going to paste this in. There are quite a few variable as
shown here:
// Register User
router.post('/register', function(req, res, next) {
// Get Form Values
var first_name = req.body.first_name;
var last_name = req.body.last_name;
var street_address = req.body.street_address;
var city = req.body.city;
var state = req.body.state;
var zip = req.body.zip;
var email = req.body.email;
var username = req.body.username;
var password = req.body.password;
var password2 = req.body.password2;
var type = req.body.type;
});
We're getting the first name, last name, all the address info,
email, username, password, and password2 because we're
going to validate it and then the type. The next thing we want
to do is our form validation. I'm going to paste this stuff in:
// Form Validation
req.checkBody('first_name', 'First name field is required').notEmpty();
req.checkBody('last_name', 'Last name field is required').notEmpty();
req.checkBody('email', 'Email field is required').notEmpty();
req.checkBody('email', 'Email must be a valid email address').isEmail();
req.checkBody('username', 'Username field is required').notEmpty();
req.checkBody('password', 'Password field is required').notEmpty();
req.checkBody('password2', 'Passwords do not
match').equals(req.body.password);
}
});
So let's go to views, users, and then our register page, and we're
going to go ahead and just paste in the code, checking for
errors:
<h2>Create An Account</h2>
{{#if errors}}
{{#each errors}}
<div class="notice error"><i class="icon-remove-sign icon-
large"></i> {{msg}}
<a href="#close" class="icon-remove"></a></div>
{{/each}}
{{/if}}
<form id="regForm" method="post" action="/users/register">
If there are errors, then we're going to loop through them and
we're going to spit out a div and notice we have the class of
error. So that's going to be kind of like a Bootstrap alert, and
then we want to echo out the message. Let's save that and we'll
test this out.
Testing the app for the errors
Now, we'll restart the app. Then go to the free signup. If I click
on Sign Up, we get a bunch of different errors, like this:
Error page 1
If I put in the first name and click on Sign Up, you can see that the
error for the first name is not here anymore:
Error page 2
So the form validation is working. Now we want each option to do
what it is supposed to do when the form actually passes.
Creating different objects in
user.js for user collection
We need to create a couple of different objects in the else
statement in users.js. So first we have the newUser object, which is
going to go into the user collection:
if(errors){
res.render('users/register', {
errors: errors
});
} else {
var newUser = new User({
email: email,
username: username,
password: password,
type: type
});
}
});
else; for now let's just console.log('is instructor'); and then the if
statement one is student:
if(type == 'student'){
console.log('is student');
} else {
console.log('is instructor');
}
});
Let's give this a shot. We'll save it. Then, let's go to the app and
restart it. Now it's not going to let us through unless we pass all
the validation, so let's just quickly do that signup, and you can
see student printing on the screen:
POST /users/...
is instructor
Now let's actually just keep this if else statement and in the
console statement, we'll say Registering Student... and Registering
Instructor.... like this:
if(type == 'student'){
console.log('Registering Student...');
} else {
console.log('Registering Instructor...');
}
});
Creating the new student
object
For the student, we're going to have to create a new student
object, so I'm going to paste the code in, just like we did with
the new user, except we have all the fields that will go in the
student collection: if(type == 'student'){
console.log('Registering Student...');
We're going to pass in the user and student object, and then
just go ahead and create the student.
Creating the new instructor
object
I'm going to just copy what we have in the if statement, paste
that in the else statement, and change the newStudent to newInstructor.
Then we change the Student object to Instructor. We'll also change
the saveStudent to saveInstructor: } else {
console.log('Registering Instructor...');
var newInstructor = new Instructor({
first_name: first_name,
last_name: last_name,
address: [{
street_address: street_address,
city: city,
state: state,
zip: zip
}],
email: email,
username:username
});
res.redirect('/');
}
Let's save that and see what happens. Now, what I want to do
is go to the layout.handlebars, go right above the body, and put in
messages, like this:
<div class="grid">
<div class="col_12">
<div class="col_9">
{{{messages}}}
{{{body}}}
</div>
User
Now we'll say db.instructors.find and that's good:
Output
You'll see we have classes in this case, which is just an empty array,
and that is what we want. Also, we should not have any db students: >
db.students.find()
>
Good, so there are no students. Awesome! So that's working perfectly.
Running the app for the
student
Now let's create an account and register as a student. We'll add
all the required fields and click on Sign Up:
Account details This will again redirect us to the main eLearn page. Now, if we
go to the database and look for the db.student.find(), we'll get the object:
Output
We'll also see the db.users.find(), as shown here:
Find user output So, the registration is done. In the next section, we'll take care
of log in.
Logging in users
In this section, we're going to take care of the user login.
Before we get to that, I want to address something. When we
registered, in the last section, everything went well. The user
got inserted, but we didn't get our message here, and we need
to fix that. So, we're going to change app.js content just a little
bit.
// Global Vars
app.use(function (req, res, next) {
res.locals.success_msg = req.flash('success_msg');
res.locals.error_msg = req.flash('error_msg');
next();
});
// Connect-Flash
app.use(flash());
Now we go back into our users route in the users.js file, and go
to our register post. Down at the bottom of our register post,
we put this req.flash line. Now here I think it's just success. So,
what you want to do is change it to success_msg:
{{#if success_msg}}
<div class="notice error"><i class="icon-remove-sign icon-large"></i> {{msg}}
<a href="#close" class="icon-remove"></a>
</div>
{{/if}}
{{#if error_msg}}
<div class="notice error"><i class="icon-remove-sign icon-large"></i> {{msg}}
<a href="#close" class="icon-remove"></a>
</div>
{{/if}}
So, that'll test for both success and error messages. If there is
any, it's going to output it as an alert. So, save that and just to
test everything out, just to test the whole message system out,
let's go ahead and register another user.
go to /login. Let's get rid of the res.render. Now, since we're using
passport, we need to squeeze another parameter in the middle.
Then we also want failureFlash and we're going to set that to true.
Then when we're logged in, we'll set a flash message. So, let's
say req.flash and pass in a success_msg, You are now logged in. Then we'll
set the user type. We'll say var usertype = req.user.type. Then we'll
say res.redirect. We want this to go to / and then we'll
concatenate on usertype. We then say usertype and then we're
going to concatenate an s. We'll make it plural and then /classes.
You'll see why in a little bit:
passport.serializerUser(function(user, done) {
done(null, user._id);
});
passport.deserializeUser(function(id, done) {
User.getUserById(id, function (err,user) {
done(err, user);
});
});
Make sure you have passport and local strategy present in the users.js file.
I'm pretty sure that's it for the actual login functionality. Let's
save that and let's go to our application.
Now, the last thing we need to do with the login is handle the
messaging. Because if we try to log in and we don't have the
right password or username, we want it to tell us. So, what we
need to do is go back to app.js and it's going to come at us in
req.flash I'll copy the res.locals.error line and we're just going to
take off the _msg so it's just error. That'll put whatever comes
back to us in a global variable. So, we'll save that:
We'll go over to the layout and then we're going to test for that
error global variable:
{{#if error_msg}}
<div class="notice error"><i class="icon-remove-sign icon-large"></i>
{{error_msg}}
<a href="#close" class="icon-remove"></a>
</div>
{{/if}}
{{#if error_msg}}
<div class="notice error"><i class="icon-remove-sign icon-large"></i>
{{error}}
<a href="#close" class="icon-remove"></a>
</div>
{{/if}}
We'll save that. Let's restart the server. And now if we go back
and I try to just click on Submit, we get missing credentials. If I
put in my username but the wrong password, we get invalid
password. Then if we put in the correct info and click on
Submit, we get You are now logged in:
This Error : Not Found above has to do with the page not being
in the route. You can look up in the instructors/classes URL and the
reason we go to that error is because back in our users.js file
and in the res.redirect route, we get into the user type. So, Brad is
an instructor so he's going to instructors/classes. Now what we
need to do is create this classes route within instructors and
students.
Now, loading that page will take care of it a little bit, but for
now, we want to finish our authentication functionality. We
need a way to see if we're logged in or not, because now that
we're logged in, we don't want the login form to show. We're
also going to want other stuff to show or not to show. So, let's
go to app.js and create something that's going to let us check to
see if we're logged in or not. And we want to access this from
anywhere, from everywhere. So, we're going to put it in app.js:
So, I'll paste it in and say app.get and then any page, or any
route. So, this is going to work in any route. We're going to
create a global variable called user. We're going to set it to
req.user or null, if they're not logged in. If we're logged in, then
we're going to get the type and we're going to put it in a global
variable called type. So, we can check for this user value to see
if they're logged in or not. Let's save that. And then we're going
to go back to the login.handlebars, which is the partial:
{{#if user}}
<h5>Welcome Back {{user.username}}</h5>
<ul class="alt">
<li><a href="/{{type}}s/classes">My Classes</a></li>
<li><a href="/users/logout">Logout</a></li>
</ul>
{{else}}
<h4>Student & Instructor Login</h4>
<form method="post" action="/users/login">
<label> Username: </label>
<input type="text" name="username">
<label> Password: </label>
<input type="password" name="password">
<input type="submit" class="button" value="Login">
</form>
{{/if}}
The above code is checking for the user variable. So, if user,
that means that they're logged in. If they are, then we want to
just say Welcome Back and then their username. Then here
we're going to have two links. One goes to My Classes. This has
that type s/classes and then also a logout link. And since we
restart the server, we have to log back in:
Welcome Back
{{#if user}}
<h5>Welcome Back {{user.username}}</h5>
<ul class="alt">
<li><a href="/{{type}}s/classes">My Classes</a></li>
<li><a href="/users/logout">Logout</a></li>
</ul>
{{else}}
<h4>Student & Instructor Login</h4>
<form method="post" action="/users/login">
<label> Username: </label>
<input type="text" name="username">
<label> Password: </label>
<input type="password" name="password">
<input type="submit" class="button" value="Login">
</form>
{{/if}}
So, let's go to our users.js route and we're just going to paste that
in. We're using a GET request to /logout and all we need to do is
call this req.logout and that'll do everything for us. And then we
want to set a message, except this should be success_msg and then
that should be fine. So, let's save that and we're going to have
to restart:
Error page 3
Error page 4
app.use('/students', students);
app.use('/instructors', instructors);
Now let's go to routes and create those two new files. One is
students.js and the other is instructors.js.
Configuring the student and
instructor route
Let's open up instructors and students files. So, in students, I'm
going to paste in code bit by bit. First of all, we'll include express
and the Router. We'll also include all the other models:
Class = require('../models/class');
Student = require('../models/student');
User = require('../models/user');
We're going to handle the students /classes route. So, let's add
router.get and say /classes as the first argument and then a function
as the second argument. In the function, we'll add the req, res, and
next arguments like this:
});
So, let's save the student and the instructor models. Then, we'll
go back to our students route. Now, in the students route, in
the router.get statement, we're going to take that Student object and
call getStudentByUsername. Then in here we're going to pass in
req.user.username:
So, this is how we can get the username of the currently logged
in user. And then second parameter will be a function, which is
going to take error and student as arguments. Then in the
function, let's check for the error. Then we're going to say
res.render students/classes and we'll pass in the student. At the bottom
of this we have to have module.exports = router: router.get('/classes',
function(req, res, next){
Student.getStudentByUsername(req.user.username,
function(err, student){
if(err) throw err;
res.render('students/classes', {student: student});
});
});
module.exports = router;
Now, I'm going to do the same thing in the instructors route.
I'm going to copy the code from the students route and paste it
into the instructors file. And we'll replace the student object with
the instructor object:
Class = require('../models/class');
Student = require('../models/student');
User = require('../models/user');
module.exports = router;
<h2>{{student.first_name}}'s Classes</h2>
<table cellspacing="0" cellpadding="0">
<thead>
<tr>
<th>Class ID</th>
<th>Class Name</th>
<th></th>
</tr>
</thead>
<tbody>
{{#each student.classes}}
<tr>
<td>{{class_id}}</td>
<td>{{class_title}}</td>
<td><a href="/classes/{{class_id}}/lessons">View Lessons</a></td>
</tr>
{{/each}}
</tbody>
</table>
Basically, we just have the user's first name, then classes, and
then a table with a list of each class and a link to view the
lessons. So, let's save that and then we'll go to our instructors
one:
<h2>{{instructor.first_name}}'s Classes</h2>
<table cellspacing="0" cellpadding="0">
<thead>
<tr>
<th>Class ID</th>
<th>Class Name</th>
<th></th>
</tr>
</thead>
<tbody>
{{#each instructor.classes}}
<tr>
<td>{{class_id}}</td>
<td>{{class_title}}</td>
<td><a href="/classes/{{class_id}}/lessons">View Lessons</a></td>
</tr>
{{/each}}
</tbody>
</table>
And this is pretty much the same exact thing except for
instructors. So, we'll save that.
So, let's see what happens if we reset the server. So, now you
see we get Brad's Classes:
Brad's classes
Error page 5
Now, you see when I click on Register for this class, it goes to
students/classes/register. And we want to make that dynamic
depending on who's logged in.
Making the link to register
dynamic
If we look in app.js, in the app.get request, we're setting the type
as a global variable. So, we can use that in the actual in the
link to register. So, let's go to views | classes and then the details
file. in this file, in the if user form, we have students. We can get
rid of that and put in {{type}} and then just an s. So, let's save
that file.
Error page 6
Making the instructor to able to
register
Now, let's go back to our instructors route. Here, we want the
instructor to be able to register. So, I'm going to paste the code
in: router.post('/classes/register', function(req, res){
info = [];
info['instructor_username'] = req.user.username;
info['class_id'] = req.body.class_id;
info['class_title'] = req.body.class_title;
Class registration
You are now registered for this class, to teach this class. It's also
showing up in the My Classes link:
Instructor registration
Now, if we look at the instructor record in the collection, we should
have a classes value. So, let's just check that out. So, let's say
db.instructors.find.pretty:
Code for the user
And then let's look at Brad. Now, you can see in classes I now have
Intro to HTML5. This has an id, it also has the class id.
In the next section, we'll do this same kind of thing for students so that
students can register for classes.
Class lessons – the last
section
We're almost there! There's one last piece of functionality that
we need and that is for lessons. We need to be able to add
them and show them. I'm logged in the classes as an
instructor:
<thead>
<tr>
<th>Class ID</th>
<th>Class Name</th>
<th></th>
<th></th>
</tr>
</thead>
<tbody>
{{#each instructor.classes}}
<tr>
<td>{{class_id}}</td>
<td>{{class_title}}</td>
<td><a href="/classes/{{class_id}}/lessons">View Lessons</a></td>
<td><a href="/classes/{{class_id}}/lessons">View Lessons</a></td>
</tr>
{{/each}}
</tbody>
So, now we have Add Lesson. If I click on the Add Lesson link,
we're going to get an error:
Error page 7
<h2>Create An Account</h2>
{{#if errors}}
{{#each errors}}
<div class="notice error"><i class="icon-remove-sign icon-large">
</i> {{msg}}
<a href="#close" class="icon-remove"></a></div>
{{/each}}
{{/if}}
<form id="regForm" method="post" action="/users/register">
<div>
<label>Account Type</label>
<select name="type">
<option value="student">Student</option>
<option value="instructor">Instructor</option>
</select>
</div>
<br />
<div>
It's going to be a lot smaller, so let's edit it. This will just say Add
Lesson as the heading. We'll just get rid of error code snippet.
Then the action is going to be
/instructors/classes/{{class_id}}/lessons/new:
<h2>Add Lesson</h2>
<form id="regForm" method="post"
action="/instructors/classes/{{class_id}}/lessons/new">
Noe, let's get rid of Account Type div and we're going to have Lesson
Number in the first name div. So, that's going to be a text field and
then the name is going to be lesson_number and you can get rid of
the value:
<h2>Add Lesson</h2>
<form id="regForm" method="post"
action="/instructors/classes/{{class_id}}/lessons/new">
<div>
<label>Lesson Number: </label>
<input type="text name="lesson_number">
<div><br />
Then what I'm going to do is just copy that and then get rid of
the rest of the divs in the file except for the Submit button at
the bottom. Then we'll just paste in the code two more times.
The second div will be the Lesson Title, and the last one will be
the Lesson Body. In the Lesson Body, we're going to be have text area
so we can get rid of the input type:
<h2>Add Lesson</h2>
<form id="regForm" method="post"
action="/instructors/classes/{{class_id}}/lessons/new">
<div>
<label>Lesson Number: </label>
<input type="text name="lesson_number">
<div><br />
<div>
<label>Lesson Number: </label>
<input type="text name="lesson_number">
<div><br />
<div>
<label>Lesson Number: </label>
<input type="text name="lesson_number">
<div><br />
<div>
<label>Lesson Number: </label>
<input type="text name="lesson_number">
<div><br />
<div>
<input type="submit" value="Add Lesson">
</div>
So, let's save that and make sure that the form actually shows
up. So, we'll go to the app and we want to be logged in as an
instructor and click on the Add Lesson button:
So, there's our form. Now, if we look at the source code, you
can see the id is in the action:
<h2>Add Lesson</h2>
<form id="regForm" method="post"
action="/instructors/class/56eabbe5616af54604008710/lessons/new">
});
req.flash('success_msg','Lesson Added');
res.redirect('/instructor/classes');
});
So, now let's go to our class.js to add the Add Lesson model.
Configuring the class model
for the Add Lesson model
In class.js, we're going to go to down at the bottom and put in
the Add Lesson model. It's going to take in the info array and then a
callback. Then, we're going to say class_id = info ['class_id']:
// Add Lesson
module.exports.addLesson = function(info, callback){
class_id = info['class_id'];
Now, we are going to copy the class_id and paste it three times.
The second one's going to be lesson_number. Then, we'll have
lesson_title and lesson_body:
// Add Lesson
module.exports.addLesson = function(info, callback){
class_id = info['class_id'];
lesson_number = info['lesson_number'];
lesson_title = info['lesson_title'];
lesson_body = info['lesson_body'];
}
Class.findByIdAndUpdate(
class_id
{$push:{"lessons":{lesson_number: lesson_number, lesson_title:
);
Class.findByIdAndUpdate(
class_id
{$push:{"lessons":{lesson_number: lesson_number, lesson_title:
lesson_title,...}
{safe: true, upsert: true},
callback
);
}
Changing the button text
So, now let's change the text of the button to Add Lesson. So, we'll
go to the newlesson.handlebars file and in the button div, we'll say Add
Lesson in place of sign up: <div>
The final page There we go, the lesson has been added. Let's check it out in the
Mongo shell.
Checking the added lesson in
the Mongo shell
So, we'll go inside the Mongo shell and let's make sure we're
using the right database: > use elearn
switched to db elearn
User code
Error page 8
Viewing lessons in the class
For this, we'll go back to the classes route and let's paste the Get
Lesson code: //Get Lesson
router.get('/:id/lessons/:less_id', function(req, res, next) {
Class.getClassById([req.params.id],function(err,classname){
var lesson;
if(err) throw err;
for(i=0;i<classname.lessons.length;i++){
if(classname.lessons[i].lesson_number ==
req.parms.lesson_id)}
lesson = classname.lessons[i];
}
}
res.render('classes/lesson', { class: classname,lesson: lesson });
});
});
So, we're going to go to the id/lessons and then the lesson_id. Then
we'll say Class.getClassById, we're getting the class. And then we'll
loop through the lessons and we're going to get the specific
lesson that matches the ID. Actually, you know what, this
should actually be req.params.lesson_id. Let's save that. Then create,
inside the classes ( views folder), we're going to create a file and
save it as lesson.handlebars. I'm going to paste the code in: <a
href=/classes/{{class._id}}/lessons">Back to lessons</a>
<h2>{{lesson.lesson_title}}</h2>
{{lesson.lesson_body}}
With this in place, we have now reached the end of the book. I
hope you enjoyed the different projects we discussed
throughout the book.
Other Books You May Enjoy
If you enjoyed this book, you may be interested in these other
books by Packt:
ISBN: 978-1-78712-814-9
ISBN: 978-1-78839-508-3