CSE 340: Web Backend Development

W04 Learning Activity: Repeating the Process: Inserting New Service Projects

Overview

In previous activities you learned how to handle form submissions to insert new organizations into your application. In this activity, you will repeat that process to add the ability to insert new service projects.

In your team activity you will handle updating service projects.

Activity Instructions

One main difference between inserting new projects as compared to the organizations that you have done previously, is that projects are associated with organizations, so you need to handle that relationship when inserting new projects. To handle this, you will create a dropdown menu in the new project form that allows users to select the associated organization. This requires the controller for the form to get the list of organizations and pass it to the new project view.

There are many new files and functions to create that go end-to-end from the client to the database. As mentioned in a previous activity, you can approach these new function from a top-down or a bottom-up approach.

The previous activity followed a top-down approach starting with the views. This activity will follow a bottom-up approach, where you first create the model functions, then the controller and route functions, and finally create and update the views.

Create the model function to insert new service projects.

  1. In your src/models/projects.js file, create a new function named createProject. This function should accept the following parameters: title, description, location, date, and organizationId.
  2. The function should execute an SQL INSERT statement to add a new record to the projects table in the database using the provided parameters.
  3. The function should return the result the ID of the newly inserted project.
  4. Add the new function to the module exports.
Sample Code (click to expand)
const createProject = async (title, description, location, date, organizationId) => {
    const query = `
      INSERT INTO project (title, description, location, date, organization_id)
      VALUES ($1, $2, $3, $4, $5)
      RETURNING project_id;
    `;

    const query_params = [title, description, location, date, organizationId];
    const result = await db.query(query, query_params);

    if (result.rows.length === 0) {
        throw new Error('Failed to create project');
    }

    if (process.env.ENABLE_SQL_LOGGING === 'true') {
        console.log('Created new project with ID:', result.rows[0].project_id);
    }

    return result.rows[0].project_id;
}

Create the controller functions to serve the new project form and to process the form submission.

  1. Open your src/controllers/projects.js file.
  2. Add an import for the createProject model function you just created.
  3. Also, add an import for the getAllOrganizations function from the ../models/organizations.js file. This is needed to populate the dropdown list of organizations on the new project form.
  4. In your src/controllers/projects.js file, create a new function named showNewProjectForm. This function should do the following:
    1. Call the getAllOrganizations model function to get a list of all organizations from the database.
    2. Render the new-project view, passing in the page title and the list of organizations to populate the dropdown menu.
  5. Create another function named processNewProjectForm. This function should do the following:
    1. Extract the project data (organizationId, title, description, location, date) from the form submission using req.body.
    2. Call the createProject model function you created in the previous step, passing all of the necessary parameters.
    3. After the insertion is complete, set a success flash message.
    4. Redirect the user back to the main service project list page.
  6. Add both new functions to the module exports.
Sample Code (click to expand)
const showNewProjectForm = async (req, res) => {
    const organizations = await getAllOrganizations();
    const title = 'Add New Service Project';

    res.render('new-project', { title, organizations });
}

const processNewProjectForm = async (req, res) => {
    // Extract form data from req.body
    const { title, description, location, date, organizationId } = req.body;

    try {
        // Create the new project in the database
        const newProjectId = await createProject(title, description, location, date, organizationId);

        req.flash('success', 'New service project created successfully!');
        res.redirect(`/project/${newProjectId}`);
    } catch (error) {
        console.error('Error creating new project:', error);
        req.flash('error', 'There was an error creating the service project.');
        res.redirect('/new-project');
    }
}

Create the routes to serve the new project form and to handle the form submission.

  1. Open your src/controllers/routes.js file.
  2. Add imports for the two controller functions you just created.
  3. Create a GET route for /new-project that calls the showNewProjectForm controller function.
  4. Create a POST route for /new-project that calls the processNewProjectForm controller function.
Sample Code (click to expand)
// Route for new project page
router.get('/new-project', showNewProjectForm);

// Route to handle new project form submission
router.post('/new-project', processNewProjectForm);

Create the view for the new project form.

  1. Create a new file named new-project.ejs in the src/views/ folder.
  2. Design a form that collects the following information about the new service project: title, description, location, and date.
  3. This form also needs a way to select the organizationId associated with the project. For this, create a dropdown menu populated with organizations.
    • Use EJS syntax to loop through the organizations array passed to the view and create an <option> element for each organization.
    • The value of each option should be the organization's ID, and the display text should be the organization's name.
  4. The form should submit a POST request to the /new-project route.

Remember that AI can be very helpful in creating these kinds of forms. Just be sure to double check that all of your form field names match the expected names in your controller and database.

