Understanding Express Servers — RUN__ON Part 3
CS Weekly 8 — RUN_ON Part 3

This is Part 3 of a mini-series that discusses several topics in creating a full-stack web application called RUN__ON.
- Part 1 — Setting up CI and Project Infrastructure
- Part 2 — Setting up Heroku Postgres
- Part 3 — Creating an Express Application (You Are Here)
This mini-series is apart of an effort called CS Weekly, a manifesto towards committing oneself to weekly programmatic explorations in an article found here:
Server Application Architecture
As with most languages, defining a simple entry-point into a program is useful. In Java, one has a class with the function main() and often has an application entry file dedicated to simply calling this main function.
Although this is purely convention and not a constraint, defining an entry in a Node application called index.js (analogous to index.html) is a very common practice. The idea is to execute minimum configurations and make top-level imports. The file shouldn’t export anything nor should it contain definitions (such as functions or constants to be used elsewhere).
I often see developers importing and configuring express in the index.js entry file. Personally, I don’t like this practice and instead designate express configuration to a file called app.js. To me it creates a clearer separation of concerns, as index.js actually creates a server from Node’s http class using an express configuration. That is to say, one can define an express application without creating an HTTP server.
Thus, the server architecture, omitting sub-directories, should look as follows:
root
|_ .gitignore
|_ .travis.yml
|_ app.js
|_ index.js
|_ package.jsonAdding Debug
Before writing any code, let’s add a neat little package called debug.
npm install --save debugThis package gives us the ability to see different types of console statements depending on environment variables.
The typical use is as follows:
const debug = require('debug')('project_name::file')It exports a function that takes a string parameter. This string parameter is the name of the debugger. There can be multiple named debuggers through a project or even a single file. I recommend one per file.
These debuggers are used much like console.log:
debug('error %O', err)Throughout the application I’ll use the them as follows:
// eslint-disable-next-line no-unused-vars
const debug = require('debug')(run__on::server::<file>)The // eslint-disable... comment tells ESLint checkers that if the variable on the next line isn’t used, don’t throw an error. That’s because debuggers are often commented out, and I don’t want red lint errors lighting up as a result.
The reason the debugger name is prefixed with run__on:: is so that one can do the following (note the wildcard, *):
export DEBUG=run__on::* && npm run serverThis runs every single debugger named in the project.
If only concerned about a specific file, export just that debugger:
export DEBUG=run__on::foo.jsNow the console will show debug statements only from that specific file.
Application Entry: Creating a Server
A simple HTTP server in node can be written in very few lines. Thus I think it’s best to share those lines and then discuss their meaning:
// eslint-disable-next-line no-unused-vars
const debug = require('debug')('run__on::server::index.js')// dependencies
const http = require('http')// dynamically set PORT
const port = process.env.PORT// internals
const app = require('./app')()const server = http.createServer(app)
server.listen(port)
debug('listening on port %d', port)
First, the only dependencies so far are:
http— A Node module (docs)app(the express application, see next section)
The line:
const port = process.env.PORTdynamically sets the variable port to the environment variable called PORT. A port is just one of the many connection endpoints for a server. There’s a helpful StackOverflow thread for those interested in reading more. In production, the PORT is set by Heroku — we have no control over it, and its value will change on successive starts.
In development, a script will start the server and the PORT environment variable can be explicitly defined. This will be shown towards the end of the article.
The rest is taken care of by express, as shown here:
const server = http.createServer(app)
server.listen(port)Essentially, an express application configures details about how an HTTP server should respond to requests.
And that’s it — a rather simple entry into the application. Invoke top-level dependencies (for now this is just app.js, though in time a database import will be made) and start a server listening on a specific port for requests.
Express Applications
Express isn’t needed in order to configure an HTTP Server. However, the interface Node exposes is low-level. For common functionalities such as defining endpoints, Express provides a nice framework to work with. In their own words Express is a:
Fast, unopinionated, minimalist web framework for Node.js
To get started:
npm install --save expressEverything else written in this section will be located in app.js. Before any code is written, let’s have a quick discussion on what app.js should export.
Exporting a Function
As seen in index.js, the express import is some kind of Object:
const server = http.createServer(app)Given that express is just a configuration, it seems clear to me that it’s possible to configure various express applications for multiple servers on different ports. Perhaps in the future one might want to add parameters when importing. Therefore, I believe it’s ideal to actually export a function:
app.js:
module.exports = function () {
return app
}index.js:
const app = require('./app')()Note: an es6 fat arrow could be used in place of the
functionkeyword.
Now we’re ready to explore what Express is.
How Express Works: A Stack of Middleware
Express applications are remarkably simple once you start writing them. Most express applications can be honestly boiled down to two items:
- Set some configurations
.set() - Add a series of functions (called middleware)
.use()
All of the middleware functions (from hereon ‘middleware’) are invoked in the order they are specified.

Express Configurations
What kinds of things would be configured for an express application?
- Key/Values (ex ‘port’)
- Register paths (ex ‘views’)
- Register engines (ex ‘handlerbars’)
At this stage in the application, we’ll do a few things:
- Set the ‘Port’ value:
- Enable
'trust proxy' - Set a ‘views’ directory and ‘view engine’
Setting Port
We need to do 2 things:
- Pass the port from
index.jsto the Express Application - Set the Express Application port from a parameter
index.js
const app = require('./app')({ port })app.js
module.exports = function ({ port }) {
const debug = require('debug')('run__on::server::app.js')
const express = require('express')
const app = express()
if (port) {
debug('setting port to %d', port)
app.set('port', port)
}
}Enabling Trust Proxy
Examination of the Heroku documentation demonstrates that requests are forwarded to the application through a load balancer. In other words, by using Heroku your application sits behind their proxies.
Thus, we have to specify that we trust headers containing x-Forwarded, which is accomplished by a simple line:
app.enable('trust proxy')From the Express documentation:

Setting Views (to Pug)
Server side view engines are used to render HTML on the server. In other words, an application doesn’t need Vue or React or any fancy front-end JavaScript frameworks. For simple applications, one could just send server-side rendered HTML using a template language of their choice (ex: handlebars, pug).
However, as stated in Part 1, a Vue client will be used down the line.
Then why bother setting a server-side template engine? Specifically for one, super simple purpose: an error fallback view. In the case that for whatever reason a request cannot resolve by sending the Vue client, a generic Error page can be shown.
Pug is an extremely minimal (read simple) template engine. We can avoid writing HTML tags and all sorts of annoying syntax, which is desirable given that the only thing we’re using this for is to show a generic error page.
npm install --save pugSet the view engine with:
app.set('view engine', 'pug')
Then, tell express where these pug files are located so that paths can be omitted when rendering from these templates:
app.set('views', path.join(__dirname, 'views'))Thus, the Express Application so far is:
module.exports = function ({ port }) {
const debug = require('debug')('run__on::server::app.js')
const express = require('express')
const path = require('path')
// Configuration ----------------/
debug('configuring express application') const app = express() // set the port
if (port) {
debug('setting port to %d', port)
app.set('port', port)
} // res.render calls use pug templates in views directory
app.set('view engine', 'pug')
app.set('views', path.join(__dirname, 'views')) // for use behind Heroku proxy
app.enable('trust proxy') return app
}
Creating An Error View
All we are going to do is render a bare-bones HTML document with a title, body and <h1> tag. This is quite easily accomplished with a few lines in pug.
mkdir views
touch views/error.pugerror.pug
html
head
title= title
body
h1= messageIn the above, title= and h1= designate template variables. When we want to render this error.pug file, it will expect to receive a title and a message in its parameters.
Express Middleware
The application so far has a few basic configurations. Now, we have to add the middleware functions — in order.
What kinds of middleware functions would an express application use?
- Incoming request modifiers
- Request verification
- Request handlers
- Response handlers
- Error handlers
At this stage, we’ll do the following (in order):
- Setup basic security
- Setup a rate limiter
- Setup a router
- Setup an error handler
Everything we add will be after the prior configuration:
module.exports = function ({ port }) {
const debug = ...
const express = ...
app.set(...) // MIDDLEWARES HERE
}
Basic Security with Express Helmet
Straight from the Express documentation, Helmet is listed as a security best practice.
Straight from Helmet:
Helmet helps you secure your Express apps by setting various HTTP headers. It’s not a silver bullet, but it can help!
The beauty is, it’s super simple to use for most cases.
npm install --save helmetwithin app.js
const helmet = require('helmet')
app.use(helmet())
There are optional configurations to set with Helmet, and Helmet offers a nice table to show exactly what it’s doing and how to configure each setting:
Express Rate Limiter
Rate limiting monitors the number of requests being made by a particular address over a window of time. It essentially blocks malicious scripts from spamming requests to your server.
npm install --save express-rate-limitwithin app.js:
const RateLimit = require('express-rate-limiter')app.use(new RateLimit({
// 10 minutes
windowMs: 10 * 60 * 1000,
// 1000 requests per 10 mins
max: 1000,
delayMs: 0
}))
Here I’ve set some arbitrary values: every 10 minutes, a client can make 1000 requests. This is very small and will definitely change in the future, but it’s a nice value to start with and can be verified with tests.
Add Routes To Middleware Stack
Express Routing will be covered in a future article. For now, I’ll demonstrate just exactly where it goes in the middleware stack, but note that we won’t be actually creating a Router just yet.
Create a routes directory with an index.js:
mkdir routes
touch routes/index.jsInside app.js instead of explicitly calling app.use(), we’ll pass app to the router which will be responsible for setting things up:
require('./router')(app)Inside routes/index.js, we’ll just create some boilerplate:
const debug = require('debug')('run__on::server::routes/index.js')module.exports = (app) => {
debug('configuring express application router')
}
In the future, this file will use app.use() to add Express.Router configurations.
Middleware Functions
The first thing to note is that middleware are callback functions, denoted cb:
app.get('...', cb1, cb2, ..., cbn)It’s desirable to define callback functions somewhere else and import them, rather then defining them in-line. The reason is that they can’t be tested or used again if they’re defined anonymously in-line.
Thus, let’s create a directory for middlewares:
mkdir middlewareThere are two types of middleware functions
- ‘regular’
- ‘error’
Regular middleware functions utilize 3 parameters:
req— short for Request. This contains an HTTP request Object with all sorts of information about the request made by a clientres— short for Response. This contains methods for the express application to send HTTP response Objects back to clients.next— Calls the next middleware in the stack. Either takes no arguments or a single argument.
Error middlewares receive an additional argument err as the first argument, then everything else: req, res, next.
The term ‘middleware’ is ambigious — there could be many different middleware functions:
- security
- validation
- response handling
- error handling
This is optional, but I like to place these functions in files that match their purpose, then import them all into a single file middleware/index.js.
touch middleware/index.jsThe reason is rather simple: one can import any middleware by doing the following:
const { internalError } = require('./middleware')The actual file where the function lives doesn’t have to be included in the require path. In the end, index.js will just contain an Object of every middleware function. Separating them out into files just makes them easier to maintain.
Note: given that middleware are callbacks which could just as easily be defined in-line, they’re suitable for fat-arrow functions. In other words a first-class function with closure isn’t needed.
Adding An Error Middleware
The last middleware to add for now is a catch-all error handler.
touch middleware/errors.jsA scaffold for a first error middleware is:
const internalError = (err, req, res, next) => {
// handle error here
}Recall earlier that a pug view named error.pug was created, containing a title and a message. Well, this error handler will only be called when something goes wrong, and thus we can do two things for now:
console.log/debugprint the error- Render the
error.pugview with a basic message
In order to do the latter, res.render() can be used. Recall earlier that both views and view engine where set in the application configuration. This allows us to simply send a string of the name of the file (omitting the ‘pug’ extension), as well as an object mapping each template variable to a value:
res.render('error', {
title: '...',
message: '...'
})Thus, a complete error middleware is as simple as:
const debug = require('debug')('run__on::server::middelware/error.js')const internalError = (err, req, res, next) => {
debug('internal error %O', err)
res.render('error', {
title: 'Error status 500',
message: 'Internal Server Error'
})
}module.exports = {
internalError
}
Note: If you use
middleware/index.js, make sure to import/export this function there as well.
Final Touches and Testing
So far, we’ve done the following:
- create an HTTP server using an Express Application configuration
- Create an Express Application to trust proxies, use pug views located in a ‘views’ directory,
- Add security and rate-limiting middlware functions
- Initialize an empty Router
- Add an error middleware
Setting PORT through environment
At the beinning of the article I said we’d need to invoke the server with a PORT environment variable. The last step is to add a script in the package.json that does exactly this. Furthermore, I suggest installing nodemon as a development dependency as will become clear shortly:
npm install --save-dev nodemonpackage.json
"scripts": {
"start": "node index.js",
"server": "cross-env PORT=3000 nodemon index.js",
"test": "npm run lint",
"lint": "standard"
}The reason nodemon is used in development is that it hot-loads changes. Without it, the server process has to be manually killed and restarted in order to see changes. nodemon automatically restarts the server process after each save.
cross-env was downloaded in the prior tutorial. If you omitted that step, I suggest installing it now:
npm install --save-dev cross-envIt allows a common api to list environment variables regardless of operating system.
In order to start everything AND see the debug logs, use:
export DEBUG=run__on::server* && npm run serverI get the following pretty logs (note that the debug package uses a different color for each named debugger!):

