Middleware refers to modules that are chained 'between' when your Express app is instantiated and when your server is launched with app.listen(myPortNumber)
.
We'll stick to two easy examples that illustrate the usefulness of middleware: logging and static resources.
Using middleware is simple. Use Node.js to require
the package that you'd like to use. Then add the middleware to your app by calling it with .use()
. The order that you call your middleware may matter, so look in the docs for your middleware to make sure it's well-positioned.
Here's how we would deploy the logging middleware Morgan.
First, npm install morgan
. We'll use the same express configuration that we did before.
const express = require('express'); // makes Express available in your app.
const app = express(); // Creates an instance of Express, which allows us to begin routing.
const morgan = require('morgan'); // Require our middleware
app.use(morgan('dev')); // use our middleware. Check docs for the mode ('dev') you'd like to use.
app.listen(3000); // Starts up our server on port 3000.
module.exports = app; // Allows other files to access our Express app.
That's it!
Static Resources
One extremely useful bit of middleware is Express' static resource routing method.
As we saw before, all of your assets must be routed properly in your application. Without static resource routing, commonly-used assets like a logo must be routed relative to every page that uses them. What a nightmare!
Thankfully, Express allows us to set a folder as our 'static' directory. This directory, usually called 'public', holds commonly-shared files like a logo image. Here's how we would .use()
static routing.
const express = require('express');
const app = express();
const morgan = require('morgan');
app.use(morgan('dev'));
app.use('/static', express.static(__dirname + '/public')); // Sets up static resource routing to our /public directory
app.listen(3000);
module.exports = app;
Once configured, your HTML can reference any file in your 'public' directory by putting '/static' in front of the route. A logo could be referenced as '/static/kittensLogo.png' on every page your website. No more painful manual routing for your shared files.
You might notice that our call to .use()
looks similar to any other route. It is. When our application makes a request to a file at '/static', our express.static
router takes over and serves up the file from our public directory. This means that the path '/static' is just a convention. Since .use()
operates just like any other route, so you can specify whatever you want.
Body Parsing
If you call, our HTTP request object sometimes contains a body (payload) of information from the user. For various reasons that are beyond the scope of this article, parsing the data from this body can be annoying.
Thankfully, Body Parser exists. Body Parser can be configured to automatically parse JSON and URL encoded strings in HTTP. As with other middleware, set up is simple.
First npm install body-parser
. Then, using our original Express configuration:
const express = require('express');
const app = express();
const morgan = require('morgan');
const bodyParser = require('body-parser'); // Require body-parser
app.use(morgan('dev'));
app.use(express.static(__dirname + '/public'));
// BODY PARSER
app.use(bodyParser.urlEncoded()); // Tells Express to use body-parser to parse url encoded strings
app.use(bodyParser.json()); // Tells Express to use body-parser to parse JSON
app.listen(3000);
module.exports = app;
Once you have Body Parser configured, you'll be able to access the .body
property on your request object. The body is itself an object, which contains whatever came through the request. For example, if a user were to send form data for a field title 'username', we could access their input by using request.body.username
. You can then use the body to do whatever you want in your route.
// Our route to handle requests to /kitten
kittens.get('/', function (request, response, next) {
let myBody = request.body;
response.render(myKittenPage, myBody)
});
Error-Handling Middleware
Since your Express app relies on asynchronous functionality, you will need to handle your errors. Errors are typically handled with a .catch()
at the end of a promise chain.
Here's how that would look:
app.get('/path', function (req, res) {
AsyncFunction()
.then(function(resultsOfAsync) {
// do stuff
})
.then(function(moreResultsOfAsync) {
// do more stuff
})
.catch(console.error) // If an error is thrown, our catch block will process it and error log it in the console.
})
This approach will work fine. But as your program grows, you'll find yourself writing many catch blocks. What happens if you want to do something a bit more customized with your error? Perhaps you want to color it in your console, do additional processing, or have some fallback response. You'll need to re-create this code in every .catch()
– not exactly a DRY approach.
Express allows us to centralize error handling by defining error-handling middleware.
First, we can head to our main app (the same file we used in previous examples). We'll define our single, error-handling middleware here.
const express = require('express');
const app = express();
const morgan = require('morgan');
const bodyParser = require('body-parser');
app.use(morgan('dev'));
app.use(express.static(__dirname + '/public'));
app.use(bodyParser.urlEncoded());
app.use(bodyParser.json());
// ERROR-HANDLING MIDDLEWARE
app.use(function(err, req, res, next) { // A function with these four parameters will always be treated as error-handling middleware by Express.
console.error(chalk.red.underline.bold(err)); // turns our error red, underlines it, and bolds it
res.send(404); // sends a 404 to our user
});
app.listen(3000);
module.exports = app;
We call app.use()
so that Express will use our error handling on every route – if there's an error. We can define anything we want within our function(err, req, res, next)
. To create error-handling middleware, we just need to create a function with four parameters inside our app.use. Express will always see this as error-handling. Our first parameter will be our error.
There's two big benefits to this approach. First, we can do so much within our error handler, since we have access to req
, res
, and next
.
Second, we can rewrite all of our .catch()
blocks in an extremely simple way:
app.get('/path', function (req, res) {
AsyncFunction()
.then(function(resultsOfAsync) {
// do stuff
})
.then(function(moreResultsOfAsync) {
// do more stuff
})
.catch(next) // If an error is thrown, send it to our Error-Handling Middleware!
})
Now, we can use .catch(next)
in every Promise chain. Our error-handing middleware will catch the error and do whatever we defined to handle it.
That wraps up our exploration of Express.js. Happy building!