Week 4
Middleware functions
Quiz 3: RESTful APIs 20 mins
There will be a quiz today. It will be worth 2% of your final grade.
Assignment Reminder
Assignment 1 - Basic CRUD - is due before next week's class.
# Agenda
- AMA (15 min)
- Quiz (20 min)
- Break (10 min)
- What is middleware? (15 min)
- Common use cases (5 min)
- Built-in middleware
- Community developed middleware
- EX4-1 Router Module (30 mins)
- Break (10 mins)
- EX4-2 Project Folder Structure (10 mins)
- EX4-3 carId Validator Middleware (30 mins)
- Assignment 1: Basic CRUD (30 mins)
# Review
The solution to last week's in-class exercise follows a similar pattern as the update methods. In fact the bulk of the code is a direct copy and paste. It is only the code in the else block that is different.
app.delete('/api/cars/:carId', (req, res) => {
const carId = parseInt(req.params.carId)
const index = cars.findIndex(car => car.id === carId)
if (index < 0) {
res.status(404).send({
errors: [
{
status: 'Not Found',
code: '404',
title: 'Resource does not exist',
description: `We could not find a car with id: ${carId}`
}
]
})
} else {
const deletedCar = cars[index]
cars.splice(index, 1)
res.send({data: deletedCar})
}
})
We save a copy of the car object that we are about to remove from the collection, so that we can send as confirmation in the response data. Then use the array.splice()
method to cut out the element at index position index
that we looked up in the earlier if
block.
To slice() or to splice() ... that is the question.
Don't worry if you get these two array methods confused, most people do. When you're not sure, check the docs ...
The slice() (opens new window) method returns a shallow copy of a portion of an array into a new array object selected from begin to end (end not included). The original array will not be modified.
The splice() (opens new window) method changes the contents of an array by removing or replacing existing elements and/or adding new elements.
# What is middleware
Middleware functions are used to encapsulate functionality that you want to apply to multiple routes without repeating the code in every route handler. They run before the route handlers are evaluated and may be chained together. Each middleware function can either end the request or pass control to the next function in the pipeline, until the final route handler is reached.
From the docs ... (opens new window)
Middleware functions are functions that have access to the request object (req), the response object (res), and the next function in the application’s request-response cycle. The next function is a function in the Express router which, when invoked, executes the middleware succeeding the current middleware.
Middleware functions can perform the following tasks:
- Execute any code.
- Make changes to the request and the response objects.
- End the request-response cycle.
- Call the next middleware in the stack.
# Middleware function signature
const myMiddleware = (req, res, next) => {
// validate something
// if fail, return error to client
// if OK, call next()
}
# Types of middleware
An Express application can use the following types of middleware:
- Application-level middleware
- Router-level middleware
- Error-handling middleware
- different function signature
- Built-in middleware
- express.json()
- express.urlencoded()
- express.static
- Third-party middleware
# Common use cases
- router modules
- validation
- error handling
- image handling
- enforcing security best practices
# Timestamp Logger
This is a simple example of a middleware function that logs the requested resource path and the time of the request. It is invoked on the main express app with no route qualifier, so it will apply to all incoming HTTP requests.
const express = require('express')
const app = express()
const timestampLogger = (req, res, next) => {
console.log(`${req.path} requested at ${Date.now()}`)
next()
}
app.use(timestampLogger)
This was a trivial example of an access log middleware. A more production ready solution can be found in the popular third-party module called morgan (opens new window). It is easily added to your project like this ...
npm install morgan
// app.js
const morgan = require('morgan')
const express = require('express')
const app = express()
app.use(morgan('tiny'))
// rest of your app config.
# Router modules
A special kind of middleware is the Router (opens new window) Module.
A router object is an isolated instance of middleware and routes. You can think of it as a “mini-application,” capable only of performing middleware and routing functions. Every Express application has a built-in app router.
A router behaves like middleware itself, so you can use it as an argument to app.use() or as the argument to another router’s use() method.
# EX4-1 Router Module
Objective
Move all of the existing route handler methods (like app.get
or app.post
) from the main app.js file to a separate carsRouter.js
Router module.
Picking up from last week's Express CRUD exercise, create a new GitHub repo called mad9124-w21-ex4-middleware
and import this starter repo (opens new window).

