Lesson 12. Capstone: Enhancing the Confetti Cuisine site with Express.js

After some consideration, I decided that it would be easier to rely on a web framework to assist me in building a web application for Confetti Cuisine. Building custom routes and application logic has become a tedious task, so I’m converting my application to use Express.js.

I still want the application to have home, courses, and sign-up pages. I need to convert the routes to use keywords and syntax found in Express.js. I need to make sure that I serve my static assets out of the public directory and have all necessary package.json configurations set up for launching the application locally. When I feel ready to make this transformation, I’ll start by initializing the project with npm init.

12.1. Initializing the application

To begin this site redesign, I’m going to create a new project directory called confetti_cuisine and enter that folder. Within the project folder in terminal, I’ll initialize the application package.json with npm init.

Remembering the configurations that I previously set, I’ll keep the default settings for the project name and use entry point main.js.

Now that my package.json is set up, I’m going to add a start script under "scripts", which will allow me to run the application by using npm start instead of node <filename>. I add "start": "node main.js" to my list of scripts.

Tip

Don’t forget to separate multiple script items with a comma.

The last step in the initialization process is adding the main Express.js web framework, EJS templating, a layout, and http-status-codes packages to this project. To do so, I run npm install express ejs express-ejs-layouts http-status-codes --save in the command line.

Note

The --save flag saves the express package as a dependency in this project’s package.json. This way, any future work on this project will ensure that Express.js is installed before anything is able to work.

My resulting package.json file looks like the following listing.

Listing 12.1. Project configurations in package.json
{
  "name": "confetti_cuisine",
  "version": "1.0.0",
  "description": "A site for booking classes for cooking.",
  "main": "main.js",
  "scripts": {
    "test": "echo "Error: no test specified" && exit 1",
    "start": "node main.js"
  },
  "author": "Jon Wexler",
  "license": "ISC",
  "dependencies": {                       1
    "ejs": "^2.6.1",
    "express": "^4.16.4",
    "express-ejs-layouts": "^2.5.0",
    "http-status-codes": "^1.3.0"
  }
}

  • 1 List dependencies installed for this project.

Before I add any new files, I’m going to set up my application’s directory structure. The final project structure will look like listing 12.2. I’ll add the following:

  • A views folder to hold my HTML pages
  • A controllers folder to hold any routing functions
  • A public folder with css, js, and images folders within to hold my client-side assets

Listing 12.2. Confetti Cuisine project file structure
.                                 1
|____main.js
|____public
| |____css
| | |____confetti_cuisine.css
| | |____bootstrap.css
| |____images
| | |____product.jpg
| | |____graph.png
| | |____cat.jpg
| | |____people.jpg
| |____js
| | |____confettiCuisine.js
|____package-lock.json
|____package.json
|____controllers
| |____homeController.js
| |____errorController.js
|____views
| |____index.ejs
| |____courses.ejs
| |____contact.ejs
| |____error.ejs
| |____thanks.ejs
| |____layout.ejs

  • 1 List of project directory from root

Great. Now I’m ready to add application logic.

12.2. Building the application

Now that the application is set up with Express.js installed, I’ll create my main.js application file. Although this file will resemble my http module version, writing it from scratch will eliminate a ton of headaches in converting line by line. My main.js will look like the code in listing 12.3.

The first line of main.js requires the contents of the Express.js package, assigning them to a constant called express. As with the app constant in the first version of this application, I’ll instantiate the express object, representing this project’s main application framework as another constant called app. The app constant will have the ability to set up a GET route, listening for requests made to the root URL (/) and responding with the Express.js send function called on the response. I can finally set up the server to listen on port 3000 and log a message to my console when it’s up and running.

Listing 12.3. Setting up the main application logic in main.js
const express = require("express"),                     1
  app = express();                                      2

app.set("port", process.env.PORT || 3000);

app.get("/", (req, res) => {                            3
  res.send("Welcome to Confetti Cuisine!");
});

app.listen(app.get("port"), () => {                     4
  console.log(
    `Server running at http://localhost:${app.get(
      "port"
    )}`
  );
});

  • 1 Require express.
  • 2 Instantiate the express application.
  • 3 Create a route for the home page.
  • 4 Set the application up to listen on port 3000.

With this logic in place, I can start the application by running npm start in my command line.

The json and urlencoded Express.js middleware functions will be used as middleware that interpret incoming request bodies for me. In main.js, I’ll add the code in the next listing.

Listing 12.4. Adding body parsing to the top of main.js
app.use(
  express.urlencoded({          1
    extended: false
  })
);
app.use(express.json());

  • 1 Tell the Express.js app to use body-parser for processing URL-encoded and JSON parameters.

Now my application is ready to analyze data within incoming requests. Next, I need to create routes to reach views in my application.

12.3. Adding more routes

Now that my application has a starting point, I’m going to create routes for the courses and sign-up pages. Additionally, I’ll add a POST route to handle submissions made from the form on the sign-up page.

First, I create a home controller in my controllers folder, which is where I’ll store the functions my routes will use. I need to require this controller by adding const homeController = require("./controllers/homeController") in main.js. I add the code in the next listing to my home controller, below my application’s first route. All three of these functions respond with an EJS page reflecting the requested route. I need to create the following views: courses.ejs, contact.ejs, and thanks.ejs.

Listing 12.5. Adding route actions to my home controller in homeController.js
exports.showCourses = (req, res) => {          1
  res.render("courses");
};
exports.showSignUp = (req, res) => {
  res.render("contact");
};
exports.postedSignUpForm = (req, res) => {
  res.render("thanks");
};

  • 1 Add callback functions for specific routes.

In my main.js, I add the following routes and modify my original home-page route to use my home controller too, as shown in listing 12.6. The first route handles GET requests made to view course listings. For the most part, this route behaves similarly to the home page. The route for the contact page also listens for GET requests, as most people will be expecting a sign-up form on this page when the /contact URL is requested. The last route is for POST requests targeting the /contact URL. The GET route is used internally to view who submitted a request to get in contact. The POST route is used by the sign-up form on the contact page.

Listing 12.6. Adding routes for each page and request type in main.js
app.get("/courses", homeController.showCourses);         1
app.get("/contact", homeController.showSignUp);
app.post("/contact", homeController.postedSignUpForm);

  • 1 Add routes for the courses page, contact page, and contact form submission.

Now that all the routes are defined, I’m still missing the bulk of the content. It’s time to add and render some views.

12.4. Routing to views

With Express.js, my views are going to be cleaner and easier to render. I need to create the views listed in table 12.1.

Table 12.1. Confetti Cuisine views

Filename

Purpose

layout.ejs Serves as the application’s main styling and navigation foundation
index.ejs Produces the home page’s content
courses.ejs Displays course content
contact.ejs Displays the contact form
thanks.ejs Displays a thank-you message upon form submission
error.ejs Displays an error message when a page isn’t found

I’ll start by generating my application’s layout view, which will handle what the navigation and general site structure looks like from page to page.

For the layout to work, I need to include it in my main application file, right below my initialization of the Express.js module, as shown in listing 12.7. First, I require the express-ejs-layouts module to allow me to use the layout.ejs file. Then, I set the application server to use the ejs rendering template. Last, I set the application server to use the recently required layouts module. This way, when a new view is rendered, it goes through the layout.ejs file.

Listing 12.7. Enable EJS layout rendering in main.js
const layouts = require("express-ejs-layouts");      1

app.set("view engine", "ejs");                       2
app.use(layouts);                                    3

  • 1 Require the express-ejs-layouts module.
  • 2 Set the application to use ejs.
  • 3 Set the application to use the layout module.

I’ll add this file, called layout.ejs, to my views folder. The key component of this file includes <%- body %>, which will be replaced by my target route’s rendered content.

Each of the following views will use this layout to provide visual consistency (and to avoid repetition of code between files). Within the views folder, I’m going to create index.ejs, courses.ejs, contact.ejs, thanks.ejs, and error.ejs files. Like the layout file, these views render as Embedded JavaScript, allowing me to dynamically feed content to the pages from the server file. After creating index.ejs, I change my home-page route (/) to render the index page in place of sending plain text.

The one view I need to focus on is contact.ejs, where I’ll have prospective students fill out a form, submitting a POST request to my application’s /sign-up route. That form will look like the HTML in the next listing. Notice that the form action is /contact and the form method is POST. When the form is submitted, it will make a POST request to the /contact route.

Listing 12.8. Contact form in contact.ejs
<form action="/contact" method="post">
  <label for="name">Name</label>
  <input type="text" name="name">
  <label for="email">Email</label>
  <input type="email" name="email">
  <input type="submit" value="Submit">
</form>                                  1

  • 1 Display example contact form.

I should be all set. If I named my routes to match and render their corresponding views, I should be able to launch my app and see those views rendered within the layout I built. The only thing missing is my app’s ability to serve images and other static files, which I cover next.

Note

The layout file is rendered on every page I visit. Try adding new HTML content above and below the <%- body %>marker. Notice that these elements are applied to every page.

12.5. Serving static views

In my first take of this application using http, serving static assets became a big mess. With every new asset I added to my project directory, I needed to create a new route and handle it appropriately. Luckily, Express.js handles this task nicely, requiring virtually no effort on my part to handle any and all static files I want my application to serve. To enable static assets, I’ll use Express.js’ static function by adding app.use(express.static ("public")) below the initialization of Express.js in my application file. This addition allows individual assets in the application to be served directly.

The last major step in converting the app to an Express.js app is using dynamic content in the views.

12.6. Passing content to the views

Confetti Cuisine often modifies its course listings, so the application is better off not showing those courses on a static web page. With Express.js, passing content from the server logic to the view is a piece of cake.

For this app, I need to display an array of course offerings as a JavaScript object. Then I can send the object to my rendered view. I add the code in listing 12.9 to homeController.js. By assigning the courses variable to an array of JavaScript objects, I can use this list and target specific keys in my view. The res.render method allows me to pass the courses object to the view and refer to it as offeredCourses on that page.

Note

Within the view, I can access this array by using the variable name offeredCourses. Within the home controller, that array goes by the name courses.

Listing 12.9. Set up content on server and pass into rendered view in homeController.js
var courses = [
  {
    title: "Event Driven Cakes",
    cost: 50
  },
  {
    title: "Asynchronous Artichoke",
    cost: 25
  },
  {
    title: "Object Oriented Orange Juice",
    cost: 10
  }
];                                       1

exports.showCourses = (req, res) => {
  res.render("courses", {
    offeredCourses: courses              2
  });
};

  • 1 Define an array of courses.
  • 2 Pass the courses array to the view.

To benefit from this feature, I need to add some EJS and HTML to loop through the offeredCourses list in courses.ejs and print the relevant content, as shown in listing 12.10.

Listing 12.10. Loop through and display dynamic content in view in courses.ejs
<h1>Our Courses</h1>
<% offeredCourses.forEach(course => { %>        1
  <h5> <%= course.title %> </h5>
  <span>$ <%= course.cost %> </span>
<% }); %>

  • 1 Loop through the array of courses in the view.

Now the application is complete. My courses page looks like figure 12.1. Instead of modifying my courses.ejs view every time a modification is made to the course offerings, I can change the array in my main application file. Running the application is the easy part now.

Figure 12.1. View of courses page

I should anticipate that things won’t go exactly as planned, so it’s always smart to prepare for certain errors and handle them accordingly. Soon, when this array of courses is replaced by contents from a persistent database, I won’t need to make any code changes to update the course listing.

12.7. Handling the errors

An application handling most expected outcomes ensures a fairly consistent and good experience for its users. I know that my application may be missing some foolproof logic, though, and I prefer to send my own custom error messages to my client’s audience when those errors occur.

For error handling, I’ll create an error controller, errorController.js, to store my functions, as shown in listing 12.11. The first function handles all requests not previously handled, which fits the category of URLs visited without an active route and results in a 404 error, serving error.ejs. The last function handles any internal server errors that occur. Instead of necessarily crashing and scaring the audience away, I prefer a friendlier message.

Listing 12.11. Adding error handling routes in errorController.ejs
const httpStatus = require("http-status-codes");

exports.pageNotFoundError = (req, res) => {                   1
  let errorCode = httpStatus.NOT_FOUND;
  res.status(errorCode);
  res.render("error");
};

exports.internalServerError = (error, req, res, next) => {    2
  let errorCode = httpStatus.INTERNAL_SERVER_ERROR;
  console.log(`ERROR occurred: ${error.stack}`)
  res.status(errorCode);
  res.send(`${errorCode} | Sorry, our application is taking a nap!`);
};

  • 1 Handle all requests not previously handled.
  • 2 Handle any internal server errors.

Then I add routes to correspond to these functions. I’ll add the routes in listing 12.12 to trigger the functions in my error controller if no proceeding routes respond to a request.

Note

The order of routes matters. These routes must go below any preexisting routes, as they act as a catch-all and override any routes below them.

Listing 12.12. Adding error handling routes in main.js
app.use(errorController.pageNotFoundError);            1
app.use(errorController.internalServerError);

  • 1 Add error handlers as middleware functions.

I need to require this controller by adding const errorController = require("./ controllers/errorController") to the top of my main.js file. Now my application is ready to handle errors and launch. When a URL is visited without a corresponding route, users see my cat, Hendrix, relaxing on the error page (figure 12.2).

Figure 12.2. View of error page

Summary

Through this project, I redefined the Node.js project file structure to fit a web framework. I used npm to install three external packages. Then I rebuilt the main application file, using Express.js syntax. To create a path for specific URLs, I set up new routes, using Express.js keywords. For a consistent user interface, I used layouts with EJS. Using Express.js’ static library, I set up static assets to be served to the client through my public folder. Last, I added content to the main project application file and set up that content to be served dynamically to one of my views.

With consistent practice of these techniques and proper error handling, I can use Express.js to build future applications in a few steps. With new features such as layouts and dynamic content, I can try to send content to other views in my application or try modifying the layout as it’s used throughout the app.

In unit 3, I discuss how to organize application code around persistent data with Express.js.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset