W03 Learning Activity: Route and Query Parameters
Overview
In this activity, you will learn about route parameters and query parameters. Then, you will add new functionality to your application to load specific organizations by their unique identifiers using these parameters.
Preparation Material
When building web applications with Express, handling dynamic user inputs is essential. Two common methods for capturing data from URLs are query parameters and route parameters. While they serve different purposes, understanding both is crucial for creating flexible and user-friendly applications. These concepts build directly on the routing fundamentals you have learned, extending your ability to create dynamic, data-driven routes.
Query Parameters: The Classic Approach
Query parameters (also called GET parameters) are key-value pairs appended to a URL after a question mark (?). You have encountered this pattern countless times when using search engines, filtering products, or configuring page views. They represent optional information that modifies how a page behaves without changing which page you are accessing.
For example, in the URL https://store.com/products?view=grid&sort=price, view and sort are query parameters with values grid and price respectively. The base page is still /products, but the parameters tell the server how to present that page.
Defining and Accessing Query Parameters
In Express, query parameters do not need to be defined in advance. They are automatically parsed from the URL and made available in the req.query object, which makes them incredibly flexible for user-driven customization:
app.get('/products', (req, res) => {
const viewType = req.query.view; // 'grid', 'details', etc.
const sortBy = req.query.sort; // 'price', 'name', etc.
res.send(`Showing products in ${viewType} view, sorted by ${sortBy}`);
});
Multiple parameters are separated with ampersands (&), and Express automatically parses them into a JavaScript object. This means you can access any query parameter as a property on req.query, even if you did not anticipate it when writing your route.
When to Use Query Parameters
Query parameters excel in scenarios where you need flexibility and optional configuration:
-
Optional configuration: When parameters might be absent entirely (for example,
?sort=priceor no parameters at all) -
Filtering and sorting: When users can select from multiple criteria (
?category=shoes&color=red&size=9) -
Pagination: For page numbers and size limits (
?page=2&limit=20) -
Search queries: For user-entered search terms (
?search=blue+shoes) -
Preserving UI state: For saving view preferences that persist across page loads (
?view=grid&expanded=true)
Query Parameters and User Experience
Query parameters create shareable URLs that preserve user preferences. When someone bookmarks /products?category=electronics&sort=price, they return to exactly the same filtered, sorted view. This makes query parameters perfect for search results, filtered lists, and any situation where users might want to share or bookmark a specific configuration.
Route Parameters: Dynamic Path Components
Route parameters take a fundamentally different approach by capturing values directly from the URL path structure itself. This creates cleaner, more semantic URLs that feel like natural extensions of your site's hierarchy. Instead of asking "How should I show this page?", route parameters answer the question "Which specific thing are we looking at?"
Defining Route Parameters
In Express, route parameters are defined right in the route path by using a colon (:) followed by a parameter name. These parameters act as placeholders for dynamic values that will be extracted from the actual URL when a request comes in:
app.get('/user/:id', (req, res) => {
// Route handler logic here
});
In this example, the route /user/:id includes a parameter named id. This route will match URLs like /user/123, /user/sarah, or /user/admin, capturing whatever comes after /user/ as the id parameter.
Accessing Route Parameters
Once a route parameter is defined, its value can be accessed in the route handler through the req.params object. This object contains key-value pairs where the keys are the parameter names from your route definition, and the values are the actual segments from the requested URL:
app.get('/user/:id', (req, res) => {
const userId = req.params.id; // Extract the 'id' parameter
res.send(`You requested information for user ID: ${userId}`);
});
You can define multiple parameters in a single route, separated by slashes. For example, /blog/:year/:month/:slug would capture three different values, allowing URLs like /blog/2024/march/express-tutorial. These would be accessible as req.params.year, req.params.month, and req.params.slug respectively.
When to Use Route Parameters
Route parameters are ideal for representing the core identity of what you are displaying:
-
Representing resources: For entities with unique identifiers (
/products/123,/orders/ORDER-456) -
Hierarchical data: For parent-child relationships (
/categories/electronics/laptops) -
Clean, semantic URLs: For human-readable and SEO-friendly paths (
/blog/2023/express-tutorial) - Required values: When the parameter must be present for the route to make sense
Query vs. Route Parameters: Understanding the Difference
While both methods pass data through URLs, they serve fundamentally different purposes and have distinct characteristics that make each appropriate for different scenarios:
Structural Differences
-
URL Structure: Route parameters are embedded within the path structure (
/user/123), while query parameters come after a?at the end of the URL (/user?id=123) - Definition Requirements: Route parameters must be predefined in your route patterns, while query parameters can be added freely without changing your routes
- Presence Requirements: Route parameters are typically required (the route will not match without them), while query parameters are optional by default
Conceptual Differences
- Purpose: Route parameters identify what you are looking at, while query parameters modify how you see it.
- Semantics: Route parameters change the meaning of the page, while query parameters change the presentation of the page.
- URL Hierarchy: Route parameters create logical hierarchies in your URL structure, while query parameters add optional metadata.
Mental Model: Books and Reading Preferences
Think of route parameters like choosing a specific book from a library: /library/books/the-great-gatsby identifies exactly which book you want. Query parameters are like your reading preferences: /library/books/the-great-gatsby?font=large&theme=dark – you are still reading the same book, but you are customizing how you want to read it.
Using Both Together
The real power comes from combining both approaches strategically. This allows you to create URLs that are both semantically meaningful and functionally flexible:
// URL: /products/shoes?color=blue&size=9&sort=price
app.get('/products/:category', (req, res) => {
const category = req.params.category; // 'shoes'
const color = req.query.color; // 'blue'
const size = req.query.size; // '9'
const sort = req.query.sort; // 'price'
res.send(`Showing ${color} ${category} in size ${size}, sorted by ${sort}`);
});
This approach creates intuitive URLs where the main resource is identified by route parameters (what category of products), while filtering, sorting, and display options are controlled by query parameters (how to show those products). Users understand that /products/shoes and /products/electronics are fundamentally different pages, while ?color=blue and ?color=red are different views of the same page.
Parameter Validation and Safety
Since parameters come from user input, validation is crucial for both security and user experience. Different parameter types require different validation approaches.
Query Parameter Validation
Query parameters are optional by nature, so your validation should handle missing values gracefully while rejecting invalid ones:
app.get('/products', (req, res) => {
const viewType = req.query.view;
// Define valid options
const validViews = ['grid', 'details', 'list'];
// Validate if provided
if (viewType && !validViews.includes(viewType)) {
return res.status(400).send('Invalid view type. Must be grid, details, or list.');
}
// Use validated value or sensible default
const finalView = viewType || 'grid';
res.send(`Showing products in ${finalView} view`);
});
Route Parameter Validation
Route parameters always exist when a route matches, but they may not contain the expected data format or type:
app.get('/user/:id', (req, res) => {
const userId = req.params.id;
// Validate that the ID is numeric
if (!/^\d+$/.test(userId)) {
return res.status(400).send('Invalid user ID. Must be a number.');
}
// Convert string to number for use in application logic
const numericUserId = parseInt(userId, 10);
res.send(`You requested information for user ID: ${numericUserId}`);
});
Security Consideration
Always validate and sanitize parameter values before using them in database queries, file operations, or any other potentially dangerous operations. Parameters are user input and should never be trusted without validation.
Activity Instructions
For this activity, you will enhance your application by adding new functionality related to organizations. Specifically, you will create a new organization details page to display information about a specific organization and the service projects associated with it. This will use route parameters to identify organizations by their unique IDs.
New Functionality
You will add the following new functionality:
- Create a new organization details page that lists the details of a specific organization and the service projects associated with that organization. The URL for this page will be in the format
/organization/:idsuch as/organization/123 - Update your main partner organizations page (
/organizations) that lists all organizations so that it now includes links to the new organization details pages.
To complete this activity, follow these steps:
Create the model functions for organization details
For the organization details page, we will need two model functions:
- A function to retrieve the details of a specific organization.
- A function to retrieve the service projects associated with a specific organization.
In your organization model file src/models/organizations.js, create a new function that retrieves the details of a specific organization. This function should accept an organization ID as a parameter and return the relevant data.
- Open the
src/models/organizations.jsfile and add the following new function for retrieving organization details. It should come directly after the other function you already have that retrieves all organizations. - Export the new function by adding it to the export statement at the bottom of the file as follows:
const getOrganizationDetails = async (organizationId) => {
const query = `
SELECT
organization_id,
name,
description,
contact_email,
logo_filename
FROM organization
WHERE organization_id = $1;
`;
const query_params = [organizationId];
const result = await db.query(query, query_params);
// Return the first row of the result set, or null if no rows are found
return result.rows.length > 0 ? result.rows[0] : null;
};
// Export the model functions
export { getAllOrganizations, getOrganizationDetails };
Important Security Note
Notice that this function uses a parameterized query to safely include the organization ID in the SQL statement, which helps prevent SQL injection attacks. If you included the organization ID directly in the query string, it could allow malicious users to execute arbitrary SQL commands.
In this course, you should always use parameterized queries when including user input in SQL statements. Never put the user input directly into the query string.
Next, you will create the model function to retrieve the service projects associated with a specific organization. This function should be in the src/models/projects.js model file, because it related specifically to projects.
- Open the
src/models/projects.jsfile and add the following new function for retrieving service projects associated with an organization. It should come directly after the other function you already have that retrieves all projects. - Export the new function by adding it to the export statement at the bottom of the file as follows:
const getProjectsByOrganizationId = async (organizationId) => {
const query = `
SELECT
project_id,
organization_id,
title,
description,
location,
date
FROM project
WHERE organization_id = $1
ORDER BY date;
`;
const query_params = [organizationId];
const result = await db.query(query, query_params);
return result.rows;
};
// Export the model functions
export { getAllProjects, getProjectsByOrganizationId };
Create the controller function for organization details
In your organization controller file src/controllers/organizations.js, create a new controller function that uses the model functions you just created to get the details of organization and its service projects and then pass that data to a new view to render the information.
- Open the
src/controllers/organizations.jsfile and import the two model functions at the top of the file (replace your earlier import statement that only importedgetAllOrganizationsto now import two functions from that model): - Next, add the the following new controller function for the organization details page. It should come directly after the other controller function you already have to render the list of all organizations. This controller function should call the two model functions you just imported to get the organization details and its associated service projects, then pass that data to a new view to render the information.
- Notice that this code refers to an EJS template named
organization.ejswhich does not exist yet. You will create this EJS template in a future step. - Export the new controller function by adding it to the export statement at the bottom of the file as follows:
import { getAllOrganizations, getOrganizationDetails } from '../models/organizations.js';
import { getProjectsByOrganizationId } from '../models/projects.js';
const showOrganizationDetailsPage = async (req, res) => {
const organizationId = req.params.id;
const organizationDetails = await getOrganizationDetails(organizationId);
const projects = await getProjectsByOrganizationId(organizationId);
const title = 'Organization Details';
res.render('organization', {title, organizationDetails, projects});
};
// Export any controller functions
export { showOrganizationsPage, showOrganizationDetailsPage };
Update the router to include the new route
In your router file src/controllers/routes.js, add a new route that maps to the organization details controller function.
- Open the
src/controllers/routes.jsfile. At the top, add the following import to the list of model imports: - Add the following new route definition. You can place this at the end of the list of the other routes.
import { showOrganizationDetailsPage } from './organizations.js';
// Route for organization details page
router.get('/organization/:id', showOrganizationDetailsPage);
Notice that this route uses a URL parameter (:id) to capture the organization ID from the URL. This ID is then accessed by the controller function to fetch and display the details for the specific organization.
For example, the URL for this page will look like /organization/13 where 13 is the ID of the organization.
Create the organization details view
Create a new view file src/views/organization.ejs) to display the organization details and its associated service projects.
- Create a new file named
organization.ejsin yoursrc/viewsdirectory. - This EJS template should include the following:
- The header and footer partials.
- An
<h2>heading for the organization name. - An image of the organization's logo.
- A paragraph with the organization's description.
- A list of the service projects associated with the organization. Each service project should be a link to the service project details page. You have not created this page yet, but you will in the team activity this week. The URL for the links should be
/project/[id]where[id]is the ID of the service project. (This link will not work yet, but as soon as you create the page in your team activity it will work.)
Sample code (click to expand)
Your code for src/views/organization.ejs should look similar to the following:
<%- include('partials/header') %>
<main>
<h1><%= title %></h1>
<h2><%= organizationDetails.name %></h2>
<img src="/images/<%= organizationDetails.logo_filename %>" alt="<%= organizationDetails.name %> logo">
<p>Description: <%= organizationDetails.description %></p>
<h3>Service Projects</h3>
<ul>
<% projects.forEach(project => { %>
<li><a href="/project/<%= project.project_id %>"><%= project.title %></a></li>
<% }) %>
</ul>
</main>
<%- include('partials/footer') %>
Notice that the image URL will be built to have the logo filename from the organization details, so it will generate a URL like this /images/logo.png where logo.png is the filename of the organization's logo.
Update the organizations list view to include links
Update the main partner organizations view src/views/organizations.ejs to include links to the new organization details pages.
- Open the
src/views/organizations.ejsfile. - Modify the code that displays each organization to include a link to the organization details page. The new link should look as follows:
<a href="/organization/<%= organization.organization_id %>"><%= organization.name %></a>
Notice that the organization id is placed in the link URL, so that it will generate a URL like this /organization/13 where 13 is the ID of the organization.
Sample code (click to expand)
The complete src/views/organizations.ejs file should look similar to this:
<%- include('partials/header') %>
<main>
<h1><%= title %></h1>
<p>Here are a few of our partner organizations:</p>
<ul>
<% organizations.forEach(organization => { %>
<li><img src="/images/<%= organization.logo_filename %>" alt="<%= organization.name %> logo"><a href="/organization/<%= organization.organization_id %>"><%= organization.name %></a>: <%= organization.contact_email %></li>
<% }); %>
</ul>
</main>
<%- include('partials/footer') %>
Test your application
Start your application and navigate to the organizations list page. Click on an organization link to verify that the organization details page displays the correct information.
Hint: View the HTML source
If you encounter any issues with the links or images not displaying correctly, viewing the HTML source can help you diagnose the problem. In the browser, right-click on the page and select "View Page Source" (or a similar option depending on your browser). This will show you the raw HTML that was generated by your EJS templates.
Deploy your updated application
Once you have verified that everything is working correctly locally, deploy your updated application to your hosting environment, and test it there.
Submission
You have now completed all the required steps for this week's learning activities.
Return to Canvas to take the associated quiz there.
Other Links:
- Return to: Week Overview | Course Home