CSE 340: Web Backend Development

W04 Learning Activity: Handling Form Submissions

Overview

In this activity, you will learn how to handle form submissions in a web application using Express.js. You will explore techniques for processing form data, validating user input, and providing feedback to users.

Preparation Material

Static websites can only display information, but dynamic web applications need to collect and process user input. HTML forms are one of the most common ways websites to get the information from users to pass to the server for processing.

POST Requests

When a user submits a form, the data needs to be processed on your server. Up until now, you have worked with GET routes that respond to page requests. Form submissions typically use POST routes, which are designed to handle data submission and modification operations.

POST requests work differently than GET requests. Instead of passing data in the URL (like query parameters), POST requests send data in the request body. This allows for larger amounts of data and keeps sensitive information like passwords out of URLs and browser history.

Understanding Request Bodies

The request body is where POST data is transmitted. When a user submits a form, their browser packages the form data and sends it as the body of the HTTP request. Your Express application needs special middleware to parse this body data and make it available in your route handlers.

// Express middleware to parse form data from request bodies
app.use(express.urlencoded({ extended: true }));
app.use(express.json()); // For handling JSON data from API requests

After adding this middleware, form data becomes available in your route handlers through the req.body object. Each form field appears as a property on this object, with the property name matching the form field's name attribute.

Working with POST data

Once you have the proper middleware in place, you can access the form data in your route handlers through the req.body object. This example shows a basic POST route that receives user registration data:

// Handle user registration form submission
app.post('/register', (req, res) => {
    // Extract data from request body
    const { email, password, age } = req.body;
    
    // Log the received data for debugging
    console.log('Registration data received:', {
        email: email,
        password: password,
        age: age
    });
    
    // For now, just send a simple response
    res.send(`Registration received for ${email}`);
});

Notice how this route uses app.post() instead of app.get(), and accesses form data through req.body rather than req.params or req.query. The form that submits to this route must specify the correct method and action:

<form method="POST" action="/register">
    <input type="email" name="email" required>
    <input type="password" name="password" required>
    <input type="number" name="age" required>
    <button type="submit">Register</button>
</form>

Activity Instructions

In this activity you will add the ability for a user to create a new organization and save it to the the database.

New Features

You will add the following features to your application:

  1. A new page for creating new organizations /new-organization .
  2. New route, model, and controller logic to support creating new organizations.

In a future activity, you will add client-side and server-side validation for this form.

Include a placeholder logo

Download the file placeholder-logo.png and place it in your public/images directory. This will be used for all new projects that are created.

Why a placeholder image?

Allowing users to upload files is actually a fairly complex task, especially with the free hosting solution you are using for this course. To avoid this complexity in this activity, you will use a placeholder logo for all new organizations.

Create the view for the new organization form

Create a new view file at src/views/new-organization.ejs with the following content:


<%- include('partials/header') %>
<main>
    <h1><%= title %></h1>

 <form action="/new-organization" method="POST">
    <div class="form-group">
      <label for="name">Organization Name</label>
      <input
        type="text"
        id="name"
        name="name"
      />
    </div>

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

    <div class="form-group">
      <label for="contactEmail">Contact Email</label>
      <input
        type="text"
        id="contactEmail"
        name="contactEmail"
      />
    </div>

    <button type="submit">Create Organization</button>
  </form>

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

A few things to notice about this code:

Validation Attributes

In a future activity, you will add client-side and server-side validation for this form. This form purposely does not include validation attributes yet so that you can test them later.

Create the controller and route to render this view

You need to create a new controller function and route handler so that when someone visits the URL /new-organization, they will see the new organization form.

  1. Open src/controllers/organizations.js to add a new controller for the organization form.
  2. Add the following function to render the new organization form:
    const showNewOrganizationForm = async (req, res) => {
        const title = 'Add New Organization';
    
        res.render('new-organization', { title });
    }
    

    Notice that the res.render call specifies the view name new-organization which corresponds to the src/views/new-organization.ejs file.

  3. Add the showNewOrganizationForm function to your exports at the bottom of src/controllers/organizations.js.
  4. Open src/controllers/routes.js.
  5. Import your new showNewOrganizationForm controller function at the top with your other imports.
  6. Add a new GET route for /new-organization that uses the showNewOrganizationForm controller with the following code:
    // Route for new organization page
    router.get('/new-organization', showNewOrganizationForm);
    

Add a link to your new form

To make it easy for users to access the new organization form, you will add a link to it from your main organizations page.

  1. Open src/views/organizations.ejs
  2. At the bottom of the page, add a link to the new organization form:
    <p><a href="/new-organization">Add a new organization</a></p>

Test the form and verify that it loads

Run your application, in your browser visit the organizations page, and click on your new link to visit /new-organization to see the new organization form.

At this point, you will not be able to submit the form yet, but you should be able to see it render in your browser.

Create the model for adding a new organization

Now that the form is displaying, the next task is to prepare the functions to handle the form submission and create a new organization in the database.

In this step, you will create a model function that accepts the organization data and inserts it into the database.

  1. Create a new file at src/models/organizations.js.
  2. Add the following code to define a function that inserts a new organization into the database:
    /**
     * Creates a new organization in the database.
     * @param {string} name - The name of the organization.
     * @param {string} description - A description of the organization.
     * @param {string} contactEmail - The contact email for the organization.
     * @param {string} logoFilename - The filename of the organization's logo.
     * @returns {string} The id of the newly created organization record.
     */
    const createOrganization = async (name, description, contactEmail, logoFilename) => {
        const query = `
          INSERT INTO organization (name, description, contact_email, logo_filename)
          VALUES ($1, $2, $3, $4)
          RETURNING organization_id
        `;
    
        const query_params = [name, description, contactEmail, logoFilename];
        const result = await db.query(query, query_params);
    
        if (result.rows.length === 0) {
            throw new Error('Failed to create organization');
        }
    
        if (process.env.ENABLE_SQL_LOGGING === 'true') {
            console.log('Created new organization with ID:', result.rows[0].organization_id);
        }
    
        return result.rows[0].organization_id;
    };
    
  3. Add the createOrganization function to your exports at the bottom of src/models/organizations.js.

Configure Express to Handle POST Requests

When you submit your form, you will use the POST method and send the data in the request body. This step will walk you through the changes you need for Express to handle this data.

  1. Open your server.js file.
  2. Directly before your app.use line of code that defines your static pages, add the following middleware to parse URL-encoded data and JSON data from POST requests:
    // Allow Express to receive and process common POST data
    app.use(express.urlencoded({ extended: true }));
    app.use(express.json());
    

Middleware in action

These two middleware functions are a perfect example of the power of middleware. They parse the request body and set up variables that are easy to use, and make them available for any code that comes later in the pipeline.

Set up the controller and route to handle the form POST

Now you will create the controller function and route handler to process the form submission when the user submits the new organization form.

Recall from above that when a user submits the form, it will send a POST request to the URL /new-organization. Currently you have the following GET route handler defined:

// Route for new organization page
router.get('/new-organization', showNewOrganizationForm);

Notice that this is defined for router.get, where get shows that this will handle the get requests. So if someone enters the URL /new-organization in their browser, this route will handle the request and render the new organization form. When the form is submitted, it will send a POST request to the same URL, but your code will handle it differently. Instead of sending back the form, when a POST request is received, you will process the form data and take the appropriate action. There are two different actions for the same URL depending on whether it is a GET or a POST request.

CSE 341: Web Services

This idea of handling the same URL differently based on the HTTP method is a common pattern in web development. It allows you to have a single URL for a resource, but perform different actions depending on whether the request is a GET, POST, PUT, DELETE, etc.

This pattern is especially common when creating Web Services, and it is the focus of CSE 341: Web Services that you will take after this course.

  1. Open src/controllers/organizations.js to add a new controller for processing the POST form submission.
  2. At the top of the file, add an import for your new function createOrganization to the list of imports from the organization model.
  3. Add the following function to handle the form submission:
    const processNewOrganizationForm = async (req, res) => {
        const { name, description, contactEmail } = req.body;
        const logoFilename = 'placeholder-logo.png'; // Use the placeholder logo for all new organizations
    
        const organizationId = await createOrganization(name, description, contactEmail, logoFilename);
        res.redirect(`/organization/${organizationId}`);
    };
    

    Notice that once the organization is created, the user is redirected to the page for that specific organization using the organizationId returned from the createOrganization function.

  4. Add this new function processNewOrganizationForm to the list of exports at the bottom of the file.
  5. Open the src/controllers/routes.js file to add a new route for handling the POST request from the new organization form.
  6. At the top of the file, add an import for your new processNewOrganizationForm controller function.
  7. Add the following route handler to use your processNewOrganizationForm function for POST requests to /new-organization:
    // Route to handle new organization form submission
    router.post('/new-organization', processNewOrganizationForm);
    

    Notice that this route is defined for router.post, which means it will handle POST requests to the /new-organization URL. This is different from the GET route we defined earlier, which handles GET requests to the same URL.

Error Handling

Why are there no try/catch blocks in this code to log information to the console?

You do not need to include a try/catch block in each of these functions because you have already created a global error handler that will catch errors and log them in a consistent way. If you need to take certain action, you could catch an error and do that action, or you could add additional information, but then, you would likely want to throw the error again to be caught by your global error handler.

Test the form submission

Run your application and visit /new-organization in your browser to see the new organization form.

Fill out the form with valid data and submit it. After submitting the form, you should be redirected to the page for the newly created organization.

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: