Color Mode

Understanding and Using Middleware in Express

In this assignment, you will implement the middleware concepts you learned in the reading activity. Middleware functions allow you to process requests, share data across your application, and create reusable functionality that makes your code cleaner and more organized. By the end of this assignment, you'll gain a solid understanding of how to create middleware functions, use res.locals to share data across views, and apply middleware both globally and to specific routes.

Preparation

Before starting the assignment, we are going to create a section in your server.js file specifically for middleware functions. This will help keep your code organized as you add multiple middleware functions throughout the assignment. Just before your route handlers, add the following:


        /**
         * Configure Express middleware
         */

        // Middleware to make NODE_ENV available to all templates
        app.use((req, res, next) => {
            res.locals.NODE_ENV = NODE_ENV.toLowerCase() || 'production';

            // Continue to the next middleware or route handler
            next();
        });
    

This middleware sets the NODE_ENV variable in res.locals, making it accessible in all your EJS templates. This is useful for conditionally rendering content based on the environment (development or production). Normally you would combine multiple middleware functions together, but for clarity, we are adding them one at a time in this assignment.

Assignment Instructions

1. Introduction to Middleware

Start by adding a simple middleware function to log incoming requests to the console. This will help you understand how middleware processes every request that comes to your server.

Add the following middleware to your server.js file in your existing middleware section:


        app.use((req, res, next) => {
            // Skip logging for routes that start with /. (like /.well-known/)
            if (!req.path.startsWith('/.')) {
                console.log(`${req.method} ${req.url}`);
            }
            next(); // Pass control to the next middleware or route
        });
    

Save your file and navigate to different pages in your application. Watch your console to see the logs for each request. This demonstrates how middleware runs for every request before reaching your route handlers. After confirming it works, you can remove the console.log line.

2. Using res.locals for Global Data

The res.locals object allows you to set data that will be available to all your templates automatically. This eliminates the need to pass the same data to every route.

Add this middleware to your middleware functions section:


        // Middleware to add global data to all templates
        app.use((req, res, next) => {
            // Add current year for copyright
            res.locals.currentYear = new Date().getFullYear();
            
            next();
        });
    

Now update your footer.ejs partial to use the dynamic year:


        <footer>
            <p>&copy; <%= currentYear %> My Express App.</p>
        </footer>
    

Save both files and refresh any page. Your footer should now show the current year automatically without you having to pass it from each route.

3. Create Time-Based Greeting Middleware

Create global middleware that adds a greeting based on the time of day. This will help you understand how middleware can process dynamic information.

Your task: Complete this middleware to show different greetings for morning, afternoon, and evening. The message should be displayed in a paragraph tag.


        // Global middleware for time-based greeting
        app.use((req, res, next) => {
            const currentHour = new Date().getHours();
            
            /**
             * Create logic to set different greetings based on the current hour.
             * Use res.locals.greeting to store the greeting message.
             * Hint: morning (before 12), afternoon (12-17), evening (after 17)
             */
            ...
            
            next();
        });
    

Test it: Add <%- greeting %> to your header partial and refresh at different times to see the greeting change. You can also hardcode the currentHour variable to test different greetings without waiting for the actual time of day.

4. Add Theme Randomization Middleware

Create middleware that randomly selects a color theme for each page load. This demonstrates how middleware can affect the visual appearance of your site.

Your task: Complete this middleware to randomly assign one of three themes.


        // Global middleware for random theme selection
        app.use((req, res, next) => {
            const themes = ['blue-theme', 'green-theme', 'red-theme'];
            
            // Your task: Pick a random theme from the array
            const randomTheme = // Your random selection logic here
            res.locals.bodyClass = randomTheme;
            
            next();
        });
    

Update your header.ejs to use the random theme:


        <body class="<%= bodyClass %>">
    

