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:
- A new page for creating new organizations
/new-organization. - 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:
- The action of the form is set to
/new-organization, which corresponds to a new route that will handle the form submission. - The method is set to
POST, indicating that the form data will be sent in the body of the request. - Later, you will need to create a corresponding route in your Express application to handle this POST request and process the form data.
- The form includes fields for all each organization attribute: name, description, and contact email. (You will manually add a placeholder logo filename in the form processing.)
- Each input field has a
nameattribute that will be used as the key for the corresponding value in the request body. This is important for extracting the data on the server side.
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.
- Open
src/controllers/organizations.jsto add a new controller for the organization form. - 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.rendercall specifies the view namenew-organizationwhich corresponds to thesrc/views/new-organization.ejsfile. - Add the
showNewOrganizationFormfunction to your exports at the bottom ofsrc/controllers/organizations.js. - Open
src/controllers/routes.js. - Import your new
showNewOrganizationFormcontroller function at the top with your other imports. - Add a new GET route for
/new-organizationthat uses theshowNewOrganizationFormcontroller 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.
- Open
src/views/organizations.ejs - 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.
- Create a new file at
src/models/organizations.js. - 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; }; - Add the
createOrganizationfunction to your exports at the bottom ofsrc/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.
- Open your
server.jsfile. - Directly before your
app.useline 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.
- Open
src/controllers/organizations.jsto add a new controller for processing the POST form submission. - At the top of the file, add an import for your new function
createOrganizationto the list of imports from the organization model. - 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
organizationIdreturned from thecreateOrganizationfunction. - Add this new function
processNewOrganizationFormto the list of exports at the bottom of the file. - Open the
src/controllers/routes.jsfile to add a new route for handling the POST request from the new organization form. - At the top of the file, add an import for your new
processNewOrganizationFormcontroller function. - Add the following route handler to use your
processNewOrganizationFormfunction 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-organizationURL. 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:
- Return to: Week Overview | Course Home