NOTE: If using Git Bash for Windows and there’s trouble seeing the colours, try re-installing and allowing true type font. Furthermore, adding
export FORCE_COLOR=truein ~/.bashrc may help.
Testing The Error View
Currently, if you open up a browser to localhost:3000 (or whatever PORT you used), you should see:
Cannot GET /But I thought we added error middleware to handle these things, what’s going on?
Well recall the middleware parameters: error, req, res, next.
The way the middleware gets called is from another middlware function:
some_function = (req, res, next) => {
next()
}next doesn’t call the error middleware unless you add an argument. Otherwise, next calls the next regular middleware in the stack (order matters!!!!!).
So without explaining how routing works, let’s just test this out. We’ll add another middleware before the error one, and demonstrate how ‘next’ works:
app.use((req, res, next) => {
res.send('foo')
})Then, right under require('./routes')(app), create a GET request handler:
app.get('*', (req, res, next) => {
next()
})Here’s how the code should look:
Refresh or visit localhost:3000 (or your PORT) now, and you’ll see a blank page with ‘foo’ on it:

You can create other middlware functions and change their order in the stack:
app.get('*' ...)
app.use((req, res, next) => {
res.send('bar')
})
app.use((req, res, next) => {
res.send('foo')
})In the case of above, you’ll get a message ‘bar’ appearing in the browser.
However, if you change the GET handler to call next with an argument:
app.get('*', (req, res, next) => {
next('boom')
})You’ll see that the regular middleware are skipped and instead the error middleware is called:

Note:
next()without an argument will never reach the error middleware. You’ll simply get acannot GET /message if there is no further middleware.
Wrap Up
Entry files should contain minimum calls and configuration. A suitable server entry file involves creating a server using Node’s http.createServer method with an express application and binding to a PORT specified by the environment.
Express applications consist of an initial configuration and a series of functions called middleware. Middleware is divided into regular and error-handling, the latter receiving 4 arguments err, req, res, next while the former only receiving only req, res, next. The order of middleware matters and the next function calls the next middleware defined in the stack. To reach an error middleware, next is called with an argument.
The code for the entire project RUN__ON is on Github:
And the code for this specific article is in a pull request:
