Build a Web App with Node.js and Express
Intro
This is the last tutorial in the Beginning Programming / Web Development series. We will use Node.js and Express to build a simple blogging web application.You must have Node.js installed. In the terminal enter node --version
If it returns the version number it is installed. If it returns an error you need to install Node on your computer.
nodejs.org
We'll start with some definitions.
Background and Definitions
Website vs Web Application: A website provides information. A web application is a website that also provides a service.Think of a library website. If it only provides the address, hours of operation and pictures of the library it is a simple website. If it allows you to apply for a library card, reserve books, reserve study rooms, etc. then it is also a web application.
WordPress vs coding your own: Small business owners often have a website. If the website mainly provides information with a few services then you don't need to code it from scratch. WordPress is a very popular free open-source software application that lets you build a simple website without doing any programming. It does the programming for you behind the scenes. You can build a good looking website for your small business in a day, or hire someone to do it for you. There are web hosting services that bundle buying a domain name and hosting a WordPress website on their servers for around $100-$200 per year.
You can add WordPress plug-ins for a small online store, email, and much more.
Why don't all websites just use WordPress then? For sites with heavy traffic WordPress adds a lot of software overhead which slows performance. And it is not very flexible. While there are many good WordPress plug-ins, you can only use the features and plugins that are available. If you want to customize a feature or create a new one then you need to do your own programming within the confines of WordPress. That can get complicated and requires you to learn a programming language called PHP and learn how WordPress works behind the scenes.
Steps to launch a Web App: If you are coding your own web app you need to take the following steps to launch it:
- Register a domain name.
- There are a number of domain name registries where you can search for and purchase an available name.
- If your name is unique then it will be simple. If you own Joe's Restaurant in San Francisco then joesrestaurantsf.com is probably available. It will cost you around $20 per year.
- Generic names like restaurant.com though are all taken.
- Decide on the site architecture.
- What programming language and framework are you going to use on the server?
- What type of database are you going to use?
- Are you going to use a front-end framework or library like React?
- Develop the site locally on your computer.
- Set up the development environment on your computer. At a minimum that means installing a text editor like VS Code, installing Node.js, and knowing how to use a Terminal application.
- Develop and test all features locally before putting them on the server.
- Upload the site on a web server and attach a database.
- There are a number of web hosting services available. They charge a monthly fee that depends on how much traffic your site receives and what add ons it has such as a database.
- Maintain the site.
- Once your site goes live you will need to maintain it. That means adding or modifying site content, identifying and fixing bugs, and adding or modifying site features.
- Make the changes in your development environment. Test them. When ready, upload them to the server.
For this tutorial we will focus on steps 2 and 3. Decide on the site architecture and develop the site locally.
Site Architecture: For any web application you need to decide on the architecture up front. That includes determining what programming language and framework to use, what database to use, and whether to use a front-end framework or library like React. We will use JavaScript with Node.js and Express, a SQLite database, and we will not use a front-end framework.
Language and Framework:
- A programming language provides basic programming functionality like creating and working with objects, arrays, functions, etc.
- A Web framework is software designed to support a web application.
- Rather than coding the whole site by hand a web framework sets up most of the functionality for you. The framework has lots of code that operates behind the scenes.
- The framework will have an API with methods you can call in your code. You just need to know what methods to call, and what they do, without having to know all the details of how they work.
- You can add in other third-party software packages to do specialized activities like interacting with a database.
- Most of the popular frameworks are free and open sourced which means teams of volunteers maintain it.
- Popularity of a framework is critical because the more popular a frameworks is the more likely there will be high quality third party packages available to use in your app.
- For most web apps you can use the programming language and framework you are most comfortable with. Some of the most popular include JavaScript with Node and Express, Ruby and Rails (aka Ruby on Rails), Java and Spring, Python and Django, PHP and Laravel, and Microsoft's ASP.NET.
JavaScript with Node.js Express: Virtually all web applications use JavaScript on the front end to manipulate the web page directly in the browser.
- Node.js: In 2009, Node.js was released allowing users to run JavaScript on servers. Node took the JavaScript engine software from the Chrome browser and added its own modules to interact with the server's operating system and the internet. For a solo developer this means you can program your entire website, front-end and back-end, using one programming language: JavaScript.
- Npm: Node.js comes with a package manager called npm (Node Package Manager) that lets you install third party software packages directly in your application. Npm packages are either entirely coded in JavaScript or have a JavaScript interface. A list of all the available npm packages is at: npmjs.com
- Express: Express is the most popular web framework available as an npm package.
- It is light-weight which means it doesn't add a lot of software overhead and is very flexible.
- It is not highly opinionated which means common features can be set up many different ways. That much flexibility has its own upsides and downsides. The biggest upside is you are more in control of how your app functions. And there is less "voodoo" going on behind the scenes.
- The express website lists the API methods available: expressjs.com
- Other npm packages: You will usually use multiple npm packages in your web app in addition to Express. We will be using the Sequelize and Sqlite3 packages to create and interact with a database.
- EJS (Embedded JavaScript) is a simple templating language. It uses regular HTML markup but has a special syntax for injecting JavaScript. EJS files use the .ejs extension. We will use EJS to generate our HTML files.
- EJS VS Code extension: If you are using the VS Code text editor install the EJS Language Support extension. When the extension is installed the syntax highlighter will recognize the .ejs file extension and property highlight the EJS syntax. To add the extension:
- Open VS Code.
- Click the extensions icon
- Enter "EJS language support" in the search box and click
Install
.
Setup the App
Express Generator:
- Let's get started. Fortunately Express has a program called express-generator that will build a skeleton web application for us.
- Open your Terminal application and cd to the directory you want to put the web project in.
- Decide on a name for the application. We'll call it express-blog-app.
- Run the command: npx express-generator express-blog-app --view=ejs
- This will create a directory called express-blog-app with some directories and files that create a very simple web application.
- The npx command runs an npm package without installing it on your computer.
- The --view=ejs option tells Express what templating engine we want to use. We will use EJS.
- Open the express-blog-app project with VS Code.
- Click the Terminal menu and select New Terminal to open a new terminal window.
- Express generator does not install any of the packages we need in our project so we need to install them. The npm packages used in our projects are called "dependencies". In the terminal enter: npm install
- Now we can run our development server: npm start
- Open a browser tab and go to: http://localhost/3000
- You should see a simple web page that says "Welcome to Express".
- Congratulations, you have a working web application using Node.js and Express. Now lets take a quick look at the directories and files and what they do.
File tree:
├── package.json ├── node_packages... ├── public
│ ├── images
│ ├── javascripts
│ └── stylesheets
│ └── style.css ├── bin
│ └── www ├── app.js ├── routes
│ ├── index.js
│ └── users.js
└── views
├── error.ejs
└── index.ejs
Explanation:
- package.json is the manifest file for a Node.js project. It contains the metadata of the project including the project name, version number, short command line scripts, and names of installed npm packages.
- node_packages: This folder holds the npm packages installed in your app.
- public folder holds the static assets used by our app including images, stylesheets and front-end JavaScript files.
- bin/www: This file is the entry point for the app. It creates the web server using Node.js.
- app.js: This file is launched when the web server is created. It loads the all the module dependencies, and creates the express app object which registers our route handlers and connects our public assets and views to the application.
- routes/index.js: When a user enters a URL in their browser it sends a GET or POST request over the internet to the server. GET request get a web page, POST requests post data from a form. The server receives the request, parses the URL, and routes it to the correct handler function. Right now we just have one route. The handler function just sends the home page back to the browser.
- views: The views directory holds our EJS files which convert to HTML. Right now we just have an index.ejs file and an errors.ejs file to display error information.
Remove unneeded code/files
- Delete users router: Express generator generates an empty users router. We won't be using that so:
- Delete the file routes/users.js.
- Open the app.js file and delete two lines:
var usersRouter = require('./routes/users');app.use('/users', usersRouter);
- Delete styles.css content: We will be using the Bootstrap framework for styling so:
- Open the stylesheets/style.css file and delete all the content.
Add header and footer partials
- We can pull out HTML markup that gets repeated in multiple files and put it into separate files called partials.
- Add two files to the views directory called header.ejs and footer.ejs and populate them with the below markup.
- The example code on GitHub has a favicon image in the images folder called blog-favicon.png. So we'll put a favicon link in the head element.
- We'll use the Bootstrap Styling Framework's CDN link. Get the latest link at getbootstrap.com
- We'll leave the styles.css stylesheet link even though we won't actually use it in this tutorial. We will leave it because you may want to add your own style rules to the app that aren't available in Bootstrap.
views/header.ejs
<!DOCTYPE html>
<html>
<head>
<title><%= title %></title>
<link href="/images/blog-favicon.png" rel="icon" type="image/x-icon">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
<link rel='stylesheet' href='/stylesheets/style.css'>
</head>
<body>
<nav class="navbar navbar-dark bg-dark">
<div class="container">
<a class="navbar-brand" href="/">Blog App</a>
</div>
</nav>
<main class="container mb-4">
views/footer.ejs
</main> </body> </html>
- Now change the index.ejs file to remove the HTML from the top and bottom of the file and include the header and footer partials.
views/index.ejs
<%- include('header') %> <h1><%= title %></h1> <p>Welcome to <%= title %></p> <%- include('footer') %>
- Now lets see the web page with the Bootstrap styling.
- Stop the server from the Terminal by pressing Control+C
- Restart the server: npm start
- and view the changes in the web browser at http://localhost:3000
Database
- Most web applications use a database to store data.
- SQLite: We will use a simple relational database called SQLite because it is trivial to install and use. It is fine to use in development but for a production app we would use a more powerful database like Postgres, MySQL or MongoDB. Those databases are more complicated to set up in your development environment.
- Sequelize: We will also use the Sequelize npm package. Sequelize provides an interface between your JavaScript program and the database. Instead of having to enter commands using the SQL database language, you can enter JavaScript methods provided by Sequelize that will do it for you behind the scenes. This type of package is called an ORM (Object Relational Mapper).
- Install the SQLite database as an npm package called sqlite3. And install Sequelize:
- npm install sqlite3 sequelize
- Sequelize has a command line tool that will generate the code for us.
- First generate the basic file structure for interacting with the database.
- npx sequelize-cli init
- This command may give you the below prompt:
Need to install the following packages:
sequelize-cli@versionNumber
Ok to proceed? (y)- Enter: y
This will add the following file tree to the project:
├── config │ └── config.json ├── migrations ├── models
│ └── index.js
└── seeders
- The config.json file is set up to interface with a MySQL database. Change it to interface with an SQLite database by replacing the code in the file with the below. This will create the database in a file called database.sqlite3.
config/config.json
{ "development": { "dialect": "sqlite", "storage": "./database.sqlite3" } }
- Now generate a data model named Article. To add fields include the --attributes option and list the field names paired with the data type. Every database has its own set of data types. Add fields named title and content with data types string and text. The string data type is for a short line of text while the text data type is for text that is more than one line.
- npx sequelize-cli model:generate --name Article --attributes title:string,content:text
- This creates two files.
├── migrations │ └── timestamp-create-article.js └── models
└── article.js
- The migration file will be used to generate the articles database table.
- The models/article.js file connects the code in the routes file to the articles database table.
- Run another sequelize command to create the database and add the articles table.
- npx sequelize-cli db:migrate
Add the Blog ejs files and routes
- Add the form file to create a new blog post called create.ejs. Put it in the views folder.
views/create.ejs
<%- include('header') %> <h1>Create Article</h1> <hr> <form method="POST" action=""> <div class="mb-3"> <label for="title" class="form-label">Title</label> <input type="text" id="title" name="title" class="form-control" maxlength="200" required> </div> <div class="mb-3"> <label for="content" class="form-label">Content</label> <textarea id="content" name="content" rows="10" class="form-control" required></textarea> </div> <button type="submit" class="btn btn-secondary mt-3">Submit</button> </form> <%- include('footer') %>
- Change the index.ejs file to show a list of all the articles.
views/index.ejs
<%- include('header') %> <h1 class='mb-4'> Articles <a href='/create' class='btn btn-outline-secondary float-end'> Create new article</a> </h1> <ul class="list-group"> <% articles.forEach((article) => { %> <li class="list-group-item"> <a href="/<%= article.id %>"><%= article.title %></a> <small> (Date <%= article.createdAt.toLocaleString("en-US", { month: 'short', day: 'numeric', year: 'numeric' }); %>) </small> </li> <% }); %> </ul> <%- include('footer') %>
- Create a file called detail.ejs that displays a specific article.
- It has buttons to update or delete the article.
- The database creates a file called createAt that stores the date the record was created. We display the date on the detail page.
views/detail.ejs
<%- include('header') %> <h1> <%= article.title %> <a href="/<%= article.id %>/delete" class="btn btn-outline-danger float-end ms-2">Delete</a> <a href="/<%= article.id %>/update" class='btn btn-outline-secondary float-end'>Edit</a> </h1> <p> <strong>Date:</strong> <%= article.updatedAt.toLocaleString("en-US", { month: 'long', day: 'numeric', year: 'numeric' }); %> </p> <hr class='mt-2'> <div style="white-space: pre-line"> <%= article.content %> </div> <%- include('footer') %>
- Create a file called update.ejs that displays a form to edit a specific article. It displays the existing content in the title and content fields.
views/update.ejs
<%- include('header') %> <h1>Update Article</h1> <hr> <form method="POST" action=""> <div class="mb-3"> <label for="title" class="form-label">Title</label><br> <input type="text" id="title" name="title" class="form-control" value="<%= article.title %>" maxlength="200" required> </div> <div class="mb-3"> <label for="content" class="form-label">Content</label><br> <textarea id="content" name="content" rows="10" class="form-control" required><%= article.content %></textarea> </div> <div class="mb-3"> <button type="submit" class="btn btn-secondary">Submit</button> <a href="/<%= article.id %>" class="btn btn-outline-secondary float-end">Cancel</a> </div> </form> <%- include('footer') %>
Change the routes/index.js file. For each route assign a handler function.
- The root route "/" calls the list handler function that displays the article title and createdAt field values.
- The "/create" route calls the createForm handler function which sends the create form as the response.
- The "/create" post route calls the create handler function which posts the form data to the database and redirects to the "/:id" route which will display the detail page.
- The "/:id" route calls the detail handler function which sends the article detail page as the response. :id is a variable for the article id number.
- The "/:id/update" route calls the updateForm handler function which gets the article data from the database and sends the update form as the response.
- The "/:id/update" post route calls the update handler function which posts the form data to the database and redirects to the "/:id" route which will display the detail page.
- The "/:id/delete" route calls the destroy handler function which deletes the article from the database and redirects to the "/" root route which will display the list of articles.
routes/index.js
const express = require('express'); const router = express.Router(); const { Article } = require('../models'); const createError = require('http-errors'); // Routes router.get('/', list); router.get('/create', createForm); router.post('/create', create); router.get('/:id', detail); router.get('/:id/update', updateForm); router.post('/:id/update', update); router.get('/:id/delete', destroy); // Controller Functions // GET /articles async function list(req, res, next) { const articles = await Article.findAll({ attributes: ['id', 'title', 'createdAt'], order: [['createdAt', 'DESC']] }) res.render('index', { title: 'Blog App', articles }); } // GET /articles/:id async function detail(req, res, next) { const article = await Article.findByPk(req.params.id); if (!article) { return next(createError(404)); } res.render('detail', { title: 'Article', article }); } // GET /articles/create function createForm(req, res, next) { res.render('create', { title: 'Create Article' }); } // POST /articles/create async function create(req, res, next) { const article = await Article.create(req.body, {fields: ['title', 'content'] }); res.redirect(`/${article.id}`); } // GET /articles/:id/update async function updateForm(req, res, next) { const article = await Article.findByPk(req.params.id); res.render('update', { title: 'Update Article', article }); } // POST /articles/:id/update async function update(req, res, next) { const id = req.params.id; await Article.update(req.body, {where: {id: id}, fields: ['title', 'content'] }); res.redirect(`/${id}`); } // GET /articles/:id/delete async function destroy(req, res, next) { await Article.destroy({where: {id: req.params.id}}); res.redirect('/'); } module.exports = router;
- We are done with our Blog app. Let's try it out.
- Stop the server from the Terminal by pressing Control+C
- Then restart it: npm start
- View the changes in the web browser: http://localhost:3000.
- To fill in the form content you can use lorem ipsum filler text. There is a website that you can use to generate it: loremipsum.io