Introduction to Node’s Express JS

A NodeJS beginner’s tutorial

Robbie Jaeger
10 min readAug 7, 2017
Photo by Dean Johns

After reading through a bunch of Express introductions lately, many are either outdated or go too in-depth too quickly, incorporating databases and templating engines right off the bat. In this more basic introduction, we’ll touch on many basics of an Express server:

  1. Express setup
  2. A basic server file
  3. How to serve static assets
  4. How to serve JSON
  5. Express middleware

Before we get into it, I want to give a shout out to Express for their awesome documentation. As a former doc writer, I appreciate this.

What is Express? It’s a minimalist and extensible web framework built for the Node.js ecosystem. It enables you to create a web server that is more readable, flexible, and maintainable than you would be able to create using only the Node HTTP library, which can get verbose and complicated for even the most basic web servers. Express is going to make creating a web server much easier! As a matter of fact, it’s difficult to even find examples of real-world web applications that use only the Node HTTP library because you’d have to be sadistic to do it.

Some Assumptions

To make this post fairly short, I’m making assumptions that you have a basic knowledge of the request-response cycle, HTTP request methods, and fundamental JavaScript (with some ES6 syntax). Let’s get started!

Setup

Make a new directory to hold this project, and then change into that directory. As most Node projects begin, open your terminal and start with initializing NPM to create a package.json file. Then install Express as a dependency.

npm init --yes
npm install --save express

The Server

We’ll start with the classic “hello world” example, explain that, and then build from there.

First, create a new file called server.js that will hold the code for, you guessed it, our Express server. In this file, write the following code:

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

app.get('/', (request, response) => {
response.send('hello world');
});

app.listen(3000, () => {
console.log('Express intro running on localhost:3000');
});

In your terminal, start the server using the command:

node server.js

Then head on over to localhost:3000 in your browser, and you should see:

Classic “hello world” example output in the browser.

Viola! Let’s talk about the first two lines.

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

The first line requires the Express module that was installed via NPM, and the second line sets up our Express application. With this application (app), you can configure and add functionality to your server.

app.listen(3000, () => {
console.log('Express Intro running on localhost:3000');
});

The app.listen() function tells the server to start listening for connections on a particular port, in this case port 3000. This is why we went to localhost:3000 to look at our hello world example. When the server is ready to listen for connections, the callback is called and logs Express Intro running on localhost:3000 in the terminal.

The last part is the route handler:

app.get('/', (request, response) => {
response.send('hello world');
});

This part is fairly dense because Express is able to give a lot of functionality with very little code. app.get() creates a route handler to listen for GET requests from a client. The first argument in this function is the route path. In this case, we’re listening for GET requests on localhost:3000/. If we wanted to listen for a POST request, then we would use app.post() — for a PUT request, app.put(), and so on for any other HTTP method.

The second argument is a callback function that takes a request object and a response object. The request object contains information about the request that came from the client (request headers, query parameters, request body, etc.). The response object contains information that we want to send as a response back to the client. The response object also has functions that enable us to actually send a response.

Inside app.get(), the response.send('hello world'); function sends a response with content in the body of the response. In this case, the body contains the plain text hello world. Now we know how to set up a route!

How to Serve Static Assets

So we can serve back plain text, but what about an actual HTML file with styling? For that, we need to tell our server where to look for static asset files. “​​Static [assets are] any content that can be delivered to an end user without having to be generated, modified, or processed.” We need to configure the static path in Express. Think of most HTML, CSS, images, and client-side JavaScript files as static assets.

Before we configure the static path, let’s make some static assets to serve. Create a directory called public in the root of the application to hold static asset files. In the public directory, create two files: index.html and styles.css. So the project structure looks like this so far:

express-intro
|__ server.js
|__ node_modules
|__ public
| |__ index.html
| |__ styles.css

In the index.html, use this basic HTML structure with the stylesheet linked:

<!DOCTYPE html>
<html>
<head>
<title>Express Intro</title>
<link rel="stylesheet" type="text/css" href="styles.css">
</head>
<body>
<h1>My Static File</h1>
</body>
</html>

The styles.css file simply contains:

h1 {
color: blue;
}

Now we want to serve the index.html file when a client requests localhost:3000/. For Express to know that the index.html file is in the public directory, we have to configure the static assets path.

When Express gets a request for static assets at localhost:3000/, it will look in the static assets directory and try to find an index.html file at the root level of the static assets directory. In this case, it would be public/index.html.

In server.js, add this line to configure the path to use for static assets:

app.use(express.static('public'))

So what is this line really doing for us? Firstly, app.use() configures your application to use a middleware function (we’ll get into middleware in the next section). At a high level, app.use() says for every request to the server, always run the function passed into app.use(), kind of like a callback function. In this case want to say: for every request to the server, make sure to use the specified directory as a starting point for all static asset files.

The express.static('public') part defines the path to your static assets. The 'public' string refers to a directory where your static files are stored:

const express = require('express');
const app = express();
app.use(express.static('public'));app.get('/', (request, response) => {
// We don't need to explicitly use this handler or send a response
// because Express is using the default path of the static assets
// to serve this content
});
app.listen(3000, () => {
console.log('Express Intro running on localhost:3000');
});

Start up the server with node server.js and check it out! It’s not the most beautiful thing, but it is a thing, and you made the thing.

Serving the static HTML and CSS file.

Take some time to go through the server file again. Read through the paragraphs above to solidify the reasoning behind each line.

How to Serve JSON

For some applications, we’ll want to respond with data in the form of JSON instead of responding with an HTML page. Let’s make an endpoint that responds with a basic JSON object.

First, make a route handler in the server file for a GET request to localhost:3000/json. The route path'/json' could be any string after /, but we’ll name it after the data type we’re serving just for clarity:

app.get('/json', (request, response) => {
// Respond with JSON data here
});

We’re not dealing with static asset files here. So we need to be explicit and tell Express what we want send as the response and how we want send it. Let’s respond with the object {"name": "Robbie"}. We also want to send a status code to notify the client that the response was a success. Modify the route to:

app.get('/json', (request, response) => {
response.status(200).json({"name": "Robbie"});
});

We’re using the response object to build a response and then send the response. First, we add the status code (200 success) to the response object, and then send JSON data as the response using .json(). So now if you stop the server with control + c and restart it with node server.js, head over to localhost:3000/json and you should see something like this:

JSON response from localhost:3000/json

Awesome —this is just a simple JSON object, but imagine the possibilities of the data you can generate and host on your own server now!

Middleware

We already mentioned middleware above with app.use(). Let’s get a little more familiar with middleware and then we can create our own.

Photo by Hieu Vu Minh

Express middleware are functions that run after a request is received but before the route handler function. Middleware functions have access to the request object, response object, and a function called next to pass control to the next middleware function or to the route handler.

Let’s begin to add our own custom middleware to the server. The basic structure of a middleware function is:

const middlewareFuncName = (request, response, next) => {

next();
};

The same structure with some comments:

const middlewareFuncName = (request, response, next) => {
// Middleware code to run here
// Move on to next middleware function or the route handler
next();
};

To start, let’s add some middleware to log the request URL to the terminal. Add this function to your server.js file:

const urlLogger = (request, response, next) => {
console.log('Request URL:', request.url);
next();
};

Then add the middleware as a callback function in the route handler for localhost:3000/json. This middleware will run only for this route because we’ve added the function reference as a callback in the route handler.

app.get('/json', urlLogger, (request, response) => {
response.status(200).json({"name": "Robbie"});
});

Restart the server, and in your browser go to localhost:3000/json. In your terminal, you should see something like this — success!

Terminal output from the urlLogger middleware.

Let’s add some middleware that is maybe a little more useful. Say you want to log the time that a client requests a page from the server, which you might see in your deployed application’s server logs. Let’s add the function:

const timeLogger = (request, response, next) => {
console.log('Datetime:', new Date(Date.now()).toString());
next();
};

And then add the middleware to the localhost:3000/json route handler as well:

app.get('/json', urlLogger, timeLogger, (request, response) => {
response.status(200).json({"name": "Robbie"});
});

Restart the server, head over to localhost:3000/json, then in your terminal you should see something like:

Terminal output from urlLogger and timeLogger middleware functions.

Obviously, your date and time will not be the same. We’ve added two callback functions to the /json route handler, and you can continue adding more middleware functions as a list of callbacks in the route handler.

In fact, let’s refactor this so that every route uses these middleware functions, and we won’t have to specify the callback functions for every single route handler. We can do this in one place in our server file using app.use():

app.use(urlLogger, timeLogger);

This configures our entire application to use these two middleware functions. Now you can remove the middleware callback functions from the localhost:3000/json route handler.

If you restart the server and go to localhost:3000/ and then localhost:3000/json, then you should see something like this in your terminal:

Terminal output after using app.use() to add middleware functions for every route.

What is interesting is that you can see the additional request for the stylesheet. These loggers are simple examples of middleware, but you can use middleware for more complex tasks like authentication or authorization, for instance. A site with authorization could use middleware to detect if the client is authorized to view the page before you send the response. If they’re not authorized, redirect the client somewhere else. If the client is authorized, then move on to the route handler (using the next function) and send a successful response.

Wrap Up

By the end of it all, this is what the server file looks like:

const express = require('express');
const app = express();
const urlLogger = (request, response, next) => {
console.log('Request URL:', request.url);
next();
};
const timeLogger = (request, response, next) => {
console.log('Datetime:', new Date(Date.now()).toString());
next();
};
app.use(urlLogger, timeLogger);
app.use(express.static('public'));
app.get('/', (request, response) => {
// We don't need to explicitly use this handler or send a response
// because Express is using the default path of the static assets
// to serve this content
});
app.get('/json', (request, response) => {
response.status(200).json({"name": "Robbie"});
});
app.listen(3000, () => {
console.log('Express Intro running on localhost:3000');
});

Now that you have the basics of Express, you can expand what you know to add a database, add testing for your endpoints, handle more types of HTTP methods, and so much more functionality!

Note: At this point, this server is set up only for a development environment and is not set up for a test or production environment.

Further Challenges

To challenge yourself a little beyond this blog post, here are a few things you can work on implementing:

  1. Add client-side JavaScript file in the public directory for some interactivity. Maybe add a button with an event listener.
  2. Make a new endpoint so that if the client makes a GET request to localhost:3000/sunsets, then the user sees a page with a bunch of pictures of sunsets.
  3. Instead of passing the {“name": “Robbie"} JSON object directly into .json() for the localhost:3000/json route. Create some JSON data in a separate file, load that file in the server, and send that file’s JSON data as a response instead of {“name": “Robbie"}.
  4. Add a custom 404 page for when the client makes a request to an undefined endpoint. Hint

--

--

Robbie Jaeger

Software developer,Instructor at the Turing School of Software and Design.