Beginner's guide to building a server using Express as a Node.js framework
Lisa Jung
Posted on September 3, 2020
In my previous blog, Beginner's guide to creating a Node.js server, we looked under the hood of Node.js.
Now that we have an understanding of how Node.js works, we can finally use Express to build your server. Get excited because Express is about to make your life so much easier!
What is Express?
Express is a Node.js web application framework that provides a robust set of features for web and mobile applications(definition from express.js).
In another words, the long lines of code we had to write with Node.js to extract data from an HTTP request, handle and parse data, and other grunt work can now be handled for us by Express.
This framework comes with a suite of tools, utility functions, and rules on how the app should be built. It allows us to install third party packages in our project to complete tedious tasks for us.
Because of that, we can now write cleaner code and focus on our business logic to make our app even more awesome.
Ready to get more done with less code? Let's get started!
By the end of this blog, you will be able to:
- understand the core concepts of Express such as middleware, routing and serving files
- build a simple server using Express that responds to get and post requests
Prerequisite Installation
If you do not have Node.js installed already, download it here. Save it and run the installer.
Prerequisite Reading
I will be making frequent references to my previous blog to highlight the difference between Express and Vanilla Node.js.
I highly recommend you read it beforehand as it will help you gain a deeper understanding of Express concepts.
GitHub Repo
Here is the GitHub repo of the server we will be building. Feel free to refer to this repo if you encounter any errors while building your server.
In the repo, there is a folder titled views. This contains html files that our server will send to the browser.
You will be instructed to copy and paste the content of these files to the files in your server in later steps.
But more on that later! Just have it pulled up on a different tab for now.
What are we making?
I am a sucker for funny dog names. We will be creating a very simple server that users can use to submit a funny dog name.
On the home page, the user will be greeted with a welcome message. There will be a link in the nav bar called "Share Funny Dog Names."
When clicked, this link will take you to the funny-name page where you will be asked to submit a name through a form. This action will send a post request to the server.
When the server receives the post request for user input, it will redirect the user to the home page and print the user input in the console of your server.
There are a lot of things we need to install and set up before we can begin to code. Follow steps 1-9 to complete the set up.
Set up
Step 1: Create a directory for our server
In the appropriate directory, type in the following in your terminal.
#in terminal
mkdir Indiana_Bones
Get into Indiana_Bones directory and open it up in your text editor.
#in terminal
cd Indiana_Bones
code .
Step 2: Create server.js within Indiana_Bones
In your terminal, execute the following command.
#in terminal
touch server.js
You will see that server.js has been created within your directory.
Step 3: Create a folder called "util" at the same level of server.js
Inside of util folder, create a file called path.js
Step 4: Create a folder called "routes" at the same level of server.js
Inside of routes folder, create two JavaScript files:
- funny-name.js
- home.js
Step 5: Create a folder called "views" at the same level of server.js
Inside of views folder, create two html files:
- funny-name.html
- home.html
Your directory should look like the following:
These two files will contain html code that will display info in the browser. When our server receives HTTP requests from the browser, we will be sending these files as a response.
As this blog will solely focus on how we can use Express to create a server, we will not be going over the html code.
I have provided the html code in a GitHub repo so you can copy and paste the code into the designated files in our server.
Go to this repo and click on views folder. Inside, you will see two html files: funny-name.html and home.html.
You have identical folder and file structure in your server. Copy and paste the content into the respective files in your server.
Step 6: Create a package.json file
If you are a beginner, the chances are you have used npm (Node Package Manager) multiple times without really understanding what it is.
npm is an online repository for the publishing of open-source Node. js projects; second, it is a command-line utility for interacting with said repository that aids in package installation, version management, and dependency management(excerpt from node.js).
We will be installing third party packages from npm to do all the grunt work for us.
In order to start, we need to create a package.json file. This file essentially keeps track of all the packages and applications it depends on, information about its unique source control, and specific metadata such as the project's name, description and author(excerpt from nodesource.com).
In your terminal, type in:
#in terminal
npm init
A series of questions regarding the details of your app will be presented to you as you press enter after each question.
You can fill out this info if you choose to but for the purpose of this tutorial, we can skip this part. Press enter multiple times until the terminal exits out of the questionnaire.
At this time, you should see that package.json file has been created for you. We will go over how this file comes into play in a bit!
Step 7: Install nodemon
In my previous blog, we had to manually restart the server every time we wanted to see the result of making changes to our code.
We could avoid this tedious task by installing a third party package called nodemon. This will restart the server for you every time you make changes to your code!
Install nodemon by running the following command in the terminal.
#in terminal
npm i nodemon --save--dev
Notice how I added --dev at the end? We are specifying that we will be using this tool only during development. We do this because when our app is deployed, nodemon does not have to be installed on any server where we will run our application.
In the file explorer, you will now see that package-lock.json file has been created for you(blue box). If you look at package.json file, you will see that nodemon has been added as a dependency(red arrow).
Look at the scripts section highlighted with a green box. You will see that "start" script is set to "node server.js". Change this to:
#in package.json
"start": "nodemon server.js"
This step accomplishes two things:
- it allows nodemon to automatically restart the server whenever you make changes to your server side code
- we no longer have to use the command "node server.js" to start the server. The server can now be started with the command "npm start".
Step 8: Install Express
#in terminal
npm i --save express
Express is an integral part of a deployed app that must be installed on any server where we run our app. By omitting --dev after --save, we are installing Express as a production dependency.
You will now see that express has been added as a dependency in package.json.
Step 9: Install body-parser
body-parser is a third party package that parses incoming request bodies. It essentially extracts the entire body portion of an incoming request stream and exposes it on req.body.
This will save us from having to write long lines of code later but we will delve more into this down the road.
For now, install it by running the following command in your terminal.
#in terminal
npm i body-parser
Creating a server and middleware functions with Express
Step 1: Create an Express app
In server.js, copy and paste the following code.
#in server.js
const express = require('express');
const app = express();
const homeRoutes = require('./routes/home');
const nameRoutes = require('./routes/funny-name');
const bodyParser = require('body-parser');
app.use(bodyParser.urlencoded({extended: false}));
app.use(homeRoutes);
app.use(nameRoutes);
app.listen(3000);
server.js starts a server and listens on a specified port for requests. This is where request is received and where request is funneled through middleware functions until it finds a middleware designed to respond to that request.
Let's go over this line by line. To explain the code in a logical manner, I may skip around a little bit so be sure to pay attention to the line numbers.
Line 1
In order to create a server with Express, we need to import Express to gain access to all the cool functionalities that come with it.
Line 2
Among these functionalities, express() is used to create an Express app. Set express() equal to a constant app.
Line 13
app.listen(3000) creates a server that listens on port 3000.
Lines 7-8
Our server will receive data through the body of incoming request. Before we can work with the data, we need to first parse the data.
During setup, we have installed bodyParser. It is a function that parses incoming request and handle data by listening for req.on('data') and constructing req.body from the chunks of data it receives. For more explanation on this concept, check out my blog here.
bodyParser parses the data differently depending on its type and it requires us to specify the data type.
The data types could vary and are listed below:
- application/x-www-form-urlencoded
- multipart/form-data
- application/json
- application/xml
- others
On line 7, we import the bodyParser into server.js.
On line 8, we specify that that our bodyParser will be parsing bodies of data sent through a form.
In my previous blog, we built a server with Vanilla Node.js. In order to parse the incoming request data, we had to write long lines of code below.
#route.js from a previous blog r
if (url === "/mood" && method === "POST") {
const body = [];
req.on("data", (chunk) => {
body.push(chunk);
});
return req.on("end", () => {
const parsedBody = Buffer.concat(body).toString();
console.log(parsedBody)
const mood = parsedBody.split("=")[1];
fs.writeFile("user_mood.txt", mood);
return res.end();
});
}
We were able to accomplish the same thing with just two lines of code(lines 7-8) with Express!
Lines 10-11
These are our middleware functions. Middleware functions are loaded by calling app.use() or app.http method such as app.get(). When a request is received by the server, it travels through middleware functions from top(app.use(homeRoutes)) to bottom(app.use(nameRoutes)).
This diagram from expressjs.com does a fantastic job showing the elements of a middleware function call.
The details of middleware functions have actually been written in routes files to compartmentalize our code and keep server.js looking nice and clean. We will delve into this diagram shortly!
Lines 4-5
In order to get access to the middleware functions defined in routes files(home.js and funny-name.js), we import these files into server.js. These are set equal to constants homeRoutes and nameRoutes respectively.
Lines 10-11
Then we pass middleware functions defined in these files into app.use(homeRoutes) and app.use(nameRoutes).
If the concept of middleware goes straight over your head, don't worry. We will be going over this more in detail in the next step. I just wanted to expose you to the concept so we can connect the dots at the end of this blog.
Step 2: Understand Middleware
Middleware is one of the most important concepts to understand while building with Express. Let's dig deeper into it!
The excerpt from okta had one of the best explanations I have encountered thus far. I am sharing that definition in a paragraph below.
"Express middleware are functions that execute during the lifecycle of a request to the Express server. Each middleware has access to the HTTP request and response for each route (or path) it’s attached to. In fact, Express itself is compromised wholly of middleware functions. Additionally, middleware can either terminate the HTTP request or pass it on to another middleware function using next. This “chaining” of middleware allows you to compartmentalize your code and create reusable middleware."
This diagram breaks down the elements of a middleware function call.
Middleware functions have access to the request object(req), the response object(res) and the next function(next). It is prefaced by the http method and url path(route) of the request that triggers the function defined within.
This function can execute any code, make changes to the request and the response objects, end the request-response cycle, or call the next middleware in stack(excerpt from express.js).
When a request is received by the server, it is funneled through middleware functions from top to bottom. It keeps traveling down until it finds the middleware designed to handle that request. When the request finds the right middleware, the middleware sends an appropriate response to the browser.
I have created a diagram to help you visualize this concept a little better.
I was thinking about how I can make this tutorial even nerdier and came up with this analogy that may help you understand this concept a little better.
Imagine yourself as the hero of an old school video game. Your quest is to find the treasure hidden in huts and castles. In the beginning of the game, you are given a magical key(request). Your journey begins in a one way path, where you will go through mysterious huts and castles(middlewares) that may contain the treasures(response) you seek. You must try to open the door to these mysterious locations with your key(request). Only when you have found the door that is opened with your key, will you have found the treasure and have achieved your mission(response sent to the browser).
But this game is not as easy as it seems. Some of the huts and castles are marked with a secret emblem(next()). The locations with this emblem will transport you to the next location(next middleware) whereas the locations lacking these emblems will trap you inside for eternity.
Ok ok... I will stop geeking out. Let's write some middleware functions and see what it's all about!
Step 4: Set up router-level middleware functions for funny-name page
As seen in the end product demo, our app will have two pages: a home page that displays a greeting message and a funny-name page that displays a form. The user can use this form to submit their funny dog name and send a post request to the server.
Once the server receives the post request, it redirects the user to the home page and prints the user input in the console of your server.
Let's start with the funny-name page.
This page can send two types of requests.
1.) GET request: When a user is directed to localhost:3000/funny-name, the browser sends a get request to the server. Our server will send funny-name.html file to the browser. This file contains html code that displays a nav bar, a form, and a meme on the page.
2.) POST request: when a user submits a funny dog name through a form, the page will send a post request to the server. User input will be printed in the console of our server(red box).
As mentioned earlier, middleware functions are loaded in server.js. However, we did not write out middleware functions in server.js. We wrote it out in different modules(routes>home.js & routes>funny-name.js) to compartmentalize our code.
In your routes folder, open funny-name.js file. Copy and paste the following code.
#in routes>funny-name.js
const express = require("express");
const router = express.Router();
const path = require("path");
const rootDir = require("../util/path");
router.get("/funny-name", (req, res, next) => {
res.sendFile(path.join(rootDir, "views", "funny-name.html"));
});
router.post("/funny-name", (req, res, next) => {
console.log(req.body);
res.redirect("/");
});
module.exports = router;
In your util folder, open path.js file. Copy and paste the following code.
#in util>path.js
const path = require('path')
module.exports = path.dirname(process.mainModule.filename)
Let's go over this line by line! We will start with the funny-name.js file.
Line 1
We import Express into funny-name.js file.
Line 3
Importing Express gives us access to express.Router().This allows us to create router-level middleware to respond to certain HTTP requests.
Router-level middleware works in the same way as application-level middleware, except it is bound to an instance of exprerss.Router()(excerpt from Express.js).
SEt express.Router() equal to the constant router.
Lines 9-11
These lines of code sets up a router-level middleware that responds to a get request from the funny-name page(localhost:3000/funny-name).
Upon receiving a get request from the page, it sends a funny-name.html file to the browser(line 10). Let's take a closer look at this block of code.
#in routes>funny-name.js
router.get("/funny-name", (req, res, next) => {
res.sendFile(path.join(rootDir, "views", "funny-name.html"));
});
res.sendFile() is a utility function that allows us to send a file to the browser. Within the parenthesis, we must specify the path to the file we want to send(funny-name.html).
Your intuition might tell you include the file path within the project like the following.
#in routes>funny-name.js
router.get("/", (req, res, next) => {
res.sendFile('./views/funny-name.html');
});
However, you will encounter the error,"Path must be absolute or specify root to res.sendFile." if you try to execute it.
res.sendFile requires absolute path in our operating system to this file. We can get the absolute path quite easily with with a core module called path and a helper function defined in path.js file.
We need to import both into funny-name.js. Lines 5 and 7 takes care of that.
Line 5
We import a core module called path.
Line 7
We import path.js file from our util folder.
Both of these will come into play in line 10 where we send the funny-name.html file to the browser in response to a get request.
Let's turn our attention to path.js in our util folder.
Line 1
We import path core module into this file to gain access to its .dirname() method.
Line 3
path.dirname(process.mainModule.filename) gives us the absolute path to the file responsible for running our app. In our case, it is server.js. We export this helper function so that we can access it from funny-name.js.
Line 7
We import the helper function into funny-name.js.
Line 10
By using path.join method, we concatenate "views" and "funny-name.html" to the absolute path of server.js. This allows us to construct a file path of funny-name.html and enables the server to send the correct file to the browser.
Lines 13-16
We set up a router-level middleware so that when we receive a post request, the user input is printed in our server console and the user is redirected to the home page.
This middleware is very similar to the get request we have already written.
You will see that after router, we have specified the HTTP request to post. Inside our function, we have console logged user input which is stored in the body of the request(req.body).
Then, we use res.redirect and specify the url of the page we want the user to be redirected to.
Line 18
We export the router as we will be importing all the router-level middleware to middleware functions in server.js
Step 5: Set up router-level middleware function for home page
This router-level middleware function will respond to a get request from localhost:3000/. When it receives the request, it will send the home.html file to the browser as a response. This file contains the html code that will displays the following info in the home page.
In your routes folder, open home.js file. Copy and paste the following code.
#in routes>home.js
const express = require("express");
const router = express.Router();
const path = require("path");
const rootDir = require("../util/path");
router.get("/", (req, res, next) => {
res.sendFile(path.join(rootDir, "views", "home.html"));
});
module.exports = router;
The home.js file will look almost identical to funny-name.js.
The only difference is that home.js has only one router-level middleware function. When it receives a get request, it sends home.html file as a response!
The moment of truth
Now that we have written our code, let's test things out. Fire up your server by running the following command in your terminal.
#in terminal
npm start
Open up a browser and enter localhost:3000 in the url bar.
You should see a home page like the following.
On the home page, click on Share Funny Dog Names option in the nav bar. It should take you to localhost:3000/funny-name
On the funny-name page, submit your funny dog name using the form. Return to your server and look at your console. You will see your input there!
If you encounter any errors, try copying and pasting my code from my repo exactly. The chances are you may have a small typo or spacing error that is hard to detect with your eyes.
Congrats on making it to the end of the blog. This was no easy feat and you guys have made it. Now go create something fun!
Posted on September 3, 2020
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.