Lesson 16. Capstone: Saving user subscriptions

I presented the Express.js application to Confetti Cuisine, and they love it. They tell me that they’re ready to start promoting their cooking courses and want people who visit the site to subscribe to the school’s newsletter. The subscribers to this newsletter are potential customers, so Confetti Cuisine wants me to save each subscriber’s name, email address, and ZIP code.

When I have a database in place, Confetti Cuisine is comfortable with moving to the next stages of building user accounts. To accomplish this task, I need to build an application with

  • A MongoDB database
  • The Mongoose package
  • A data schema with three fields
  • A form for subscribing on the site
  • A route to handle POST requests and save the subscriber data model

16.1. Setting up the database

Now that Confetti Cuisine is ready to save user data, I need to install MongoDB and Mongoose for this project. First, I install MongoDB with Homebrew on my Mac by running brew install mongodb. Then I start the MongoDB server locally by running mongod.

In a new terminal window, in my project directory, I install the mongoose package by entering npm i mongoose -S in a new terminal window within my project folder.

Next, I open the project’s main.js file and require mongoose along with my database configuration by using the code in listing 16.1. I require mongoose in this project to use the module’s methods for building a connection to my MongoDB database. Then I set up a connection to the confetti_cuisine database on my local computer. If this database doesn’t exist yet, it’s created when I first run this application.

Listing 16.1. Setting up Mongoose in the Node.js application in main.js
const mongoose = require("mongoose");                   1
mongoose.connect(
  "mongodb://localhost:27017/confetti_cuisine",         2
  {useNewUrlParser: true}
);

  • 1 Require mongoose.
  • 2 Set up the database connection.

Next, I need to build out how my data should look before it goes into the database.

16.2. Modeling data

Because Confetti Cuisine wants me to store three fields for new subscribers, I’ll create a Mongoose schema defining those fields. First, I create a new models folder and a new subscriber.js file with the schema from listing 16.2.

I need to require mongoose into this file so that I have access to that module’s tools and methods. This Mongoose schema defines what a subscriber model should contain. In this case, every subscriber object should have name and email fields that are both Strings and a zipCode field that’s a Number.

Listing 16.2. Defining a subscriber schema in subscriber.js
const mongoose = require("mongoose"),          1
  subscriberSchema = mongoose.Schema({
    name: String,
    email: String,
    zipCode: Number
  });                                          2

  • 1 Require mongoose.
  • 2 Define schema properties.

Now that the schema is defined, I need to define a model to use this schema. In other words, I’ve defined a set of rules, and now I need to create a model to use those rules.

The subscriber model also lives in the subscriber.js file, but unlike the schema, the model should be accessible by other modules in the application. For that reason, I add the model to the module’s exports object, as shown in listing 16.3.

I assign my subscriber model to the module.exports object. Other modules will need to require this file to access the Subscriber model.

Listing 16.3. Creating an exported subscriber model in subscriber.js
module.exports = mongoose.model("Subscriber",
 subscriberSchema);                              1

  • 1 Export the model.

Because I know that I’ll need to save subscribers who submit a form on the site, I’ll prepare a route and some logic to create and save new subscribers to the database. All my code is related to subscribers, so I’ll create a new subscribersController.js file within the controllers folder where my actions will exist to respond to a POST route. The code in that controller appears in listing 16.4.

First, I require the subscriber.js module. Because the module lives within another local folder, the require line looks for the models folder relative to the controllers folder. Node.js looks for the subscriber.js file within the models folder and assigns that module’s exports content to a local constant called Subscriber. Right now, this module is the only one in which I need to use the Subscriber model. Now I can create instances of the Subscriber module or make calls on that model within the main application file.

The first action uses find to run a query finding all subscribers in the database and returning a promise. I use then to continue the query chain and render a view upon successfully receiving data or catching an error with catch. The second action doesn’t require a promise; it renders a view. The third action creates an instance of Subscriber and saves to the database. This behavior automatically returns a promise through Mongoose and allows me to chain more functionality or catch errors. I add mongoose.Promise = global.Promise to main.js so that Mongoose will support my promise chains.

Listing 16.4. Controller actions for subscribers in subscribersController.js
const Subscriber = require("../models/subscriber");   1

exports.getAllSubscribers = (req, res) => {           2
  Subscriber.find({})
    .exec()
    .then((subscribers) => {
      res.render("subscribers", {
        subscribers: subscribers
      });
    })
    .catch((error) => {
      console.log(error.message);
      return [];
    })
    .then(() => {
      console.log("promise complete");
    });
};

exports.getSubscriptionPage = (req, res) => {         3
  res.render("contact");
};

exports.saveSubscriber = (req, res) => {              4
  let newSubscriber = new Subscriber( {
    name: req.body.name,
    email: req.body.email,
    zipCode: req.body.zipCode
  });

  newSubscriber.save()
    .then( () => {
      res.render("thanks");
    })
    .catch(error => {
      res.send(error);
    });
};

  • 1 Require the subscriber model.
  • 2 Retrieve all subscribers.
  • 3 Render the contact page.
  • 4 Save subscribers.

At this point, my application can launch normally with npm start, but I haven’t created the routes to connect to my new controller actions. First, I create a form to correspond with my getSubscriptionPage function.

16.3. Adding subscriber views and routes

The last piece of the puzzle is adding my views and a form that visitors can use to submit their information. The subscribers.ejs view contains a loop within the HTML tags to display all the subscribers in the database, as shown in listing 16.5. EJS allows basic JavaScript to run side by side with HTML content. Here, I’m looping through the subscribers I got from the getAllSubscribers action in the subscribers controller.

Listing 16.5. Looping through subscribers in subscribers.ejs
<% subscribers.forEach(s => {%>         1
  <p>< s.name %></p>
  <p><%= s.email%></p>
<% })%>

  • 1 Loop through the subscribers array.

The other view I need is for the subscription form, which replaces my form in contact .ejs. The form posts to the /subscribe route and looks like listing 16.6. This form contains input fields with names that match the fields in the Subscriber schema. When the form is submitted, data can easily be extracted by the model’s field names and saved within a new Subscriber instance.

Note

I’m deprecating my postedContactForm in the home controller. The old route and action can be removed.

Listing 16.6. For new subscribers in contact.ejs
<form action="/subscribe" method="post">                  1
  <label for="name">Name</label>
  <input type="text" name="name" placeholder="Name">
  <label for="email">Email</label>
  <input type="email" name="email" placeholder="Email">
  <label for="zipCode">Zip Code</label>
  <input type="text" pattern="[0-9]{5}" name="zipCode"
 placeholder="Zip Code">
  <input type="submit" name="submit">
</form>

  • 1 Add a subscription form.

To get these views to display, I need to add and modify some routes in main.js, as shown in listing 16.7. First, I require subscribersController.js to the top of the file. Then I add a new route to view all subscribers; this route uses the getAllSubscribers function in subscribersController.js (figure 16.1).

Figure 16.1. Listing subscriber data on the subscribers page

Instead of creating a new route for the subscription view, I modify the /contact route to use my getSubscriptionPage function. When users click the contact button in the site’s navigation, they see my subscription form. Last, I add a POST route to let my save-Subscriber function handle submissions from the subscription form.

Listing 16.7. Adding subscriber routes in main.js
const subscribersController = require(
   "./controllers/subscribersController");                          1

app.get("/subscribers", subscribersController.getAllSubscribers);   2
app.get("/contact", subscribersController.getSubscriptionPage);     3
app.post("/subscribe", subscribersController.saveSubscriber);       4

  • 1 Require the subscribers controller.
  • 2 Add a route to view all subscribers.
  • 3 Add a route to view the contact page.
  • 4 Add a route to handle posted form data.

The result is a form accessible from the contact page where new visitors can send me their information (figure 16.2).

Figure 16.2. Listing subscription form on the contact page

The pieces are in place, and the application is ready to launch. I’m going to show this application to Confetti Cuisine. I relaunch my application with npm start and demonstrate the subscription process to see whether the company is interested. This addition could be a good way to gauge interest among subscribers to the newsletter.

Summary

In this project, I took a largely static Express.js application and modified it to start saving and displaying dynamic data. With these changes and the help of a templating engine and middleware in Express.js, this application is taking shape.

I started by connecting the application with Mongoose and using the schema and modeling tools that come with Mongoose to structure application data. Next, I connected those models with new controllers and routes that handle specific requests to view and save subscriber data. Last, I incorporated a form where users can finally interact with and pass along their information to be processed and reviewed by the Confetti Cuisine team. With the help of promises, the code is clean and ready for errors that may occur.

In unit 4, you learn how to use Mongoose on another level by building a user model. Through this model, you learn about validation and security steps taken when creating, reading, updating, and deleting (CRUD) data.

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

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