Lesson 19. Creating and reading your models

In lesson 18, you constructed your user model and built an index page to display users on the same page. In this lesson, you add more functionality to your application by focusing on the create and read functions of CRUD. You start by creating an EJS form that handles a user’s attributes as inputs. Then you create the routes and actions to handle that form data. Last, you build a show page to act as the user’s profile page.

This lesson covers

  • Constructing a model creation form
  • Saving users to your database from the browser
  • Displaying associated models in a view
Consider this

With a new way to create courses for your recipe application, you’re finding it tedious to add individual documents to your database on REPL. You decided to create dedicated routes to create new model instances, edit them, and display their data. These routes are the foundations of CRUD methods that allow interaction with your data to flow through your application views.

19.1. Building the new user form

To create a new user instance in your database, you need some way of retrieving that user’s data. So far, you’ve been entering that data directly in REPL. Because you’re moving all your data interactions to the browser, you need a form through which new users can create their accounts. In CRUD terms, that form lives in a view called new.ejs.

To start, build that form by adding the code in listing 19.1 to new.js in the views/users folder. The resulting form looks like figure 19.1. This form makes a POST request to the /users/create route upon submission. You need to make sure to create that route before you try to submit anything; otherwise, your application will crash.

Figure 19.1. Example of user-creation form in your browser

The form is embellished with bootstrap, but the major takeaways are that each user attribute is represented as a form input and that the attribute’s name is set to that input’s name property—in the case of the first name, name="first". You’ll use these name attributes later to identify values in the controller. Notice that the password, email, and zipCode fields have some unique properties. These HTML validations are some ways that you can prevent invalid or insecure information from entering your application from the web page.

Listing 19.1. Building a user creation form in new.ejs
<div class ="data-form">
  <form action="/users/create" method="POST">                  1
    <h2>Create a new user:</h2>
    <label for="inputFirstName">First Name</label>             2
    <input type ="text" name="first" id="inputFirstName"
 placeholder ="First" autofocus>
    <label for="inputLastName">First Name</label>
    <input type ="text" name="last" id="inputLastName"
 placeholder ="Last">
    <label for="inputPassword">Password</label>                3
    <input type="password" name="password" id="inputPassword"
 placeholder="Password" required>
    <label for="inputEmail">Email address</label>
    <input type="email" name="email" id="inputEmail"
 placeholder="Email address" required>
    <label for="inputZipCode">Zip Code</label>
    <input type="text" name="zipCode" id="inputZipCode" pattern="d*"
 placeholder="Zip Code" required>
    <button type="submit">Sign in</button>
  </form>
</div>

  • 1 Build a form to create user accounts.
  • 2 Add user properties as inputs to the form.
  • 3 Apply HTML attributes to protect password and email fields.

Now that you have a new view, you need a route and controller actions to serve that view. You also add the create routes and actions to handle data from that view in the next section.

Quick check 19.1

Q1:

Which form input attribute must have a value for controller actions to identify form data?

QC 19.1 answer

1:

The name attribute must be filled in on the form to create a new record. Whatever value is mapped to the name attribute is what the controller uses to compare against the model schema.

 

19.2. Creating new users from a view

The form for new users collects data as it pertains to the user schema. Next, you need to create actions for this form. To get your form to render and process data, add the code for the user actions in listing 19.2 to usersController.js.

The new action takes incoming requests to create a new user and render the form in new.ejs. The create action receives incoming posted data from the form in new.ejs and passes the resulting created user to the next middleware function through the response object. The next middleware function, redirectView, determines which view to show based on the redirect path it receives as part of the response object. If a user is created successfully, redirect to the index page.

In the create action, assign a userParams variable to the collected incoming data. Then call User.create and pass those parameters, redirecting the user to the /users index page upon success and to the error page in case of a failure.

Note

For the subscribers controller, the new and create actions effectively replace the getSubscriptionPage and saveSubscriber actions you created earlier in the book. After swapping in these new actions, you need to change the action names in the main.js routes to match.

Listing 19.2. Adding a create action to usersController.js
new: (req, res) => {                          1
  res.render("users/new");
},

create: (req, res, next) => {                 2
  let userParams = {
    name: {
      first: req.body.first,
      last: req.body.last
    },
    email: req.body.email,
    password: req.body.password,
    zipCode: req.body.zipCode
  };                                          3

  User.create(userParams)
      .then(user => {
        res.locals.redirect = "/users";
        res.locals.user = user;
        next();
      })
      .catch(error => {
        console.log(`Error saving user: ${error.message}`);
        next(error);
      });
},

redirectView: (req, res, next) => {          4
 letredirectPath =res.locals.redirect;
 if (redirectPath)res.redirect(redirectPath);
 else next();
}

  • 1 Add the new action to render a form.
  • 2 Add the create action to save the user to the database.
  • 3 Create users with form parameters.
  • 4 Render the view in a separate redirectView action.

To see this code work, add the new and create routes to main.js, as shown in listing 19.3. The first route takes incoming GET requests to /users/new and renders new.ejs in the usersController. The second route accepts POST requests to /users/create and passes that incoming request body to the create action, followed by the view redirect with the redirectView action in usersController.js. These routes can go below your user’s index route.

Note

The addition of the new and create actions to the subscribers controller means that you can remove the getAllSubscribers and saveSubscriber actions in favor of the new CRUD actions. Likewise, the only action you need in the home controller is to serve the home page: index.ejs.

Now that you’re starting to accumulate the number of routes you’re using in main.js, you can use the Router module in Express.js by adding const router = express .Router() to your main.js file. This line creates a Router object that offers its own middleware and routing alongside the Express.js app object. Soon, you’ll use this router object to organize your routes. For now, modify your routes to use router instead of app. Then add app.use("/", router) to the top of your routes in main.js. This code tells your Express.js application to use the router object as a system for middleware and routing.

Listing 19.3. Adding new and create routes to main.js
router.get("/users/new", usersController.new);            1
router.post("/users/create", usersController.create,
  usersController.redirectView);                          2

  • 1 Handle requests to view the creation form.
  • 2 Handle requests to submit data from the creation form, and display a view.

Restart your application, fill out the form on http://localhost:3000/users/new, and submit the form. If you were successful, you should see your newly created user on the index page.

When you have users successfully saving to your database, add a finishing touch. You’ve already designed the User schema with an association to the Subscriber model. Ideally, whenever a new user is created, you’d like to check for an existing subscriber with the same email address and associate the two. You do so with a Mongoose pre("save") hook.

Mongoose offers some methods, called hooks, that allow you to perform an operation before a database change, such as save, is run. You can add this hook to user.js by adding the code in listing 19.4 after the schema is defined and before the model is registered. You need to require the Subscriber model into user.js for this hook to work. Use const Subscriber = require("./subscriber").

This hook runs right before a user is created or saved. It takes the next middleware function as a parameter so that when this step is complete, it can call the next middleware function. Because you can’t use arrow functions here, you need to define the user variable outside the promise chain.

Note

As of the writing of this book, arrow functions don’t work with Mongoose hooks.

You perform this function only if the user doesn’t already have an associated subscriber, which saves an unneeded database operation. Search for one subscriber account, using the user’s email address. If a subscriber is found with a matching email address, assign that subscriber to the user’s subscribedAccount attribute. Unless an error occurs, continue saving the user in the next middleware function. You also need to add a reference to the subscriber model in user.js by adding const Subscriber = require("./subscriber") to the top.

Listing 19.4. Adding a pre(‘save’) hook to user.js
userSchema.pre("save", function (next) {              1
  let user = this;                                    2
  if (user.subscribedAccount === undefined) {         3
    Subscriber.findOne({
      email: user.email
    })                                                4
      .then(subscriber => {
        user.subscribedAccount = subscriber;          5
        next();
      })
      .catch(error => {
        console.log(`Error in connecting subscriber:
 ${error.message}`);
         next(error);                                 6
      });
    } else {
      next();                                         7
    }
});

  • 1 Set up the pre(‘save’) hook.
  • 2 Use the function keyword in the callback.
  • 3 Add a quick conditional check for existing subscriber connections.
  • 4 Query for a single subscriber.
  • 5 Connect the user with a subscriber account.
  • 6 Pass any errors to the next middleware function.
  • 7 Call next function if user already has an association.

Give this new code a shot by creating a new subscriber in REPL (or through the subscriber’s new page, if you’ve created that already) and then creating a new user in your browser with the same email address. Going back to REPL, you can check whether that user’s subscribedAccount has a value reflecting the associated subscriber’s ObjectId. This value will come in handy in the next section, when you build the user’s show page.

Quick check 19.2

Q1:

Why does the Mongoose pre("save") hook take next as a parameter?

QC 19.2 answer

1:

The pre("save") hook is Mongoose middleware, and as with other middleware, when the function completes, it moves on to the next middleware function. next here indicates the next function in the middleware chain to be called.

 

19.3. Reading user data with show

Now that you can create users, you want a way to display a user’s information on dedicated pages (such as the user’s profile page). The only operation you need to perform on the database is to read data, finding a user by a specific ID and displaying its contents in the browser.

Start by creating a new view called show.ejs. Call the view and action show, making it clear that your intention is to show user data. In show.ejs, create a table similar to the one in index.ejs, except that you won’t need the loop. You want to show all the user’s attributes. Add the code in listing 19.5 to show.ejs within the views/users folder.

This form uses the user variable’s attributes to populate each table data box. At the end, check whether that user has a subscribedAccount. If not, nothing is displayed. If a subscriber is associated, display some text and link to the subscriber’s show page.

Listing 19.5. User show table in show.ejs
<h1>User Data for <%= user.fullName %></h1>

<table class="table">                          1
  <tr>
    <th>Name</th>
    <td><%= user.fullName %></td>
  </tr>
  <tr>
    <th>Email</th>
    <td><%= user.email %></td>
  </tr>
  <tr>
    <th>Zip Code</th>
    <td><%= user.zipCode %></td>
  </tr>
  <tr>
    <th>Password</th>
    <td><%= user.password %></td>
  </tr>
</table>

<% if (user.subscribedAccount) {%>            2
    <h4 class="center"> This user has a
    <a href="<%=`/subscribers/${user.subscribedAccount}` %>">
    subscribed account</a>.
    </h4>
<% } %>

  • 1 Add a table to display user data.
  • 2 Check for linked subscriber accounts.
Note

You will need to follow the same steps in creating CRUD functions and views for the subscriber simultaneously for this linked page to work. The anchor tag href path is /subscribers/${user.subscribedAccount}, which represents the subscriber’s show route.

To make it easier to get to a user’s show page, in index.ejs, wrap the user’s name in an anchor tag that links to users/ plus the user’s ID. The table data there should look like the next listing. You embed JavaScript in the anchor tag’s href as well as in the table data content.

Listing 19.6. Updated name data in index.ejs
<td>
  <a href="<%= `/users/${user._id}` %>">        1
    <%= user.fullName %>
  </a>
</td>

  • 1 Embed the user’s name and ID in HTML.

If you refresh the users index page, you’ll notice that the names have turned into links (figure 19.2). If you click one of those links now, though, you’ll get an error because there isn’t a route to handle the request yet.

Figure 19.2. Users’ index page with linked names in your browser

Next, add the show action to usersController.js, as shown in listing 19.7. First, collect the user’s ID from the URL parameters; you can get that information from req.params.id. This code works only if you define your route by using :id (see listing 19.7).

Use the findById query, and pass the user’s ID. Because each ID is unique, you should expect a single user in return. If a user is found, add it as a local variable on the response object, and call the next middleware. Soon, you’ll set up the next function to be showView, where you render the show page and pass the user object to display that user’s information. If an error occurs, log the message, and pass the error to the next middleware function.

Listing 19.7. Show action for a specific user in usersController.js
show: (req, res, next) => {
  let userId = req.params.id;                                       1
  User.findById(userId)                                             2
      .then(user => {
        res.locals.user = user;                                     3
          next();
      })
      .catch(error => {
        console.log(`Error fetching user by ID: ${error.message}`);
        next(error);                                                4
      });
    },

showView: (req, res) => {
  res.render("users/show");                                         5
}

  • 1 Collect the user ID from the request params.
  • 2 Find a user by its ID.
  • 3 Pass the user through the response object to the next middleware function.
  • 4 Log and pass errors to next function.
  • 5 Render show view.

Last, add the show route for users in main.js with the following code: router.get ("/users/:id", usersController.show, usersController.showView). This show route uses the /users path along with an :id parameter. This parameter will be filled with the user’s ID passing in from the index page when you click the user’s name in the table.

Note

You can group routes that are related to the same model in main.js for better organization.

Restart your application, and click a user’s name. You should be directed to that user’s show page, as shown in figure 19.3.

Figure 19.3. Users show page in your browser

You now have the ability to create data in your application and view it on a few web pages. In lesson 20, you explore ways of updating and deleting that data.

Quick check 19.3

Q1:

True or false: the URL parameter representing the user’s ID must be called :id.

QC 19.3 answer

1:

False. The :id parameter is essential for getting the ID of the user you’re trying to display, but this parameter can be referenced by any name you choose. If you decide to use :userId, make sure that you use that name consistently throughout your code.

 

Summary

In this lesson, you learned how to create index, new, and show pages for your models. You also created routes and actions to process user data and create new accounts. Finally, you customized the user show page to show user data and an indicator for linked subscriber accounts. You have two of the four CRUD building blocks in place. In lesson 20, you apply the update and delete functions to your three models.

Try this

Your user-account creation form is ready to create new accounts, but you’ve implemented certain validations on the user model that may allow a form to be submitted with no data saved. Try to test some of your validations to ensure that they’re working correctly, as follows:

  1. What happens when you enter an email address with capital letters?
  2. What happens when a required field is missing?

It’s good that you get redirected to the new page again, but you have improvements to make in the error messages shown on the screen.

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

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