Add this CSS to your existing body rule set in the main.css file to see the themes in action:


        &.blue-theme { background-color: #e3f2fd; }
        &.green-theme { background-color: #e8f5e8; }
        &.red-theme { background-color: #ffebee; }
    

Test it: Refresh your pages multiple times to see different background colors.

Remember, in this course, we're using modern nested CSS. The rules mentioned above should be nested inside the body {...} rule set as nested rules. If you're not familiar with this concept, take a moment to review it either with a teammate or by asking AI for clarification.

5. Create Query Parameter Helper Middleware

Build middleware that makes query parameters easily accessible to all your templates. Implement the middleware and prove it's working by printing the data on the course page (e.g., views/course-details.ejs).

Your task: Complete this middleware to make query parameters available globally and then update the course details template to display them for verification.


        // Global middleware to share query parameters with templates
        app.use((req, res, next) => {
            // Make req.query available to all templates for debugging and conditional rendering
            res.locals.queryParams = req.query || {};
            
            next();
        });
    

Test it: Visit a course details URL with query params (for example /catalog/[course]?sort=professor) and confirm the parameters appear on the page.

Hint: Displaying Sort Status

If you're having a hard time figuring out how to show the sort status based on query parameters, here's a hint on how you might implement it in your course-details.ejs template:


            <% if (???) { %>
                <div class="sort-message">
                    <p>Currently sorted by: <%= ??? %></p>
                </div>
            <% } else { %>
                <div class="sort-message">
                    <p>No sorting applied, using default sorting by time.</p>
                </div>
            <% } %>
        

This task is designed to give you practice using middleware to share data across your application, as well as editing EJS templates. Developing familiarity with conditional rendering in EJS is an important skill for building dynamic web applications.

6. Create a Demo Page with Special Headers (Route-Specific Middleware)

Create a demo page that demonstrates the difference between global middleware and route-specific middleware. Your global middleware has been adding data to all pages, but route-specific middleware only affects specific routes.

Your task: Create route-specific middleware that sets custom HTTP headers only for the demo page.


        // Route-specific middleware that sets custom headers
        const addDemoHeaders = (req, res, next) => {
            // Your task: Set custom headers using res.setHeader()
            // Add a header called 'X-Demo-Page' with value 'true'
            // Add a header called 'X-Middleware-Demo' with any message you want
            
            next();
        };
    

Now create the demo route that uses this middleware:


        // Demo page route with header middleware
        app.get('/demo', addDemoHeaders, (req, res) => {
            res.render('demo', {
                title: 'Middleware Demo Page'
            });
        });
    

Create a new template demo.ejs in your views directory:


        <%- include('partials/header') %>
        <main>
            <h1><%= title %></h1>
            
            <div class="demo-section">
                <h2>Global Middleware Results:</h2>
                <!-- NOTE: We intentionally used <<%- "%=" %> for the greeting because you cant display a p tag within another p tag -->
                <p><strong>Greeting:</strong> <%= greeting %></p>
                <p><strong>Current Year:</strong> <%= currentYear %></p>
                <p><strong>Theme:</strong> <%= bodyClass %></p>
                
                <% if (Object.keys(queryParams).length > 0) { %>
                    <p><strong>Query Parameters:</strong></p>
                    <ul>
                        <% Object.keys(queryParams).forEach(key => { %>
                            <li><%= key %>: <%= queryParams[key] %></li>
                        <% }); %>
                    </ul>
                <% } else { %>
                    <p><em>No query parameters. Try adding ?debug=true to the URL.</em></p>
                <% } %>
            </div>
            
            <div class="demo-section">
                <h2>Route-Specific Middleware Results:</h2>
                <p>This page has special HTTP headers that other pages don't have!</p>
                <p><strong>To see the headers:</strong></p>
                <ol>
                    <li>Open your browser's Developer Tools (F12)</li>
                    <li>Go to the Network tab</li>
                    <li>Refresh this page</li>
                    <li>Click on the demo page request</li>
                    <li>Look for headers starting with 'X-Demo' or 'X-Middleware'</li>
                </ol>
                <p><em>These headers only appear on this page because of route-specific middleware!</em></p>
            </div>
            
            <p><a href="/">&larr; Back to Home</a></p>
        </main>
        <%- include('partials/footer') %>
    

Add a link to your demo page in your navigation:


        <li><a href="/demo">Middleware Demo</a></li>
    

Test your complete system:

Understanding Global vs Route-Specific Middleware

Through this assignment, you've experienced both types of middleware:

Two Types of Middleware

Global middleware (using app.use()) runs for every request to your application. Your greeting, theme, and query parameter middleware are global.

Route-specific middleware only runs for specific routes where you include it. Your demo page middleware only runs when someone visits /demo.

Key Concepts Summary

You've successfully implemented middleware that demonstrates core Express patterns: processing request data, sharing information across your application, and applying functionality selectively. Your demo page shows how all these pieces work together to create a cohesive application experience.

The middleware concepts you practiced are fundamental to building professional web applications: global data sharing, dynamic content generation, request processing, and selective application of functionality.

Experiment and Extend

Try these extensions to deepen your middleware understanding:

  1. Add More Themes: Expand your theme array with additional color schemes.
  2. Create Seasonal Greetings: Add middleware that changes greetings based on the month or season.
  3. Add Request Counting: Create middleware that tracks how many requests have been made to your demo page specifically.

These experiments are essential for helping you understand how middleware can solve more complex problems while keeping your code organized and maintainable. It's crucial that you don't just skim through them. Take the time to dive in and really explore these concepts. Middleware is a foundational part of web development and you'll need to use it consistently. Make sure you're fully grasping these concepts by working through the experiments carefully and testing your understanding along the way.