This presentation can be viewed here
# Express Middleware
- Students Will Be Able To:
- Describe the Purpose of Middleware
- Use
method-overrideMiddleware and HTML Forms to Add, Update & Delete Data on the Server - Use Query Strings to Provide Additional Information to the Server
- Setup
- What is Middleware?
- Our First Middleware
- Key Middleware
- Express Request-Response Cycle
- Creating To-Dos
- method-override Middleware
- Delete a To-Do
- Exercise: Update a To-Do
-
This lesson builds upon the
express-todosproject created in the previous lesson. -
If your previous code has the
index&showroutes working, you may continue to build upon it, however, if it wasn't working, or you just want to play it safe, there is starter code provided:- Be sure to
cdinto thestarter-code/express-todosfolder - Then install the node modules:
$ npm install
- Be sure to
-
Make sure that you're in the express-todos folder and open your text editor.
-
In the Intro to Express lesson, we identified the three fundamental capabilities provided by web application frameworks:
- The ability to define routes
- The ability to process HTTP requests using middleware
- The ability to use a view engine to render dynamic templates
-
We've already defined routes and rendered dynamic templates.
-
In this lesson we complete the trifecta by processing requests using middleware.
-
A middleware is simply a function with the following signature:
function(req, res, next) {}
-
As you can see, middleware have access to the request (
req) and response (res) objects - this allows middleware to modify them in anyway they see fit. -
Once a 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&redirectmethods...
-
Yes, actually you have already written middleware - the controller actions,
todosCtrl.index&todosCtrl.show, are middleware! -
The controller middleware functions didn't need to define the
nextparameter because they were at the end of the middleware pipeline. That is, they ended the request/response cycle by calling a method on theresobject, e.g.,res.render. -
The
nextfunction is also used for error handling.
-
There's no better way to understand middleware than to see one in action.
-
Open server.js and add this "do nothing" middleware:
app.set('view engine', 'ejs'); // add middleware below the above line of code app.use(function(req, res, next) { console.log('Hello SEI!'); next(); });
-
Type
nodemonto start the server, browse tolocalhost:3000, and check terminal.
-
Note that
app.usemounts middleware functions into the middleware pipeline. -
Let's add a line of code that modifies the
reqobject:app.use(function(req, res, next) { console.log('Hello SEI!'); // Add a time property to the req object req.time = new Date().toLocaleTimeString(); next(); });
-
Now let's pass this info to the todos/index.ejs view...
-
It's the responsibility of controllers to pass data to views.
-
Let's update the
indexaction in controllers/todos.js so that it passesreq.time:function index(req, res) { res.render('todos/index', { todos: Todo.getAll(), time: req.time // add this line }); }
-
Now let's render the time in todos/index.ejs by updating the
<h1>as follows:<h1>Todos as of <%= time %></h1>
-
Refresh!
-
The order that middleware is mounted matters!
-
In server.js, let's move our custom middleware below where the routers are being mounted:
app.use('/', indexRouter); app.use('/todos', todosRouter); app.use(function(req, res, next) { console.log('Hello SEI!'); req.time = new Date().toLocaleTimeString(); next(); });
-
Refresh shows that it no longer works :(
-
Move it back above the routes - that's better.
-
Express generator mounted several key middleware by default...
-
morgan: Logger that logs requests.
-
express.json & express.urlencoded (formerly known as
body-parser): Parses data sent in the body of the request and populates areq.bodyobject containing that data. -
cookie-parser: Populates a
cookiesproperty on thereqobject 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 thecookieobject on the response object. -
express.static: Serves static assets, such as css, js and image files.
- Again, here's a great flow to follow when you want to add functionality to your web app:
- Identify the "proper" Route (Verb + Path)
- Create the UI that issues a request that matches that route.
- Define the route on the server and map it to a controller action.
- Code and export the controller action.
res.rendera view in the case of aGETrequest, orres.redirectif data was changed.
-
What functionality do we want? Do we want to show a form on the
indexview, or do we want a separate page dedicated to adding a To Do? Typically, you'd want have the form on the same page, however, for completeness, we'll use the dedicated page approach. -
Checking the Resourceful Routing for CRUD Operations in Web Applications Chart, we find that the proper route is:
GET /todos/new
-
Next step is to add a link in views/todos/index.ejs that will invoke this route:
... </ul> <a href="/todos/new">Add To-Do</a> </body>
-
Step 2 is done. On to step 3 - defining the route on the server...
-
Let's add the
newroute in routes/todos.js as follows:router.get('/', todosCtrl.index); router.get('/new', todosCtrl.new); router.get('/:id', todosCtrl.show);
-
I'm going to post this question in Slack for you to REPLY to:
Why must thenewroute be defined before theshowroute?
-
Step 4 says to code the
todosCtrl.newaction we just mapped to thenewroute: -
In controllers/todos.js:
module.exports = { index, show, new: newTodo }; function newTodo(req, res) { res.render('todos/new'); }
-
Note that you cannot name a function using a JS reserved word.
-
Now we need that
newview. -
Create views/todos/new.ejs, copy over the boilerplate from another view, then put this good stuff in there:
<body> <h1>New Todo</h1> <form action="/todos" method="POST" autocomplete="off"> <input type="text" name="todo"> <button type="submit">Save Todo</button> </form> </body>
-
FYI that
autocomplete="off"attribute will prevent the sometimes annoying autocomplete feature of inputs. -
Verify that clicking the Add To-Do link displays the page with the form...
-
Performing a Create data operation using a form is a two-request process.
-
Checking the Routing Chart again shows that the proper route for creating data on the server is:
POST /todos
-
That's why the form's attributes have been set to:
action="/todos"method="POST"
-
Check this out if you want to learn more about HTML Forms.
- Same process:
- Determine proper route - check!
- Create UI - check!
- Define the route on the server - next...
-
In routes/todos.js:
router.get('/:id', todosCtrl.show); router.post('/', todosCtrl.create); // add this route
-
Yay! Our first non-
GETroute!
- Same process:
- Determine proper route - check!
- Create UI - check!
- Define the route on the server - check!
- Code and export the controller action - next...
-
In controllers/todos.js:
... create }; function create(req, res) { console.log(req.body); req.body.done = false; Todo.create(req.body); res.redirect('/todos'); }
-
Temporarily comment out the
Todo.create(req.body);line so that we can check out what gets logged out...
-
req.bodyis courtesy of this middleware in server.js:app.use(express.urlencoded({ extended: false }));
-
The properties on
req.bodywill always match the values of the<input>'snameattributes:<input type="text" name="todo">
-
We already did Step 5 with the
res.redirect. -
All we need is that
createin models/todo.js:module.exports = { getAll, getOne, create }; function create(todo) { todos.push(todo); }
-
Test it out!
-
Note that when
nodemonrestarts the server, added to-dos will be lost.
-
As shown on the Resourceful Routing for CRUD Operations in Web Applications Chart, performing full-CRUD data operations requires that the browser send
DELETE&PUTrequests. -
Using JavaScript (AJAX), the browser can send HTTP requests with any method, however, HTML can only send
GET&POSTmethods. So what do we do if we want to delete a To-Do? -
method-override middleware to the rescue!
-
Using
method-overrideallows browsers to inform the server that it actually wants it to consider the request it sends to be something other than aPOST- as you'll soon see, we'll be using forms with method="POST". -
First we need to install the middleware:
$ npm i method-override
-
Require it below
loggerin server.js:var logger = require('morgan'); var methodOverride = require('method-override');
-
Now let's add
method-overrideto the middleware pipeline:app.use(express.static(path.join(__dirname, 'public'))); app.use(methodOverride('_method')); // add this
-
We are using the Query String approach for
method-overrideas documented here.
-
The user story reads:
As a User, I want to delete a To Do from the list -
Same process:
- Determine proper route
-
The RESTful route is:
DELETE /todos/:id
- Same process:
- Determine proper route - check!
- Create UI - next...
-
By default,
method-overrideonly listens forPOSTrequests. -
Therefore, we'll use a
<form>for the UI in views/todos/index.ejs:<% todos.forEach(function(t, idx) { %> <li> <form action="/todos/<%= idx %>?_method=DELETE" class="delete-form" method="POST"> <button type="submit">X</button> </form>
-
The
?_method=DELETEis the query string.
-
Let's some styling in public/stylesheets/style.css:
.delete-form { display: inline-block; margin-right: 10px; } .delete-form button { color: red; } li { list-style: none; }
-
Refresh and use DevTools to ensure the links look correct.
- Same process:
- Determine proper route - check!
- Create UI - check!
- Define the route on the server - next...
-
I bet you could have done this one on your own!
-
In routes/todos.js:
router.post('/', todosCtrl.create); router.delete('/:id', todosCtrl.delete);
- Same process:
- Determine proper route - check!
- Create UI - check!
- Define the route on the server - check!
- Code and export the controller action - next...
-
Similar to
newTodo, we can't name a functiondelete, so...create, delete: deleteTodo }; function deleteTodo(req, res) { Todo.deleteOne(req.params.id); res.redirect('/todos'); }
-
Any questions?
-
All that's left is to add the
deleteOnemethod to theTodomodel:module.exports = { getAll, getOne, create, deleteOne }; function deleteOne(id) { todos.splice(id, 1); }
-
Does it work? Of course it does!
- Updating a To-Do is very similar to creating one because it also is a two-request process:
- One request to display a form used to edit the To-Do.
- Another request to submitted the form to the server so that it can update the To-Do.
- Exercise #1:
- As a User, when viewing the show page for a To-Do, I want to be able to click a link to edit the text of the To-Do
- Exercise #2:
- As a User, when editing a To-Do, I want to be able to toggle whether or not it's done
- Hints:
- Follow the same steps we followed multiple times for adding functionality!
- Be sure to reference the Routing Chart to determine the proper routes!
- You will want to pre-fill the
<input>with the todo text - use thevalueattribute and some EJS to pull this off. - Don't forget that the controller action will first have to get the To-Do being edited so that it can be sent to the view.
-
Hints for Exercise #2 (Toggling
done):- Use an
<input type="checkbox" ...> - Checkboxes are checked when a
checkedattribute exists (no value is assigned). - Use a ternary expression to write in the
checkedattribute, or an empty string. - If the checkbox is checked when submitted,
req.body.donewill have the value of"on", otherwise there won't even be areq.body.doneproperty.
- Use an
-
Enjoy!
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.

