Express is a popular Node.js web server framework for declaratively exposing routes and handling http traffic to those routes. In this post, we'll get started building a web app based on express, but for now, we'll just set up a single route handler to return some simple HTML to the browser. I'll be using ES6 features in this post, including const, arrow functions, and template literals.

To get started, let's create a new project. I'm using Mac, but these instructions should be roughly the same for Linux. I've never done Node development on a Windows machine, so I won't be including steps for that platform, but I trust you, reader. You are industrious. I think you'll be able to ascertain the analogous tools necessary to make this work on your platform.

Let's start by cding wherever you build new projects and creating a directory for the project. I keep apps like this in ~/code/apps, so in my terminal, I'm typing:

cd ~/code/apps  
mkdir express-app  
cd express-app  

We will need to install some third-party modules using npm, so let's tell npm about our project using npm init. It will ask you for some information, and for the most part, the defaults it suggests are fine. I took the defaults except for description (which is empty) and "main" (which refers to the entry point of the app). For description I wrote "A server built with express" (although you can leave it blank if you like) and for "main" I typed "app.js" (the default is index.js). Feel free to fill in other details if you so choose. This should generate the following package.json file in the root of your project:

  "name": "express-app",
  "version": "1.0.0",
  "description": "A server built with express",
  "main": "app.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  "author": "",
  "license": "ISC"

Let's create and edit app.js now. If you use an IDE-style editor, you probably open projects and create files from within the editor itself. I use vim so I'll just type vim app.js. This file will contain our main server logic where we set up a port listener using express and route traffic to handlers. Let's start by including express. In your terminal, type npm install express to add it as a dependency of the project. (If you open package.json, you will see that it is listed under "dependencies.") Back in app.js, add the following code to get access to the express library.

const express = require('express');  

This tells Node we want to use a third-party library called "express"; by default, Node will look for required things (that don't look like directory paths) in node_modules. If you run ls node_modules in the root of your project, you should see that it now includes express . . . and probably a lot of other stuff too. Since npm 3, node modules are installed flat when possible, which just means that all of the things you see listed by ls node_modules are your dependencies and your dependencies dependencies and their dependencies etc. etc. except when there is a version conflict. Understanding how npm installs modules isn't really necessary to setting up an express server, however. I just thought you might be surprised to discover there's more in the node_modules directory than express at this point.

Let's continue by creating an instance of express. We do this by invoking the function returned by require('express'):

const express = require('express');  
const app = express();  

We will use app to configure our routing and a variety of other things related to our application. For now, though, let's just tell express what port to listen on so it can start receiving traffic:

const express = require('express');  
const app = express();

app.listen(3000, () => console.log('Express listening on port 3000'));  

app.listen takes a port number to listen on and a callback that is invoked once the server has started. We're telling express to listen on port 3000 and then log that it's listening once the server has started.

We actually have a fully functional server at this point. If you go back to your terminal and type node app.js, you should see "Express listening on port 3000," and you should also notice that your command prompt does not return. That's because, while the server is listening, the node process that you created is still running and therefore the terminal in which you ran the node command cannot be used for other things. So our server is listening, but we don't have any way to talk to it yet because we haven't configured any routing. If you try to navigate in your browser to localhost:3000 (i.e. open a new tab and type "localhost:3000" and press enter), you should see a message from express that says "Cannot GET /." Let's go back to app.js and configure the root path ("/") to respond to requests.

const express = require('express');  
const app = express();

app.get('/', (req, res) => res.send('Hello world!'));

app.listen(3000, () => console.log('Express listening on port 3000'));  

Here we've called app.get and passed it a string (the route) and a function (the handler). This tells express that when it receives a GET request for / that it should invoke this function. Each route handler (excluding error handlers, which we'll talk about another time) receives 3 parameters: req (an object with information about the incoming request), res (an object with information about the outgoing response), and next (a callback to indicate to express that the handler is done). Here, we're sending the response (via res.send) within the handler, so there's no need to call the next function, so we're not including it in the parameter list. next is useful for chaining handlers together to make common code reusable. We'll look at that in another post.

If you return to your running node server in the terminal, use Ctrl-C to terminate the process, rerun node app.js, and then refresh your browser at localhost:3000, you should now see the words "Hello world!" Notice that the server does not pick up your changes until you restart it. Once the server starts, it's an isolated process unaware of any changes to files in the project. It can become annoying to have to restart the server manually every time you change something, so let's install another module to help us manage this process. Back in your terminal, type: npm install -D nodemon. This installs the nodemon module as a development dependency (in package.json it will be listed under devDependencies). This just means that, on a real server running a production environment, it wouldn't need to be installed. dependencies are modules necessary to run a production app. devDependencies are tools for testing and automation and building assets and other local development that aren't strictly necessary to run the application.

We have one more change to make our application restart itself. We installed nodemon locally (as opposed to globally), which means running nodemon from the command line produces "-bash: nodemon: command not found." Open up package.json again and under scripts (which should already by there because, by default, npm init adds a test command there), add "start": "nodemon app.js". Things inside scripts are commands that can be run via npm, but npm also places any locally installed module binaries temporarily in your path to make them accessible. If you return to the terminal and run npm start, npm will run the script labeled "start" which will run nodemon. This time, however, it will work fine because npm start adds the nodemon binary to the path of the process as it invokes the command from package.json. When you type npm start, you will see more output than before, but at the bottom, it should still say "Express server listening on port 3000."

Let's make one final change to our app to make it more semantically correct (and to see nodemon in action). We're returning text in our route handler right now. If you inspect "Hello world!" in your browser (right click on the text and click "Inspect" or "Inspect Element" if you've never done this before . . . it will show you the HTML on the current page and highlight the element you inspected), you'll see that it inserts that text directly into the body tag. Since this is an HTML server, we should probably return HTML from our route. Let's return to app.js and change the response text to look like this:

const express = require('express');  
const app = express();

app.get('/', (req, res) => res.send(`  
    <h1>Hello world!</h1>

app.listen(3000, () => console.log('Express listening on port 3000'));  

Now when we receive a GET request at /, we're returning a full and valid (albeit sparse) HTML document. Notice first, in your terminal, that nodemon detected the change (it should say "restarting due to changes...") and restarted the server for us. That means we don't need to use ctlr-c to terminate the process and then rerun it. Back in your browser, if you refresh the page, you should now see that "Hello world!" is much larger and bolder. That's because we made it an h1. If you inspect the DOM using your developer tools, you should see that the structure matches what we returned from the server.

That's a good starting point. Next time we'll look at adding an error handler and a 404 route handler to catch any routes not explicitly included in app.js. Thanks for reading! Feel free to comment with parts of express or web development patterns you'd like to see covered in this series or with anything that I skimmed over too quickly.