CSE 340: Web Backend Development

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:

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:

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

Conceptual Differences

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:

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:

  1. A function to retrieve the details of a specific organization.
  2. 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.

  1. Open the src/models/organizations.js file 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.
  2. 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;
    };
    
  3. Export the new function by adding it to the export statement at the bottom of the file as follows:
  4. // 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.

  1. Open the src/models/projects.js file 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.
  2. 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;
    };
  3. Export the new function by adding it to the export statement at the bottom of the file as follows:
  4. // 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.

  1. Open the src/controllers/organizations.js file and import the two model functions at the top of the file (replace your earlier import statement that only imported getAllOrganizations to now import two functions from that model):
  2. import { getAllOrganizations, getOrganizationDetails } from '../models/organizations.js';
    import { getProjectsByOrganizationId } from '../models/projects.js';
    
  3. 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.
  4. 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});
    };
  5. Notice that this code refers to an EJS template named organization.ejs which does not exist yet. You will create this EJS template in a future step.
  6. Export the new controller function by adding it to the export statement at the bottom of the file as follows:
  7. // 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.

  1. Open the src/controllers/routes.js file. At the top, add the following import to the list of model imports:
  2. import { showOrganizationDetailsPage } from './organizations.js';
  3. Add the following new route definition. You can place this at the end of the list of the other routes.
  4. // 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.

  1. Create a new file named organization.ejs in your src/views directory.
  2. 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.)
  3. 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.

  1. Open the src/views/organizations.ejs file.
  2. Modify the code that displays each organization to include a link to the organization details page. The new link should look as follows:
  3. <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: