Color Mode

Building a Course Catalog with Route Parameters

In this assignment, you will create a simple course catalog system that demonstrates how route parameters work in Express. You will build two connected pages: a catalog list that shows available courses and individual course detail pages that display specific information based on the course ID captured from the URL.

This hands-on practice will help you understand how route parameters capture dynamic values from URLs and use them to display different content, building directly on the concepts you learned in the reading activity.

Setting Up Your Course Data

Since we have not yet covered databases or external data sources, you will work with a hardcoded JavaScript object that represents course information. Copy and paste this course data structure into your server.js file, placing it near the top after your imports but before your routes:


        // Course data - place this after imports, before routes
        const courses = {
            'CS121': {
                id: 'CS121',
                title: 'Introduction to Programming',
                description: 'Learn programming fundamentals using JavaScript and basic web development concepts.',
                credits: 3,
                sections: [
                    { time: '9:00 AM', room: 'STC 392', professor: 'Brother Jack' },
                    { time: '2:00 PM', room: 'STC 394', professor: 'Sister Enkey' },
                    { time: '11:00 AM', room: 'STC 390', professor: 'Brother Keers' }
                ]
            },
            'MATH110': {
                id: 'MATH110',
                title: 'College Algebra',
                description: 'Fundamental algebraic concepts including functions, graphing, and problem solving.',
                credits: 4,
                sections: [
                    { time: '8:00 AM', room: 'MC 301', professor: 'Sister Anderson' },
                    { time: '1:00 PM', room: 'MC 305', professor: 'Brother Miller' },
                    { time: '3:00 PM', room: 'MC 307', professor: 'Brother Thompson' }
                ]
            },
            'ENG101': {
                id: 'ENG101',
                title: 'Academic Writing',
                description: 'Develop writing skills for academic and professional communication.',
                credits: 3,
                sections: [
                    { time: '10:00 AM', room: 'GEB 201', professor: 'Sister Anderson' },
                    { time: '12:00 PM', room: 'GEB 205', professor: 'Brother Davis' },
                    { time: '4:00 PM', room: 'GEB 203', professor: 'Sister Enkey' }
                ]
            }
        };
    

This object structure allows you to look up course information using bracket notation like courses['CS121'], which is exactly what you will do when users visit URLs like /catalog/CS121.

Assignment Instructions

1. Create the Course Catalog List Page

Start by creating a route that displays all available courses in a simple list. Add this route to your server.js file before your error handling middleware:


        // Course catalog list page
        app.get('/catalog', (req, res) => {
            res.render('catalog', {
                title: 'Course Catalog',
                courses: courses
            });
        });
    

Create the corresponding template catalog.ejs in your views directory:


        <%- include('partials/header') %>
        <main>
            <h1>Course Catalog</h1>
            <p>Browse our available courses and click on any course to see detailed information including available sections and instructors.</p>
            
            <div class="course-list">
                <% Object.values(courses).forEach(course => { %>
                    <div class="course-card">
                        <h3><a href="/catalog/<%= course.id %>"><%= course.title %></a></h3>
                        <p class="course-id"><%= course.id %> • <%= course.credits %> credits</p>
                        <p class="course-description"><%= course.description %></p>
                    </div>
                <% }); %>
            </div>
        </main>
        <%- include('partials/footer') %>
    

Add a link to the catalog in your navigation. Update your header.ejs partial:


        <nav>
            <ul>
                <li><a href="/">Home</a></li>
                <li><a href="/about">About</a></li>
                <li><a href="/catalog">Course Catalog</a></li>
                <li><a href="/products">Products</a></li>
            </ul>
        </nav>
    

Test your catalog page by visiting http://127.0.0.1:3000/catalog. You should see a list of three courses, each with a clickable link.

2. Create the Course Detail Route with Parameters

Now implement the route that captures a course ID from the URL and displays detailed information about that specific course. Add this route after your catalog route:


        // Course detail page with route parameter
        app.get('/catalog/:courseId', (req, res) => {
            // Extract the course ID from the URL
            const courseId = req.params.courseId;
            
            // Look up the course in our data
            const course = courses[courseId];
            
            // Handle course not found
            if (!course) {
                const err = new Error(`Course ${courseId} not found`);
                err.status = 404;
                return next(err);
            }
            
            // Log the parameter for debugging
            console.log('Viewing course:', courseId);
            
            // Render the course detail template
            res.render('course-detail', {
                title: `${course.id} - ${course.title}`,
                course: course
            });
        });
    

Create the course detail template course-detail.ejs in your views directory:


        <%- include('partials/header') %>
        <main>
            <div class="course-header">
                <h1><%= course.title %></h1>
                <p class="course-meta"><%= course.id %> • <%= course.credits %> credits</p>
                <p class="course-description"><%= course.description %></p>
            </div>
            
            <div class="sections">
                <h2>Available Sections</h2>
                <div class="section-list">
                    <% course.sections.forEach(section => { %>
                        <div class="section-card">
                            <div class="section-time"><%= section.time %></div>
                            <div class="section-room">Room: <%= section.room %></div>
                            <div class="section-professor">Instructor: <%= section.professor %></div>
                        </div>
                    <% }); %>
                </div>
            </div>
            
            <div class="navigation">
                <p><a href="/catalog">&larr; Back to Course Catalog</a></p>
            </div>
        </main>
        <%- include('partials/footer') %>
    

Test your route parameter by clicking on course links from the catalog page. Try visiting URLs directly like /catalog/CS121, /catalog/MATH110, and /catalog/ENG101. Check your server console to see the logged course ID.

How Route Parameters Work

The :courseId in your route pattern tells Express to capture whatever comes after /catalog/ and make it available as req.params.courseId. When someone visits /catalog/CS121, Express captures "CS121" and lets you use it to look up the specific course data.

3. Add Query Parameter Sorting

Enhance your course detail page by adding the ability to sort sections using query parameters. Replace your course detail route with the following code:


        // Enhanced course detail route with sorting
        app.get('/catalog/:courseId', (req, res, next) => {
            const courseId = req.params.courseId;
            const course = courses[courseId];
            
            if (!course) {
                const err = new Error(`Course ${courseId} not found`);
                err.status = 404;
                return next(err);
            }
            
            // Get sort parameter (default to 'time')
            const sortBy = req.query.sort || 'time';
            
            // Create a copy of sections to sort
            let sortedSections = [...course.sections];
            
            // Sort based on the parameter
            switch (sortBy) {
                case 'professor':
                    sortedSections.sort((a, b) => a.professor.localeCompare(b.professor));
                    break;
                case 'room':
                    sortedSections.sort((a, b) => a.room.localeCompare(b.room));
                    break;
                case 'time':
                default:
                    // Keep original time order as default
                    break;
            }
            
            console.log(`Viewing course: ${courseId}, sorted by: ${sortBy}`);
            
            res.render('course-detail', {
                title: `${course.id} - ${course.title}`,
                course: { ...course, sections: sortedSections },
                currentSort: sortBy
            });
        });
    

And replace your course-detail.ejs template with this updated version that includes sorting links:


        <%- include('partials/header') %>
        <main>
            <div class="course-header">
                <h1><%= course.title %></h1>
                <p class="course-meta"><%= course.id %> • <%= course.credits %> credits</p>
                <p class="course-description"><%= course.description %></p>
            </div>
            
            <div class="sections">
                <div class="sections-header">
                    <h2>Available Sections</h2>
                    <div class="sort-options">
                        <span>Sort by: </span>
                        <a href="/catalog/<%= course.id %>" class="<%= currentSort === 'time' ? 'active' : '' %>">Time</a>
                        <a href="/catalog/<%= course.id %>?sort=professor" class="<%= currentSort === 'professor' ? 'active' : '' %>">Professor</a>
                        <a href="/catalog/<%= course.id %>?sort=room" class="<%= currentSort === 'room' ? 'active' : '' %>">Room</a>
                    </div>
                </div>
                
                <div class="section-list">
                    <% course.sections.forEach(section => { %>
                        <div class="section-card">
                            <div class="section-time"><%= section.time %></div>
                            <div class="section-room">Room: <%= section.room %></div>
                            <div class="section-professor">Instructor: <%= section.professor %></div>
                        </div>
                    <% }); %>
                </div>
            </div>
            
            <div class="navigation">
                <p><a href="/catalog">&larr; Back to Course Catalog</a></p>
            </div>
        </main>
        <%- include('partials/footer') %>
    

Test the sorting functionality by clicking the sort links on any course detail page. Try URLs like:

To help you understand how route and query parameters work together we added a console log statement that shows both the captured course ID and the selected sort option each time you visit a course detail page. Feel free to remove it once you are comfortable with the functionality.

4. Add Styling with Native CSS Nesting

Create a new CSS file catalog.css in your public/css/ directory and add these modern CSS styles using native nesting:


        /* Course catalog styles using native CSS nesting */
        .course-list {
            display: grid;
            gap: 1.5rem;
            max-width: 800px;
            margin: 0 auto;
            
            .course-card {
                border: 1px solid #ddd;
                border-radius: 8px;
                padding: 1.5rem;
                background: #fff;
                transition: box-shadow 0.2s ease;
                
                &:hover {
                    box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
                }
                
                h3 {
                    margin: 0 0 0.5rem 0;
                    
                    a {
                        color: #2c5aa0;
                        text-decoration: none;
                        
                        &:hover {
                            text-decoration: underline;
                        }
                    }
                }
                
                .course-id {
                    color: #666;
                    font-weight: 500;
                    margin: 0 0 1rem 0;
                }
                
                .course-description {
                    color: #444;
                    line-height: 1.5;
                    margin: 0;
                }
            }
        }

        .course-header {
            max-width: 800px;
            margin: 0 auto 2rem auto;
            text-align: center;
            
            h1 {
                color: #2c5aa0;
                margin-bottom: 0.5rem;
            }
            
            .course-meta {
                color: #666;
                font-weight: 500;
                margin: 0 0 1rem 0;
            }
            
            .course-description {
                color: #444;
                line-height: 1.6;
                font-size: 1.1rem;
            }
        }

        .sections {
            max-width: 800px;
            margin: 0 auto;
            
            .sections-header {
                display: flex;
                justify-content: space-between;
                align-items: center;
                margin-bottom: 1.5rem;
                border-bottom: 2px solid #2c5aa0;
                padding-bottom: 0.5rem;
                
                h2 {
                    margin: 0;
                    color: #2c5aa0;
                }
                
                .sort-options {
                    display: flex;
                    gap: 1rem;
                    align-items: center;
                    
                    span {
                        color: #666;
                        font-weight: 500;
                    }
                    
                    a {
                        color: #2c5aa0;
                        text-decoration: none;
                        padding: 0.25rem 0.5rem;
                        border-radius: 4px;
                        
                        &:hover {
                            background: #f0f4f8;
                        }
                        
                        &.active {
                            background: #2c5aa0;
                            color: white;
                        }
                    }
                }
            }
            
            .section-list {
                display: grid;
                gap: 1rem;
                
                .section-card {
                    border: 1px solid #ddd;
                    border-radius: 6px;
                    padding: 1rem;
                    background: #f9f9f9;
                    
                    .section-time {
                        font-weight: bold;
                        color: #2c5aa0;
                        font-size: 1.1rem;
                        margin-bottom: 0.5rem;
                    }
                    
                    .section-room,
                    .section-professor {
                        color: #555;
                        margin-bottom: 0.25rem;
                    }
                }
            }
        }

        .navigation {
            max-width: 800px;
            margin: 2rem auto 0 auto;
            text-align: center;
            
            a {
                color: #2c5aa0;
                text-decoration: none;
                font-weight: 500;
                
                &:hover {
                    text-decoration: underline;
                }
            }
        }
    

Link your new CSS file in your header.ejs partial by adding this line in the head section:


        <link rel="stylesheet" href="/css/catalog.css">
    

Refresh your catalog pages to see the improved styling with modern CSS nesting in action.

Native CSS Nesting

The CSS you just added uses native browser support for nesting selectors, eliminating the need for preprocessors like Sass or Less. This modern approach keeps your styles organized and maintainable while working directly in standard CSS.

5. Test Invalid Course IDs

Your route already includes error handling for invalid course IDs. Test this by visiting a non-existent course like /catalog/INVALID123. You should see your custom 404 error page, demonstrating how route parameter validation connects with your existing error handling system.

Try these invalid URLs to confirm your error handling works properly:

Key Concepts Summary

You have successfully built a functional course catalog system that demonstrates the fundamental concepts of route parameters in Express. Your application captures course IDs from the URL structure, uses them to look up and display specific course information, and handles invalid requests gracefully through your existing error system.

The sorting functionality shows how route parameters and query parameters work together: the route parameter identifies which course to display, while the query parameter modifies how that course's sections are presented. This combination creates flexible, user-friendly URLs that maintain their meaning while providing customization options.

These concepts form the foundation for building dynamic web applications that respond to user navigation while maintaining clean, understandable URL structures.

Experiment and Extend

Practice the route parameter concepts you just learned with these enhancements:

  1. Add More Courses: Expand the courses object with additional classes like "HIST101" or "BIOL150" and test your routes with the new data.
  2. Create a Random Course Feature: Add a route like /catalog/random that redirects to a randomly selected course detail page.
  3. Enhanced Validation: Add validation to ensure course IDs follow the expected pattern (letters followed by numbers) before looking them up.

These experiments are designed to deepen your understanding of how route parameters behave in different scenarios while giving you practice building on the patterns you just implemented. Your final grade will be based on how well you apply these concepts in your final project, so take the time to explore and strengthen your understanding.