Add your professor (rlmckenney
) as a collaborator. Then clone your new repo to your laptop in the code folder for this course.
Install dependencies
This starter repo has dependencies defined in the package.json
file. Before we go any further, you need to run npm install
in the terminal of the project folder to download and install them.
We will refactor the Express CRUD RESTful API routes into a separate Router (opens new window) module.
Create a new file in the project folder called carsRouter.js
and in that file, create a new express.Router
object.
const express = require('express')
const router = express.Router()
This router
will have all of the same HTTP verb methods that our main app
object has. So, when we copy the methods over from app.js
, substitute router
for app
like this ...
// old code
app.get('/api/cars', (req, res) => res.send({data: cars}))
// becomes new code
router.get('/', (req, res) => res.send({data: cars}))
Notice that the route path above is just /
, whereas it was /api/cars
before. That is because all of the resource paths in this router module are relative to the root path of /api/cars
which will be set in the main app.js
module.when we tell our express app to use this router. We will set this up shortly.
Similarly, route paths with named route parameters will look like this ...
router.get('/:carId', (req, res) => {
const carId = parseInt(req.params.carId)
const car = cars.find(car => car.id === carId)
res.send({data: car})
})
Your task
Update the rest of the route handler methods accordingly.
Don't forget, we need to export the router
object from this module. Add the module.exports
line at the bottom of the carsRouter.js
file.
module.exports = router
Now, back in app.js
, we need to import the new carsRouter
object and tell the express app
object to use
it for all resource routes starting with /api/cars
App.js should now look like this ...
'use strict'
const express = require('express')
const app = express()
const carsRouter = require('./carsRouter.js')
app.use(express.json())
app.use('/api/cars', carsRouter)
const port = process.env.port || 3030
app.listen(port, () => console.log(`Server listening on port ${port} ...`))
Notice that we are no longer importing the cars
collection data in app.js
. This should be private data in our carsRouter
module. Let's load it at the top of the carsRouter.js
file.
const {cars} = require('./cars.js')
const express = require('express')
const router = express.Router()
# That should do it! Let's test it with Postman.
Run a full set of tests on all of the six /api/cars
resource routes.
# Commit your work
If everything is working, create a new git commit
with the message, "Refactor /api/cars routes into a dedicated router module."
git add -A
git commit -m "Refactor /api/cars routes into a dedicated router module."
# EX4-2 Project Folder Structure
Let's get organized
Its time to start organizing our project folder structure.
As our application grows in features and complexity, it is a good practice to organize the various modules of our application into sub-folders. This makes it much easier to navigate and find the correct module when we need to make changes.
To get started, create a new routes
folder and move the carsRouter.js
file into that new folder. The common practice is to name the files in the routes folder by the name of the resource path. So, rename carsRouter.js
to cars.js
.
Now create a new folder called data
and move the cars.js
file into that new folder and rename it index.js
.
Don't forget to update the relative paths for your require()
functions to point to the correct sub-folders. e.g.
// in the /routes/cars.js file
const {cars} = require('../data')
// in the app.js file
const carsRouter = require('./routes/cars.js')
Create one more folder called middleware
. We will add our custom middleware modules here starting with the validateId.js
that we will create in the next section.
Our reorganized project file structure should now look like this ...
.
├── app.js
├── package.json
├── data
│ └── index.js
├── middleware
│ └── validateId.js
└── routes
└── cars.js
# Test and Commit
Test all of the routes with Postman, and if everything is still working, create a new git commit
.
git add .
git commit -m "Refactor folder structure."
# EX4-3 carId Validator Middleware
Objective
Instead of repeating the block of code to check for a valid carId
on every route, we can move that to a middleware function and greatly simplify the implementation logic.
Create a new file called validateCarId.js
in the middleware
folder. Then scaffold out the basic signature for a middleware module. Remember, it is almost the same as the route handler callback functions, but has a third argument - the next()
function.
We will also need access to the cars collection, so require the cars data at the top of our middleware module.
const {cars} = require('../data')
const validateCarId = (req, res, next) => {}
module.exports = validateCarId
OK, now we can copy the carId validation logic from one of our route handlers and paste it into the validateCarId function. Most of it can be copied unchanged. Only the else
block needs to be updated. If the carId
was valid, we want to make the index position available to our route handlers as a property on the request object - so that it does not have to be looked up again.
Simply declare a new property on the req
object and assign the index
value to it.
req.carIndex = index
The last thing to do is call the next()
method, to tell express that it can move on to the next middleware function or route handler.
The complete validateCarId.js middleware module should now look like this.
const {cars} = require('../data')
const validateCarId = (req, res, next) => {
const id = parseInt(req.params.carId)
const index = cars.findIndex(car => car.id === id)
if (index < 0) {
res.status(404).send({
errors: [
{
status: '404',
title: 'Resource does not exist',
description: `We could not find a car with id: ${id}`
}
]
})
} else {
req.carIndex = index
next()
}
}
module.exports = validateCarId
To use this new custom middleware function, we need to require it in the cars router module and then tell the router
object to use
the middleware on all routes with a :carId
route parameter.
The top of /router/cars.js should look like this ...
const {cars} = require('../data')
const validateCarId = require('../middleware/validateCarId')
const express = require('express')
const router = express.Router()
router.use('/:carId', validateCarId)
We can remove the now redundant carId validation checks in the get
, put
, patch
and delete
functions. Since we no longer have a local index
variable in these functions, we need to make some minor updates to utilize the req.carIndex
property that the middleware function created.
router.get('/:carId', (req, res) => res.send({data: cars[req.carIndex]}))
router.put('/:carId', (req, res) => {
const {make, model, colour} = req.body
const updatedCar = {id: parseInt(req.params.carId), make, model, colour}
cars[req.carIndex] = updatedCar
res.send({data: updatedCar})
})
router.patch('/:carId', (req, res) => {
const {id, ...theRest} = req.body
const updatedCar = Object.assign({}, cars[req.carIndex], theRest)
cars[req.carIndex] = updatedCar
res.send({data: updatedCar})
})
router.delete('/:carId', (req, res) => {
const deletedCar = cars[req.carIndex]
cars.splice(req.carIndex, 1)
res.send({data: deletedCar})
})
# Test and Commit
Test all of the routes with Postman, and if everything is still working, create a new git commit
.
git add .
git commit -m "Refactor cars router to use carId validator middleware."
Push your commits up to the remote GitHub repo, and submit the GitHub repo's URL on BrightSpace.
# For next week
Assignment Reminder
Assignment 1 - Basic CRUD - is due before the start of next class.
Before next week's class, please read these additional online resources.
Middleware: THE core of node.js backend apps (opens new window)
Learning NPM (Node Package Manager) (opens new window) (video)
Quiz
There will be a short quiz next class. The questions could come from any of the material referenced above.