This presentation can be viewed here
-
Create a Basic App From Scratch With the Express Framework
-
Configure an Express Application's Settings
-
Render Views
-
require
and Mount (use
) Middleware -
Describe the Request/Response Cycle in an Express App
-
Use the Express Generator to Scaffold a Skeleton App
- Intro to Express
- Setup our App
- Express "Hello World"
- Basic Structure of an Express App
- Our First Route
- The Route's Callback Function
- Define a Simple Route (Practice)
- URL Parameters
- Query String Values
continued on next slide...
- Ways to Respond to a Request
- Rendering Views
- Passing Data to a View / Todos App
- Express Middleware
- The Request/Response Cycle in Express
- Adding Our Own Middleware
- Modules (Practice)
- Express Generator
- Bonus: Best Practice Routing
-
Express is the most popular web framework for Node.js.
-
It is minimalistic and lightweight, especially when compared to massive frameworks like Django and Rails.
-
Express uses Node's built-in HTTP server, but extends its capability by giving us the ability to:
- Define Routes
- "Process" requests using middleware
- Render dynamic views
-
Create a folder and cd into it:
$ mkdir first-express $ cd first-express
-
Create our
package.json
. Accept the defaults, except for the entry point - set this to be "server.js":$ npm init
-
Open the current folder in the code editor:
code .
-
Use
npm
to install the Express module in this project:$ npm install express
-
Create a
server.js
to put our web app's main code in:$ touch server.js
-
To test our setup, let's make our app return "Hello World!" when we browse to
localhost:3000
. Inserver.js
:// Load express var express = require('express'); // Create our express app var app = express(); // Define a root route directly on app // Later, we will use the router object app.get('/', function(req, res) { res.send('<h1>Hello World!</h1>'); }); // Tell the app to listen on port 3000 app.listen(3000, function() { console.log('Listening on port 3000'); });
-
Run the app:
$ node server
-
Browsing to
localhost:3000
will hit our app's root route that we defined and return "Hello World!".
-
Here is a helpful outline of what we need to do in our main Express app file - let's put this guide right in our
server.js
:// Require modules var express = require('express'); // Create the Express app var app = express(); // Configure the app (app.set) // Mount middleware (app.use) // require and mount (app.use) routes // Tell the app to listen on port 3000 app.listen(3000, function() { console.log('Listening on port 3000'); });
-
Now let's update our route to return "Hello Express" instead of "Hello World":
app.get('/', function(req, res) { res.send('<h1>Hello Express</h1>'); });
-
If you refresh the page, you'll see that it still says "Hello World!" - what's up? Well, unlike with Django, Node does not automatically restart the server for us when we make changes to our code.
-
Of course there are utilities to perform the restart for us, but until we install one later this week, get used to stopping the server with
control-c
and restarting it.
-
Looking at our first route in our editor, note that we are defining a route using the
get
method on the Expressapp
object. Later, we will learn a preferred way of defining routes using the ExpressRouter
object, but you need to be aware of defining routes this way because you will see it quite often. -
Besides the
get
method, there are other methods such aspost
,put
anddelete
, that map to the other HTTP verbs.
-
In the case of our first route, we have specified a HTTP method of
get
and a path of/
. -
Only HTTP get requests matching a path of
/
(root path) will invoke the callback function.app.get('/', function(req, res) { res.send('<h1>Hello Express</h1>'); });
-
Again, looking at our first route:
app.get('/', function(req, res) { res.send('<h1>Hello Express</h1>'); });
-
The route's callback function will be executed if a matching HTTP request (HTTP Verb + Path) comes along.
-
As usual, instead of an anonymous function for the callback, we can always use a named function if we wish:
app.get('/', handleRoot); function handleRoot(req, res) { res.send('<h1>Hello Express</h1>'); }
-
The route's callback function defines two parameters, the first representing the request object, the second the response object:
app.get('/', function(req, res) { res.send('<h1>Hello Express</h1>'); });
-
These two arguments are automatically provided to the callback by Express.
- The
request
object has properties and methods pertaining to the HTTP request and we use theresponse
object primarily to send back our app's response to the request.
- The
-
Because they are just parameter names, you can change them. For example, feel free to use
request
forreq
andresponse
forres
:app.get('/', function(request, response) { response.send('<h1>Hello Express</h1>'); });
-
Define another route that matches a
get
request to a path of/goodbye
that sends a text response of "Goodbye World". -
Don't forget to restart the server and test your new route by browsing to
localhost:3000/goodbye
.
-
Is it okay to define more than one route on the same path?
For example:app.get('/cars', function(req, res) { res.send("Here's a list of my cars..."); }); app.post('/cars', function(req, res) { res.send('Thanks for the new car!'); });
-
Remember URL parameters in Django? You know, the stuff like
<int:id>
in the URL path? -
In Express, we define them in the path string using a colon, followed by the parameter name:
-
Let's add a route with a URL parameter called
name
:app.get('/goodbye/:name', function(req, res) { res.send('Goodbye ' + req.params.name); });
-
Notice how the request object (
req
) in Express has aparams
object that we can use to access the value matching the URL parameter's segment.
-
Restart and check it out:
localhost:3000/goodbye/PeeWee
-
For what purpose did we commonly use named parameters in routes in Django?
-
Now, you will commonly use code like
req.params.id
for accessing a model object's id.
-
A URL has a query string when it contains a ? character.
-
There are keys, called query parameters, with values assigned after an equals character. Multiple key/value pairs separated by an ampersand. For example:
http://www.host.com/list?category=flowers&order=date
-
In the above example, What are the query parameters and values?
-
In Express, we access the query string parameters using the
query
object attached to the request object. Let's modify our root route to try this out:app.get('/', function(req, res) { var msg = req.query.msg ? req.query.msg : '!'; res.send('<h1>Hello Express ' + msg + '</h1>' ); });
-
What can we type in the address bar to test this route?
-
Important: Query strings have zero influence on routing - they are not considered part of the path!
-
Later you'll see that data being sent in the HTTP request's body, for example, the data being posted from a form, will be accessed via
req.body
thanks to thebody-parser
middleware (more on this later). -
In summary:
req.params
: Used to access URL parameter valuesreq.query
: Used to access query string valuesreq.body
: Used to access request body contents
-
So far we have responded in our route handler (callback) code by using the
send
method on the res (response) object. -
Here is a list of other methods that can be used to terminate the request/response cycle:
res.redirect()
- Redirect a requestres.render()
- Render a view templateres.json()
- Send a JSON responseres.jsonp()
- Send a JSON response with JSONP supportres.send()
- Send a response of various typesres.sendFile()
- Send a file as an octet stream
-
The most commonly used are the first three.
-
Let's change our
/goodbye
route to returnjson
instead of plain text:app.get('/goodbye', function(req, res) { res.json( {msg: 'Goodbye World'} ); });
-
Try it out!
Django | Express |
---|---|
Model | Model |
View | Controller |
Template | View |
With Express, we will be following the very popular Model-View-Controller (MVC) design pattern to organize our code.
-
We use the
render
method on the response object to render templates. -
Express can work with a multitude of view engines.
-
Pug
(formerlyJade
) is a template language that leverages indentation to create HTML with a "shorthand" syntax. -
When we scaffold an app using the Express Generator (more on this later), Pug is the default because it is written by the same fine people that brought us the Express framework.
-
EJS
"Embedded JavaScript" templates - which are sweet like bear meat!
-
Now let's try out views in Express by rendering a
home
view for the root route. -
Since we're going to organize our code based upon MVC, we will put all view templates into a
views
folder:$ mkdir views $ touch views/home.ejs
-
ejs
is the file extension for the EJS view engine.
-
VS Code includes Emmet and EJS support, so you will be able to type
!
and press tab to generate the HTML boilerplate inhome.ejs
:<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>First Express</title> </head> <body> </body> </html>
-
Unfortunately, EJS does not have the concept of
"{% extends 'base.html' %}" like Django does. So do we have to include boilerplate in every EJS template? -
There are packages that can be installed for implementing "layouts", however, EJS does have "partial" views built in.
-
We will cover partial views later, however, if you want to check them out before then, check out the
include
function here.
-
Add an
<h1>
inside the<body>
so that we see something :)<body> <h1>Home Page</h1> </body>
-
Okay, now let's modify our callback in our root route to render our new
home.ejs
template:app.get('/', function(req, res) { res.render('home'); });
-
We use the
render
method to render templates! -
Just the file name, not the
ejs
extension. -
Restart, browse, why didn't it work?...
-
We're going to get a little practice reading Express errors in this lesson.
-
Express' errors sometimes won't be 100% helpful, but this one, Error: No default engine was specified..., makes it clear that we need to specify a view engine.
-
This is our first opportunity to configure our app:
// Configure the app (app.set) app.set('view engine', 'ejs');
-
The
set
method on theapp
object is used to configure an Express app's settings...
-
We also need to tell Express where are views can be found:
// Configure the app (app.set) app.set('view engine', 'ejs'); app.set('views', path.join(__dirname, 'views'));
-
Don't be intimidated by this code:
path.join(__dirname, 'views')
...
-
path.join
is just a Node method that builds a properly formatted path from segment strings passed to it.__dirname
is always available and represents the path of the current folder where the currently running code lives; andviews
is the name of the folder we created to hold our views. -
path
is a core Node module, but it still must be required before we can use it...
-
Core Node modules don't have to be installed with
npm install
, but we do have torequire
them:// Require modules var express = require('express'); var path = require('path');
-
Restart and let's see what the next error is...
-
Error: Cannot find module 'ejs' - this error is telling us that we need to install the EJS view engine package:
$ npm i ejs
-
The
i
is a short forinstall
. -
We don't need to
require
the view engine - Express knows how to find it. -
Restart and bam!
-
View engines are used to dynamically render views, usually incorporating provided data, on the server before sending it to the client.
-
We just used the
render
method, passing in the view name as an argument. -
We can also pass in a JavaScript object as a second argument, and all of it's properties will be available for use directly in the view within
ejs
tags! This is pretty much like Django'srender
!
-
Let's add a route to display a list of To Do's (still in server.js):
var todos = [ {todo: 'Feed dogs', done: true}, {todo: 'Learn Express', done: false}, {todo: 'Have fun', done: true} ]; app.get('/todos', function(req, res) { res.render('todos/index', { todos: todos }); });
-
Note the JS object provided as the second argument.
-
Note that we are putting the array of todos within server.js, this would not be a great practice.
-
What would be a better approach?
-
Don't worry, you'll get a chance to refactor soon enough...
-
The string
todos/index
points to the template. We're going to have to create a folder inside of our views folder namedtodos
and add a filed namedindex.ejs
to it:$ mkdir views/todos $ touch views/todos/index.ejs
-
Now let's code our
todos/index.ejs
. Start by copying over the HTML fromhome.ejs
and fix it up to look like this:<body> <h2>Todos</h2> <ul> <% todos.forEach(function(t) { %> <li> <%= t.todo %> - <%= t.done ? 'done' : 'not done' %> </li> <% }); %> </ul> </body>
-
That my friends is embedded JavaScript between those
<% %>
and<%= %>
tags and I believe you are going to love their simplicity! -
The
<% %>
tags are for executing JavaScript such as control flow. -
The
<%= %>
tags are for writing into the HTML page. -
Restart and browse to
localhost:3000/todos
- not bad :)
-
If you have data that you want available to all views, the Express
app
object has alocals
object on it that you can add properties to. -
Let's see how we can use
locals
to provide our app'stitle
. Inserver.js
:app.set('views', path.join(__dirname, 'views')); // new code below app.locals.title = 'First Express';
-
Then in both
home.ejs
andindex.ejs
, update the<title>
element in the<head>
:<meta charset="UTF-8"> <title><%= title %></title>
Restart and check out the browser tab!
-
To demonstrate how we can add a todo (albeit, not persisted in the case of a server restart), let's add a
<form>
element toindex.ejs
below the</ul>
tag:... </ul> <form action="/todos" method="POST"> <input type="text" name="newTodo"> <input type="submit" value="Add Todo"> </form> </body>
-
FYI, with Express, we will often ignore the HTML best-practice of kabob-casing input names so that we can easily map them to JS object properties!
-
Now we need to create a route to handle the request that's going to be sent by submitting the form.
-
What will this new route's HTTP method (verb) be?
-
What will the URI be?
-
Let's stub it up like so:
app.post('/todos', function(req, res) { // for now, let's just redirect res.redirect('/todos'); });
-
Why a
redirect
instead of arender
?
-
Let's open DevTools in Chrome and examine the network request sent when submitting the form.
-
Checking the
Content-Type
of the Request Headers will show that by default, an HTML<form>
's data is submitted to the server in a format known asapplication/x-www-form-urlencoded
. -
Scroll down and check out the Form Data being sent in the request body.
-
Now on the server, let's try logging out
req.body
to verify we are receiving the body data:app.post('/todos', function(req, res) { console.log(req.body); res.redirect('/todos'); });
-
Bummer, there is no
body
property on the request object...
-
Unlike the
req.params
andreq.query
objects that we used earlier, Express by default does not parse the body for data by default. -
This is due to Express's minimalistic approach. It does not provide much functionality by default - we get to pick and choose what we want our app to spend time doing!
-
The solution though is middleware!
-
Each request in an Express app is essentially processed by a series of middleware functions.
-
Even our route handler functions are considered to be middleware - it just so happens they ended the request by calling
res.render
,res.json
,res.redirect
, etc. -
We'll come back to adding a todo in a bit, but let's first take a closer look at middleware and the request/response cycle in Express.
-
Middleware are functions that execute on each request made to the server.
-
You can add any number of middleware that you want or need to process the request.
-
Middleware will process the request, one by one, in the order they were mounted by with
app.use()
.
-
Middleware can be used to, log info, compile sass/less (css), do authentication, make changes to the req/res object, etc.
-
Once a piece of middleware has done its job, it either calls
next()
to pass control to the next middleware in the pipeline or ends the request as we've been doing with therender
/redirect
methods.
-
Just to demonstrate, let's write and mount a simple middleware to log out the
user-agent
of each request:// Be sure to mount (app.use) before routes app.use(function(req, res, next) { console.log(req.headers['user-agent']); next(); });
-
Note that we must call the
next
function (passed in by Express) after the middleware has accomplished its task - otherwise the request will stop dead in it's tracks! -
Restart, refresh - neato!
-
morgan: Logger that logs requests.
-
body-parser: Parses the body so that you can access data being sent in the request body with the
req.body
object. -
cookie-parser: Populates a
cookies
property on the request object so that you can access data in cookies. For example,req.cookies.sid
. cookie-parser is middleware which deals with the incoming request. To set a cookie, you would use thecookie
object on the response object. -
serve-favicon: Serves the favicon from route /favicon.ico.
-
Before we install body-parser, let's mount Express'
express.static
middleware so that when the client requests any static assets, such as CSS, JavaScript, image or HTML files, it will immediately send the requested asset to the client:app.use(express.static(path.join(__dirname, 'public')));
-
That's all there is to it! Now, all we have to do is put our static assets into a folder named
public
and the middleware will return the asset when it is requested by the browser. -
Let's check this out...
-
Let's create a
public
folder and aabout.html
file inside of it:$ mkdir public $ touch public/about.html $ echo "<h1>About Page</h1>" > public/about.html
-
Restart the server and browse to
localhost:3000/about.html
to test it out. -
Note that we do not include "public" when specifying the path to the resource.
-
Okay, let's get back to adding new todos!
-
We're going to need that body-parser middleware if we want to add a todo.
-
Body-parser middleware is one of only a couple of middleware built into Express.
-
When the body of the HTTP request contains data, body-parser middleware is used to parse that data and put it on the request object.
-
There are two body-parser middleware functions available on the express module:
- express.json(): Used to parse JSON payloads (
Content-Type=application/json
) - express.urlencoded(): Used to parse urlencoded payloads (
Content-Type=application/x-www-form-urlencoded
)
- express.json(): Used to parse JSON payloads (
-
Let's mount the
body-parser
middleware to process both application/json and application/x-www-form-urlencoded data in the body:app.use(express.static(path.join(__dirname, 'public'))); // new code below app.use(express.json()); app.use(express.urlencoded({extended: true}));
-
Note that the
json()
andurlencoded()
methods will return a middleware function that is then mounted byapp.use
.
-
With the body-parser middleware mounted, we can now use the
body
object on the request to access the new Todo being submitted and add it to thetodos
array:app.post('/todos', function(req, res) { console.log(req.body); todos.push({ todo: req.body.newTodo, done: false }); res.redirect('/todos'); });
-
Now add a todo! Be sure to check what was logged out in the server terminal too.
-
When Express receives a request that matches a route, the request will be processed by stack of ______________.
-
Middleware are not JavaScript strings, they are ____________.
-
Are route handler functions middleware too?
-
Okay, so we've had big fun getting an Express app up and running from scratch.
-
We've included some basic routes and even mounted some common and custom middleware!
-
In this part of the lesson we'll take a look at how a tool,
express-generator
, structures an Express app and mounts key middleware by default. -
Think of
express-generator
as a very lightweightdjango-admin startproject...
-
express-generator
is a command line tool that quickly generates a skeleton Node app that incorporates the Express framework. -
Let's install it:
$ npm install express-generator -g
-
express-generator
has a CLI that we want to be able to run from any project, that's why we install it using the global-g
flag.
Let's take a look at the options available to us:
$ express -h
Usage: express [options] [dir]
Options:
-h, --help output usage information
-V, --version output the version number
-e, --ejs add ejs engine support (defaults to pug)
--hbs add handlebars engine support
-H, --hogan add hogan.js engine support
-c, --css <engine> add stylesheet <engine> support (less|stylus|compass) (defaults to plain css)
-f, --force force on non-empty directory
-
We are going to generate a new app, so let's cd up and out of our
first-express
app. -
We will use the
-e
option to use the ejs template engine instead of pug. -
From your new app's parent directory:
$ express -e second-express $ cd second-express
-
Our scaffolded folder structure will look like this:
├── app.js ├── bin │ └── www ├── package.json ├── public │ ├── images │ ├── javascripts │ └── stylesheets │ └── style.css ├── routes │ ├── index.js │ └── users.js └── views ├── error.js └── index.js
-
Let's explore the above structure in our text editor...
-
A quick look at the
package.json
file reveals the default modules Express has set up. -
These modules are not installed in the
node_modules
folder by default. -
As we saw earlier,
$ npm install
without specifying a package name will install the modules listed inpackage.json
into thenode_modules
folder. Do it.
-
Starting a generated Express app properly is slightly different than what we've seen.
-
Type
npm start
. This will execute the start script specified in package.json. -
npm start
, then browse tolocalhost:3000
.
-
What's with this
./bin/www
file? Well, the Express team decided to partition out the HTTP server related code out ofapp.js
to remove code that's not really key to our app. -
Take a look at how the Express app in
app.js
exports itself and how it is required insidewww
(no file extension - weird, but true). -
Normally, we don't need to make many changes inside of
www
. We will mess with it a bit when we look at doing a realtime app, but for now, we are just going to change our app's file name...
-
In MERN/MEAN Stack apps, Angular's main module is often named
app.js
and this could get confusing having twoapp.js
files. This is why many developers name their main Express fileserver.js
. -
So let's rename it...
-
First, rename
app.js
toserver.js
. -
Then, inside of
www
, change line 7 from:var app = require('../app');
to:
var app = require('../server');
-
That's it! Restart and test.
Review briefly, then on with the picker:
- What method do we call to render a view and on what object does that method exist?
- In an Express app, the request passes through a "stack" of __________.
- What function signature does a middleware function have? (what arguments are there and what do they represent)
- What method do we call to mount middleware?
- Does it matter what order middleware is mounted?
- How are functions that handle routes different from other middleware?
-
It's been a long day, but if you're still looking for more, feel free to continue this presentation to learn about best practice routing...
-
If not, don't worry, we will cover routing in more detail in the coming days.
-
There are several ways to set up routing in an Express app.
-
In our
first-express
app, we used Express'app.get
andapp.post
methods to mount our routes. -
Express also provides a
Router
"factory" function that creates instances of "routers". -
The router objects can then be used to provide more flexible and powerful routing.
-
As a model example of using this better approach, let's look at how the
express-generator
sets up its routing. -
First, there's a
routes
folder containing _____________? -
Next, those route modules are required on lines 7 & 8 of
server.js
. -
Let's take a look at what those modules export...
-
Yes, those modules export instances of Express'
Router
object after they have had their specific routes defined withget
methods, just like we did withapp.get()
. -
Lastly, the routers are mounted in the middleware stack with the
app.use
method in lines 22 & 23 like this:```js app.use('/', indexRouter); app.use('/users', usersRouter); ```
-
Developer Reasoning: What do you suppose the Express router object really is? Discuss with your pair for a minute.
-
It's important to understand that the path specified in the `app.use` is **combined** with the path specified on the router objects...
### The Express Router Object (cont.)
-
Although MEAN/MERN Stack apps have very little convention, pledge that you will define RESTful routes whenever possible.
-
Thank you!
Let's refactor our first-express application to use the Router object as modeled by the app generated using express-generator:
-
Modularize with a module. Create a
routes
folder and name moduleindex
. -
Export an instance of
Router
-
Mount the router instance using
app.use
with a path of/
.
continued...
If you haven't already, put the todos related routes in it's own module.
Bonus: Get the router.post route to work! Hint: You can require a module wherever you need access to it!
Note: When searching for info on the Express framework, be sure that you search for the info for version 4 only - there were significant changes made from earlier versions.