Sample Code (click to expand)
<%- include('partials/header') %>
<main>
    <h1><%= title %></h1>

 <form action="/new-project" method="POST">
    <div class="form-group">
      <label for="title">Project Title</label>
      <input
        type="text"
        id="title"
        name="title"
        maxlength="150"
        required
      />
    </div>

    <div class="form-group">
      <label for="description">Description</label>
      <textarea
        id="description"
        name="description"
        maxlength="500"
        required
      ></textarea>
    </div>

    <div class="form-group">
      <label for="location">Location</label>
      <input
        type="text"
        id="location"
        name="location"
        maxlength="255"
        required
      />
    </div>

    <div class="form-group">
      <label for="date">Date</label>
      <input
        type="date"
        id="date"
        name="date"
        required
      />
    </div>

    <div class="form-group">
      <label for="organizationId">Organization</label>
      <select
        id="organizationId"
        name="organizationId"
        required
      >
        <option value="">Select an organization</option>
        <% organizations.forEach(org => { %>
          <option value="<%= org.organization_id %>"><%= org.name %></option>
        <% }); %>
      </select>
    </div>

    <button type="submit">Create Project</button>

</main>
<%- include('partials/footer') %>

Update the main service project list view to include a link to the new project form.

  1. Open your main service project list view file src/views/projects.ejs.
  2. Add a link that navigates to the /new-project route, allowing users to access the new project form.
Sample Code (click to expand)
<p><a href="/new-project">Add a New Service Project</a></p>

Test the new functionality

  1. Start your application and navigate to the main service project list page.
  2. Click the link to add a new service project. Verify that the new project form loads and that the organization dropdown is populated with organizations from the database.
  3. Fill out the form with valid data and submit it. Verify that the new project is added to the database and that you are redirected back to the main service project list page with a success flash message.
  4. Verify that the new project is displayed in the project list on the main service project list page.

Add Validation

  1. Ensure that you have proper client-side validation for all form fields to improve user experience and reduce invalid submissions:
    • Make all fields required.
    • Set appropriate maximum lengths for text fields.
    • Use the correct input types (e.g., date picker for date fields).
  2. Test your client-side validation.
  3. Implement server-side validation in the processNewProjectForm controller function to ensure data integrity:
    1. Open your src/controllers/projects.js projects controller file.
    2. Import the body and validationResult functions from the express-validator package.
    3. At the top of the controllers file, create a projectValidation array to define the validation rules for projects as follows: (remember that AI can be very helpful in generating validation code, but be sure to review and test it thoroughly)
      • title: trim, ensure not empty, length between 3 and 200.
      • description: trim, ensure not empty, length less than 1000.
      • location: trim, ensure not empty, length less than 200.
      • date: ensure not empty, valid date format.
      • organizationId: ensure not empty, valid integer.
    4. Sample Code (click to expand)
      const projectValidation = [
          body('title')
              .trim()
              .notEmpty().withMessage('Title is required')
              .isLength({ min: 3, max: 200 }).withMessage('Title must be between 3 and 200 characters'),
          body('description')
              .trim()
              .notEmpty().withMessage('Description is required')
              .isLength({ max: 1000 }).withMessage('Description must be less than 1000 characters'),
          body('location')
              .trim()
              .notEmpty().withMessage('Location is required')
              .isLength({ max: 200 }).withMessage('Location must be less than 200 characters'),
          body('date')
              .notEmpty().withMessage('Date is required')
              .isISO8601().withMessage('Date must be a valid date format'),
          body('organizationId')
              .notEmpty().withMessage('Organization is required')
              .isInt().withMessage('Organization must be a valid integer')
      ];
      
    5. In the processNewProjectForm function, check for validation errors using validationResult(req). If there are errors, set a flash message and redirect the user to the new-project view.
    6. Sample Code (click to expand)
      
          // Check for validation errors
          const errors = validationResult(req);
          if (!errors.isEmpty()) {
              // Loop through validation errors and flash them
              errors.array().forEach((error) => {
                  req.flash('error', error.msg);
              });
      
              // Redirect back to the new project form
              return res.redirect('/new-project');
          }
      
    7. Export the projectValidation array from the projects controller file so it can be used in your routes.
    8. Open your src/controllers/routes.js route handler file.
    9. Import the projectValidation array from the projects controller file.
    10. Update the POST route for /new-project to include the projectValidation array as middleware before the processNewProjectForm controller function.
  4. Test your server-side validation by submitting the new project form with invalid data and verifying that the appropriate error messages are displayed. Because your client-side validation will catch most of the errors, try submitting a project with a less than 3 letters, this should be caught only by the server-side validation logic.

Next Step

Complete the other Week 04 Learning Activities

After you have completed all the learning activities for this lesson, return to Canvas to submit a quiz.

Other Links: