Skip to content

Instantly share code, notes, and snippets.

@jim-clark
Last active October 22, 2018 23:43
Show Gist options
  • Save jim-clark/30ae13449454c35dfa59aa46a4870c44 to your computer and use it in GitHub Desktop.
Save jim-clark/30ae13449454c35dfa59aa46a4870c44 to your computer and use it in GitHub Desktop.

This presentation can be viewed here



Intro to the
Express Framework
for Node


Learning Objectives

  • 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


Roadmap


  • 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...


Roadmap (cont.)


  • 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 Framework - Intro


  • 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

Setup our App


  • 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 .


Install the Express Module


  • 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

Express - Hello World!

  • To test our setup, let's make our app return "Hello World!" when we browse to localhost:3000. In server.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');
     });

Express - Hello World! (cont.)


  • Run the app:

     $ node server
  • Browsing to localhost:3000 will hit our app's root route that we defined and return "Hello World!".


Basic Structure of Express App

  • 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');
     });

Update Our First Route

  • 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.


Our First Route (cont.)


  • Looking at our first route in our editor, note that we are defining a route using the get method on the Express app object. Later, we will learn a preferred way of defining routes using the Express Router 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 as post, put and delete, that map to the other HTTP verbs.


Our First Route (cont.)


  • 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>');
     });

The Route's Callback


  • 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.


The Route's Callback


  • 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 (cont.)


  • 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 the response object primarily to send back our app's response to the request.

The Route's Callback (cont.)


  • Because they are just parameter names, you can change them. For example, feel free to use request for req and response for res:

     app.get('/', function(request, response) {
       response.send('<h1>Hello Express</h1>');
     });

Practice (3 mins)
Define Another Route


  • 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.


Question - Basic Routing


  • 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!');
     });

URL Parameters

  • 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 a params object that we can use to access the value matching the URL parameter's segment.


URL Parameters


  • 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.


Query Strings


  • 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?


Query Strings


  • 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!


Body Data in the Request


  • 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 the body-parser middleware (more on this later).

  • In summary:

    • req.params: Used to access URL parameter values
    • req.query: Used to access query string values
    • req.body: Used to access request body contents

Ways to Respond to a Request

  • 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 request
    • res.render() - Render a view template
    • res.json() - Send a JSON response
    • res.jsonp() - Send a JSON response with JSONP support
    • res.send() - Send a response of various types
    • res.sendFile() - Send a file as an octet stream
  • The most commonly used are the first three.


Ways to Respond to a Request (cont.)


  • Let's change our /goodbye route to return json instead of plain text:

     app.get('/goodbye', function(req, res) {
     	res.json( {msg: 'Goodbye World'} );
     });
  • Try it out!


Django vs. Express Terminology!


DjangoExpress
ModelModel
ViewController
TemplateView

With Express, we will be following the very popular Model-View-Controller (MVC) design pattern to organize our code.


Rendering Views

  • We use the render method on the response object to render templates.

  • Express can work with a multitude of view engines.

  • Pug (formerly Jade) 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!


Rendering Views (cont.)


  • 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.


Rendering Views (cont.)


  • VS Code includes Emmet and EJS support, so you will be able to type ! and press tab to generate the HTML boilerplate in home.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>

Rendering Views (cont.)


  • 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.


Rendering Views (cont.)


  • Add an <h1> inside the <body> so that we see something :)

     <body>
     	<h1>Home Page</h1>
     </body>

Rendering Views (cont.)


  • 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?...


Rendering Views (cont.)

  • 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 the app object is used to configure an Express app's settings...


Rendering Views (cont.)


  • 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')...


Rendering Views (cont.)


  • 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; and views 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...


Rendering Views (cont.)


  • Core Node modules don't have to be installed with npm install, but we do have to require them:

     // Require modules
     var express = require('express');
     var path = require('path');
  • Restart and let's see what the next error is...


Rendering Views (cont.)


  • 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 for install.

  • We don't need to require the view engine - Express knows how to find it.

  • Restart and bam!


Passing Data to a View


  • 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's render!


Passing Data to a View (cont.)


  • 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.


Passing Data to a View (cont.)


  • 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...


Passing Data to a View (cont.)


  • The string todos/index points to the template. We're going to have to create a folder inside of our views folder named todos and add a filed named index.ejs to it:

     $ mkdir views/todos
     $ touch views/todos/index.ejs

Passing Data to a View (cont.)

  • Now let's code our todos/index.ejs. Start by copying over the HTML from home.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>

Passing Data to a View (cont.)


  • 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 :)


Passing Data to a View (cont.)


  • If you have data that you want available to all views, the Express app object has a locals object on it that you can add properties to.

  • Let's see how we can use locals to provide our app's title. In server.js:

     app.set('views', path.join(__dirname, 'views'));
     // new code below
     app.locals.title = 'First Express';

Passing Data to a View (cont.)


  • Then in both home.ejs and index.ejs, update the <title> element in the <head>:

     <meta charset="UTF-8">
     <title><%= title %></title>

    Restart and check out the browser tab!


Add a Todo

  • To demonstrate how we can add a todo (albeit, not persisted in the case of a server restart), let's add a <form> element to index.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!


Add a Todo (cont.)


  • 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?


Add a Todo (cont.)


  • 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 a render?


Add a Todo (cont.)


  • 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 as application/x-www-form-urlencoded.

  • Scroll down and check out the Form Data being sent in the request body.


Add a Todo (cont.)

  • 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...


Add a Todo (cont.)


  • Unlike the req.params and req.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!


Express Middleware Detour

  • 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.


Express Middleware


  • 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().


Express Middleware (cont.)


  • 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 the render/redirect methods.


The Request/Response Cycle in Express



Adding our own Middleware

  • 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!


Common Express 4.0 Middleware

  • 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 the cookie object on the response object.

  • serve-favicon: Serves the favicon from route /favicon.ico.


Middleware (cont.)

  • 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...


Middleware (cont.)


  • Let's create a public folder and a about.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.


Add a Todo (cont.)


  • 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.


Add a Todo (cont.)


  • 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)

Add a Todo (cont.)


  • 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() and urlencoded() methods will return a middleware function that is then mounted by app.use.


Add a Todo (cont.)

  • 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 the todos 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.


Review Questions


  • 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?


express-generator


express-generator

  • 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 lightweight django-admin startproject...


express-generator (cont.)


  • 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.


express-generator (cont.)


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

Generating Our App's Skeleton with
express-generator


  • 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

Folder Structure

  • 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...


Install Dependencies


  • 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 in package.json into the node_modules folder. Do it.


Starting the Application


  • 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 to localhost:3000.


bin/www - WTF?

  • What's with this ./bin/www file? Well, the Express team decided to partition out the HTTP server related code out of app.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 inside www (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...


Renaming app.js


  • In MERN/MEAN Stack apps, Angular's main module is often named app.js and this could get confusing having two app.js files. This is why many developers name their main Express file server.js.

  • So let's rename it...


Renaming app.js


  • First, rename app.js to server.js.

  • Then, inside of www, change line 7 from:

     var app = require('../app');

    to:

     var app = require('../server');
  • That's it! Restart and test.


Essential Questions


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?

Bonus: Best Practice Routing


Best Practice Routing - The Express Router Object

  • 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.


The Express Router Object


  • There are several ways to set up routing in an Express app.

  • In our first-express app, we used Express' app.get and app.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.


The Express Router Object (cont.)


  • 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...


The Express Router Object (cont.)

  • Yes, those modules export instances of Express' Router object after they have had their specific routes defined with get methods, just like we did with app.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.


The Express Router Object (cont.)


  • 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.)


Pledge to Use RESTful Routes


  • Although MEAN/MERN Stack apps have very little convention, pledge that you will define RESTful routes whenever possible.

  • Thank you!


Practice (10 mins)
Router Refactor

Let's refactor our first-express application to use the Router object as modeled by the app generated using express-generator:

  1. Modularize with a module. Create a routes folder and name module index.

  2. Export an instance of Router

  3. Mount the router instance using app.use with a path of /.

continued...


Practice (10 mins)
Router Refactor (cont.)


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!


References


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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment