Introduction
Do you ever wonder what's going on within all of the Express.js middleware that you're adding to your webapp? It's actually pretty impressive what kind of functionality you can add to your apps with just one line of code, or a few:
// requires...
var app = express();
app.use("/static", express.static(__dirname + "/public"));
app.use(cookieParser('sekret'));
app.use(compress());
The last three lines above handle quite a bit of the webapp functionality for us. The first app.use()
call tells Express where our static files are located and how to expose them, the cookieParser('sekret')
middleware handles all cookie parsing (with encryption), and the last one automatically gzip compresses all of our HTTP body data. Not bad for just three lines of code.
These middleware are pretty typical in your average webapp, but you can find some that do more than just the standard data compression or cookie parsing. Take these for example:
- helmet: Helps secure your app by setting various HTTP headers
- express-simple-cdn: Easily use a CDN for your static assets
- join-io: Join files on the fly to reduce HTTP requests count
- passport: Adds user authentication to selected routes
And here is a much larger list of middleware that you might want to use.
Now that you've seen some examples, here is just about everything you can do with it:
- Execute any code, including asynchronous code
- Make changes or additions to the request and response objects
- End the request-response cycle
- Call the next middleware in the stack
With the endless possibilities, I'm sure you have some ideas of your own that you'd like to create, so throughout the rest of this article I'll be showing you how to write your own middleware. There are a few different types of middleware you can write (application, router, error-handling, etc), but in this article we'll focus only on the application-level.
The Basics
Middleware can be thought of almost as if it's an Express route. They take the same parameters and everything, but unlike the normal routes you aren't required to provide a URL path for the middleware. The two biggest differences are how the path is treated and when it is called.
The provided path is treated as a prefix, so if you had something like app.use('/api', ...)
, then your middleware will run if /api
is called and if /api/users
is called. This is different from routes where the path must be an exact match.
The URL path can be omitted from the app.use()
call if you want your code to run for all requests, otherwise you can specify a path and have your code only run when that route (and all of its sub-routes) is requested. For example, this might be useful for adding authentication to only a few given routes.
A simple middleware might look like this:
var app = express();
app.use(function(req, res, next) {
console.log('Called URL:', req.url);
next();
});
Whereas a route handler looks like this:
var app = express();
app.get('/', function(req, res, next) {
res.send('Hey there...');
});
See? They're basically the same thing, so writing these functions should feel pretty familiar to you.
The parameters used are:
req
: An object containing all of the relevant request information. This could be anything from the URL requested to the body of a POST request to the IP address of the user.res
: This is the response object, which is used to send back data to the user for the given request. You might use this to send back an HTTP 404 response code, or to send back rendered HTML viares.render()
.next
: And finally, thenext
parameter is a callback to tell Express when our middleware has finished. If you do any IO (like database calls) or heavy computation then you'll likely need to make the function asynchronous to prevent blocking the main execution thread, in which case you'll have to usenext
.
It's worth noting that if the your middleware does not end the request-response cycle with res.end(...)
then you must call next()
to pass control to the next middleware. If you don't then the request will be left hanging and will timeout.
An Example
In this example we'll be creating middleware that helps you automatically translate text between languages. This isn't a typical i18n module though, we'll be using Google Translate instead.
Let's say you've made a chat app that lets you talk to people all over the world, and to make it seamless you need the text to be automatically translated. In this use-case most i18n modules wouldn't work since you need to pre-translate all of the strings, which we can't do since we're dealing with user input.
Sure, you could handle the translation in each of your Express routes, or you could have it handled for you in middleware, which keeps your route code cleaner and prevents you from forgetting to add translation to every route that needs it.
The strings (user messages) come in through a REST API, so we'll need to check all of the API routes' bodies for text to translate. All strings being saved to the database in the POST calls will be kept in their native languages, but all strings being retrieved from the database with GET calls will be translated to the language specified in the HTTP Accept-Language header that accompanies the user request.
I figured we wouldn't make all the messages in the database the same language since we would then need to translate some of them twice, which degrades the translation quality.
Check out our hands-on, practical guide to learning Git, with best-practices, industry-accepted standards, and included cheat sheet. Stop Googling Git commands and actually learn it!
First off, let's write a simple function to call the Google Translate API:
var googleTranslate = require('google-translate')('YOUR-API-KEY');
var translate = function(text, lang, cb) {
googleTranslate.translate(text, lang, function(err, translation) {
if (!translation) cb(err, null);
cb(err, translation.translatedText);
});
}
Then we'll use that function in our middleware code, which is exported in modules.export
for use by the application.
module.exports = function(req, res, next) {
if (req.method === 'GET') {
var lang = 'en';
var langs = req.acceptsLanguages();
if (langs[0] !== '*') {
if (langs[0].length > 2) {
// ex: en-US
lang = langs[0].substring(0, 2);
} else {
// ex: en
lang = langs[0];
}
}
if (lang !== res.body.lang) {
return translate(res.body.message, lang, function(err, translation) {
res.body.message = translation;
res.body.lang = lang;
next();
});
}
}
next();
};
NOTE: This isn't really how you modify a Response
body. I'm just simplifying it for the sake of brevity. If you want to see how to actually modify the body, then check out the compression middleware, which does it properly. You have to proxy the res.write
and res.end
functions, which I didn't do because it would just be a distraction from the concepts I'm trying to show here.
And finally, we apply the middleware to our app. Just make sure that you call the app.use
function after you've already declared your routes. The order in which it is called is the order in which each function runs.
Also, make sure you call next()
in each of your /api
routes, otherwise the middleware won't run.
var expressGoogleTranslate = require('my-translation-middleware');
var app = express();
app.get('/api/message', function(req, res, next) {...});
app.get('/api/message/all', function(req, res, next) {...});
app.post('/api/message', function(req, res, next) {...});
app.delete('/api/message/id', function(req, res, next) {...});
app.use('/api', expressGoogleTranslate);
And that's all there is to it. Any string returned in the Response
body that is a different language than what the user accepts will be translated by Google Translate, which detects which language the source text is in.
So if our response started off looking like this...
{
"message": "The quick brown fox jumps over the lazy dog"
"lang": "en"
}
...and the user only accepts Swahili, then after the middleware runs we'll get a final translation that looks like this:
{
"message": "Haraka kahawia mbweha anaruka juu ya mbwa wavivu"
"lang": "sw"
}
Conclusion
Although it may sounds intimidating, middleware is really easy to create in Express. You can use it for just about anything, no matter how simple or complex it is.
Just be sure to do a quick search on npm for whatever you're trying to make since tons of code is already out there. I'm sure there is a package already out there that does what my translation code does (and probably much better too).
Do you have any ideas for middleware to create, or how to improve my example above? Let us know in the comments!