Traditionally servers generated the content that was viewed in a browser. Another words all the HTML/CSS and JS that were to rendered and executed in the browser was sent to the browser when the page was initially loaded.
For the user to get new content they would make an HTTP Request to the server and the server would generate the content for the complete page.
There are some good reasons to generate content on the server. Some of these are:
-
Load time. Many apps load tons of JS when they are first loaded in the browser. It can take awhile for this javascript to load and execute.
Often, the server will generate initial content a user views when they go to a site. Then, after this initial load Ajax will be used to update the site with new and updated content.
-
Predictable performance. Some browers are hopelessly slow and buggy. Older browser are still used in many places all over the world. I'm talking crazy old, Netscape Navigator, IE4 old. Not everyone has modern browsers or devices that run JS efficiently.
-
Caching We can cache content generated with from the server. And caches can be smoking fast.
-
Delivering content to the user right from the user's machine, the Browser cache.
-
Delivering content from a machine geographically close to the user. From a machine in the next town over! This is why we use Content Delivery Networks (CDN).
-
Delivering content without executing code in the web application. Save the generated HTML in a file on the server and give it to the browser as needed.
-
-
RESTful The web is a highly scalable distributed system. Part of it's RESTful architectural style is that it acts like a big state machine. Where users navigate to each page/node by following links/edges.
This is easier to implement when server are generating content.
-
SEO (Search Engine Optimization) Google and other denizens of the net crawl sites and allow user to find your site based on your site's content.
This has tradionally been done by sending HTTP Requests to the server and having the content generated by the server processed by such crawlers.
There are ways to do SEO for sites that don't generate server side content, but it's somewhat more complex.
In this excercise, we will be using two modules: Jade and File System.
Jade is a server-side view engines. Much like we used handlebars on our front end to bind data to a template. We will be creating templates as .jade
files, and passing them data in our express routes.
File System is a module that allows you to read and write to files in your node server.
We will mostly be focusing on Jade in this lesson, but we will make use of FS later on.
Let's go ahead and install these:
npm install
npm install jade
https://nodejs.org/api/fs.html
Nothing new here. Just a reminder.
var mongoose = require('mongoose');
mongoose.connect('mongodb://localhost/contacts');
var express = require('express');
var app = express();
var bodyParser = require('body-parser');
var jsonParser = bodyParser.json();
Now, we need to import these modules into our app.js
. Near the top of our page, let's add:
var jade = require('jade');
var fs = require('fs');
// we set our view engine here
app.set('view engine', 'jade');
app.set('views', './templates');
Notice that we use app.set to tell express that we are going to use the jade
templating engine.
And that the jade
views will be located in the templates
directory.
// Add the Contact model
var Contact = require('./lib/contacts.js');
// Add the Utilities module, lot's of helpful function be here.
var util = require('util');
We want our server to both render json AND render html depending on which route a user hits. We can accomplish this the way we talked about earlier, where we specifically type out each individual route with its appropriate base, or we can spin off our API functionality into a separate router. Let's go behind door number 2.
First, we create our new router:
var apiRouter = express.Router();
Next, let's attach all of our previous routes to our new router instead of our express app. Your new routes will look like something like this:
apiRouter.get('/', function(req, res){
res.json({name:'Hello World!'});
});
Finally, we need to mount our new apiRouter on our express app and apply our '/api' base. This syntax is taken straight from Thursday's lesson:
app.use('/api', apiRouter);
Up until now we've been generating only JSON on the server. But, tradionally web apps generate HTML, CSS and JS on the server. The generated code and content is sent to the browser in the body of the HTTP Response.
This will create a route for '/'.
app.get('/', function(req, res) {
res.render( 'index', {name: "Max", message: 'Welcome to our contacts page! I hope you have a good stay.'});
});
The render method is added by the Jade templating module.
It will:
-
The first argument, 'index', will find the index.jade file in the templates directory. Remember we told Express which directory the templates lived in above.
-
The second argument, the object literal
{name: ...}
, will be passed to the Jade template Engine and be made availabe inside of the index.jade template. -
The Jade Template Engine will process the index.jade file and generate HTML. More below on how it's processed.
-
The HTML generated from the last step will be returned to the browser in the HTTP Response.
Add this to the templates/index.html
html
head
title #{name}'s Contact Warehouse
body
h1 #{name}'s Contact Warehouse
h4 #{message}
-
White space is VERY important in Jade. It sets the structure of the HTML document that is produced.
-
Proper indentation is a MUST. Errors will occur otherwise.
-
The layout of the jade file reflect the parent-child relationships that exist in the DOM.
- The body tag is the parent of h1 tag, h1 tag is a sibling of the h4 tag, etc.
-
The pound curly-brace syntax substitutes a variable's value into the generated HTML.
- Like string-interprelation in Ruby, ay.
Jade allows us to do some interesting things with templates. Let's explore a few of its features.
Jade's extend
and block
functions allow us to wrap one jade template around another.
Let's see this practice in action. Touch a new template called layout.jade
.
Our Layout:
html
head
title Contact Warehouse
body
header
h1 Contact Warehouse
block main
Notice the block main
line.
Now let's modify our index.jade:
extend layout
block main
h1 #{name}'s Contact Warehouse
h4 #{message}
When we call res.render
on our index.jade
file now, Jade will wrap our layout.jade
around this template and substitute our index code where we had block main
.
Read the Jade Tutorial and/or watch Jade Tutorial, Video.
Create a route in your articles app for '/'. Of course it will have a handler function.
The first argument to render would be for the name of the jade file to process, index
.
The second argument to render would be an object literal, {name: 'Joe', message: "Welcome to Joe's blog"}
,
Don't forget to create a jade template file in the templates directory. Yes, it will look like the templates/index.jade we created above
Go to /
in you browser to check that the proper HTML was generated and returned to your browser.
Once you do this, create a layout.jade
and extend it onto your index.jade
template.
Rendering our list of contacts will take a few more tricks of the trade.
First, we need to create our route. We are indexing all our contacts, so our route will be to '/contacts'. Inside of our route, we will pull all of our contacts from our database, and pass them into our .render
method to be compiled with our template.
app.get('/contacts', function(req, res) {
Contact.find({}, function(error, contactList) {
res.render( 'contacts', {contacts: contactList});
});
});
Now, we need to create our contacts.jade
template itself. We first extend our layout into our contacts template. Then, we need to iterate through all of our contacts. The syntax for doing so is:
for contact in contacts
Our second hiccup comes when we go to render the various address of our contacts. Not only do we want to iterate through these, but we want to check to see if they exist, and if not, we don't want to render any references to them.
Jade allows you to use javascript right into your templates. We won't go too deeply into the specifics of this, but we will use conditional logic to check to see whether or not our contacts have certain sets of contact information. Here's email addresses for example:
if (contact.emailAddresses.length > 0)
h4 Email Addresses:
//- // iterating through email addresses
for email in contact.emailAddresses
p #{email.emailAddressType}: #{email.emailAddress}
We check to see if the array has any elements (remember, an empty array is truthy), then we render iterate through all of our email addresses and render them accordingly.
All together, our template will look like:
extend layout
block main
h1 Contacts
.contacts
for contact in contacts
h3 Name: #{contact.firstName} #{contact.lastName}
h4 Title: #{contact.title}
//- // checking to see if we have email addresses
if (contact.emailAddresses.length > 0)
h4 Email Addresses:
//- // iterating through email addresses
for email in contact.emailAddresses
p #{email.emailAddressType}: #{email.emailAddress}
if (contact.phoneNumbers.length > 0)
h4 Phone Numbers:
//- // iterating through phone numbers
for phone in contact.phoneNumbers
p #{phone.phoneNumberType}: #{phone.phoneNumber}
if (contact.addresses.length > 0)
h4 Addresses:
//- // iterating through addresses
for address in contact.addresses
p #{address.street},
p #{address.city}, #{address.state} #{address.zipCode}
p #{address.country}
Now, let's go to http://localhost:3000/contacts
and make sure that it works!
Create a route in your articles app for '/articles'.
The first argument to render would be for the name of the jade file to process, articles
.
The second arguement would be the an object literal containing the array of articles that you would have pulled from your database.
Then, create your articles.jade
template by following the pattern outlined above.
Go to /articles
in you browser to check that the proper HTML was generated and returned to your browser.
Now that we are showing all of our contacts, it is time to create some new ones. Not only that, but we want to have the new contacts appear on our contacts index without us having to refresh the page. Let's start with our form.
Touch a new file in your templates folder called contact-form.jade
.
Jade allows you to add attributes to html tags with this syntax:
form(name="new-contact")
With this is mind, we will generate our form as so:
h1 Add new contact
form(name="new-contact")
p First Name:
input(type="text", name="first-name")
p Last Name:
input(type="text", name="last-name")
p Title:
input(type="text", name="title")
input(type="submit", value="contact")
In addition to allowing you to extend a template onto another one, jade allows you to import the body of one template into another with include
.
Let's to back to our contacts.jade
file and add the line include form.jade
*inline with block main
to include the the form in our template.
Then, let's start up our server and make sure the form is included in our contacts view.
Next, let's create another jade template called 'ajax.jade'. In this template, let's create a script tag that will generate our client-side javascript. Yes, we are using server-side javascript to generate client-side javascript.
The syntax to create a script tag in jade goes as follows:
script(type="text/javascript").
All of our javascript withh go underneath and tabbed one line over from this tag.
Our ajax call includes nothing unique to jade or node:
$(document).ready(function(){
$('form[name="new-contact"]').on('submit', function(event){
event.preventDefault();
var contact = {
firstName: $('input[name="first-name"]').val(),
lastName: $('input[name="last-name"]').val(),
title: $('input[name="title"]').val()
};
$.ajax({
method: 'POST',
url: 'http://localhost:3000/contacts',
data: JSON.stringify(contact),
contentType: "application/json; charset=utf-8"
}).done(function(response){
$('.contacts').append(response);
})
});
});
Please notice a detail though: our callback is expecting to recieve a string of html back. How do we do this? Let's go back to our app.js
to find out.
We start with our route handlers:
app.post('/contacts', jsonParser);
app.post('/contacts', function(req, res) {
});
We are expecting json, so we include our jsonParser middleware.
Next, we write our mongoose code to create a new contact document:
Contact.create(req.body, function(error, contact) {
if (error) {
console.log(error);
res.sendStatus(400);
} else {
// nothing here yet!!!
});
};
});
We handle any errors that may come up in the creation process, but we have yet to tell our server to do anything after the contact document gets created.
Here's where our File System module comes into play. We are going to use it to read our contact
template:
fs.readFile('./templates/contact.jade', 'utf8', function (err, data) {
if (err){
res.sendStatus(400);
};
// nothing to see here
});
.readFile
is a method of fs that does exactly what you'd expect. We pass it a path to the file we want it to read, the type of encoding of the file, and a callback that takes both an error and the contents of the file as 'data'. In the callback, we handle the error first, then we will compile the template using jade. That comes next.
Compiling our template may sound intimidating, but the syntax is almost identical to what we used with handlebars. We pass the jade template (stored as the variable 'data') into jade.compile
, which produces our .contactCompiler
function. Then, we call our compiler function on our newly created contact, which returns our stringified json. Finally we send the data back to our ajax request with a created header.
var contactCompiler = jade.compile(data);
var html = contactCompiler(contact);
res.send(html);
res.status(201);
All together, our route looks like:
app.post('/contacts', jsonParser);
app.post('/contacts', function(req, res) {
Contact.create(req.body, function(error, contact) {
if (error) {
console.log(error);
res.sendStatus(400);
} else {
fs.readFile('./templates/contact.jade', 'utf8', function (err, data) {
if (err){
res.sendStatus(400);
};
var contactCompiler = jade.compile(data);
var html = contactCompiler(contact);
res.json(html);
res.status(201);
});
};
});
});
Whoops, we almost forgot our contact.jade
template! In keeping our list uniform, lets just copy the code out of our contacts.jade
and make our contact.jade
look like:
h3 Name: #{firstName} #{lastName}
h4 Title: #{title}
if (emailAddresses.length > 0)
h4 Email Addresses:
for email in emailAddresses
p #{email.emailAddressType}: #{email.emailAddress}
if (phoneNumbers.length > 0)
h4 Phone Numbers:
for phone in phoneNumbers
p #{phone.phoneNumberType}: #{phone.phoneNumber}
if (addresses.length > 0)
h4 Addresses:
for address in addresses
p #{address.street},
p #{address.city}, #{address.state} #{address.zipCode}
p #{address.country}
Now, let's try it! Fire up your server, navigate over to http://localhost:3000/contacts
and try to create a contact! If the contact doesn't appear, you done goofed.
Create a post route in your articles app for '/articles'.
Create form and script jade templates.
Build out your single page upload functionality.
Go to /articles
, create an article, and watch it get rendered like magic.
Our site looks pretty hideous as is. We have no styling, and if we wanted to use more javascript, our script tag would become cumbersome and hard to manage.
When we use a grunt server, or our ruby webrick server, our whole directory is served. Node is more selective. If we want to serve up static files (Perhaps a stylesheet? Maybe some javascripts?) along with our render html, we need to explicitly tell our app to do so.
Express has built in middleware to help us accomplish this task. Calling express.static
on the path to a directory of static files we want to serve returns middleware that allows us to serve said files.
app.use(express.static(__dirname + '/public'));
In nodeland, __dirname
is a reference to the directory in which the executed file resides. Therefore, in the line above, we create the middleware that allows us to serve static files from a directory called 'public', then tell our Express app to use the middleware.
Now, our response will include whatever files we put in our 'public' directory.
Touch a 'public' directory in the root of your app, then touch a directory called 'scripts' inside of it. Next, touch a file called 'app.js' inside of your scripts directory. We're going to move our ajax call from a script tag to this file. Let's copy and past our javascript from our ajax.jade
file to our newly created scripts/app.js
file.
Finally, we need to link to our new javascript file into our layout.jade
.
Add this line:
script(src='scripts/app.js')
to the bottom of your layout, one tab over from your body tag.
Then, restart your server, open up your browser, and make sure that your app still works.
At this point, we could style our app with css stylesheet. However, a node module provides us with a more flexible, powerful option.
Stylus is a css pre-processer. It allows us to write css with variables, mixins, functions; you name it. It also frees us from having to use semi-colons or colons in our css. Anyone who has experience with SASS or LESS will find stylus familiar.
We will also be using a library for stylus call Nib. Once imported into our stylesheet, it gives us a number of premade mixins. Most importantly however, it frees us from having to use vendor prefixes with our styles. Isn't that nice?
First, we need to install both of these packages. Go ahead and run:
npm install stylus
npm install nib
Now that we have these packages in our repository, we need to import them into our server:
var stylus = require('stylus');
var nib = require('nib');
Next, we create a function that configures how Stylus processes raw .styl
files. This function is taken straight from the Stylus documentation, save for one minor addition. We expose Stylus to Nib in the last line of our function.
// creates a compile function that calls the stylus and nib middlewear in our stack
function compile(str, path) {
return stylus(str)
.set('filename', path)
.use(nib())
};
Afterwards, we need to tell our Express app to use stylus as middleware. As we pass in the middleware into our app's handler chain, we configure it by telling it both where to look for .styl
files, and what function to use to compile them. We are using the function we just created.
// we set up express to use our stylus middlewear and pass in our compile function as an object here
app.use(stylus.middleware({ src: __dirname + '/public', compile: compile }));
Now, we are all set to create a .styl
file! Touch a directory inside of 'public' called 'stylesheets'. Inside of this new directory, touch a file called 'style.styl'.
We won't get rabbit-holed into the functionality that Stylus provides, but I invite you to check out their documentation and get exploring yourself.
Instead, I have created a quick-and-dirty .styl stylesheet:
@import 'nib'
container-color = #BEB9B5
background-color = #C25B56
@import url(http://fonts.googleapis.com/css?family=Open+Sans);
body
background-color background-color
font-family
#hero
margin-left 20px
.container
width 600px
margin 50px auto
font-family 'Open Sans', sans-serif
overflow hidden
background-color container-color
.contacts
float left
width 360px
padding 20px
.form
float right
width 140px
padding 20px
Either paste this into your style.styl
file, or make your own styles. IDGAF.
Now, when we run our server, Stylus will compile our style.styl
file and create+write equivalent css to a file in the same directory called style.css
.
We aren't done yet though. We still need to link to our to-be-created stylesheet in our layout.jade
.
With this line insert into the head of our template, we should see style and color breathed into our contacts page:
link(rel='stylesheet', href='/stylesheets/style.css')
Style your articles app!
First, create a 'public' folder and configure your app to serve it with your rendered html.
Next Stylus and Nib and include them as middleware in your Express app.
Then, create your .styl
file and go to town!
Remember to add all appropriate tags to your layout.jade
file.