CSE 340: Web Backend Development

W03 Learning Activity: Status Codes and Errors

Overview

Preparation Material

HTTP Status Codes

As you may recall from earlier learning activities, each time a client makes a request of the server, the server responds with an HTTP status code.

The Request Response Lifecycle
The Request-Response Lifecycle

While you may not see HTTP status codes during normal browsing, they play a vital role in web development, diagnostics, and understanding errors. These codes provide a concise way to describe whether a request was successful, encountered a problem, or requires further action.

HTTP status codes are standardized three-digit numbers that a server includes in its response to a client's request. These codes are part of the response's headers, which are pieces of metadata that describe the server's response. They are not typically displayed to end-users (except in error situations), but they help browsers, developers, and other systems understand what happened during the request-response cycle.

Each status code is categorized based on its first digit, providing a general idea of the response type:

These categories help identify whether the issue lies with the client, server, or elsewhere in the communication process.

And within those areas, here are some of the most common status codes:

In this activity, you will learn how to correctly handle errors and use the correct HTTP status codes in your web applications.

Error Handling in Express

Every web application encounters problems: users request pages that do not exist, servers experience unexpected failures, or external services become unavailable. Without proper error handling, these situations lead to crashed applications, confusing error messages, or blank screens that frustrate users and make debugging nearly impossible for developers.

Professional Express applications handle errors gracefully using centralized error handling patterns that provide consistent user experiences and detailed debugging information.

When Express encounters an error without proper handling, the results are unpredictable and not user-friendly. Consider what happens when a user visits a non-existent page or when your application code throws an exception. By default, Express might crash the entire application, expose sensitive debugging information to users, or simply hang without responding.

These default behaviors create several serious problems: users receive confusing technical error messages instead of helpful guidance, developers struggle to diagnose issues without proper error logging, and applications become unreliable as unhandled errors can crash the entire server process.

Security and User Experience Risks

Unhandled errors often expose internal application details like file paths, database connection strings, or stack traces that attackers can exploit. Additionally, users encountering these raw error messages lose confidence in your application and may abandon their tasks entirely.

What is Error Handling?

Error handling in Express ensures that your application responds gracefully to problems by catching exceptions, logging diagnostic information, and sending appropriate responses to users.

Express provides built-in mechanisms for error handling through special middleware functions that have four parameters instead of the usual three. This creates a centralized system where all errors flow to designated handlers that can log details, determine appropriate responses, and maintain application stability.

The Global Error Handler Pattern

A global error handler is a special middleware function that Express invokes whenever an error is passed using next(err) or when an exception is thrown in synchronous route logic. This middleware must define four parameters: (err, req, res, next), which distinguishes it from regular middleware.

// Global error handler - note the four parameters
app.use((err, req, res, next) => {
    // Log error details for developers
    console.error('Error occurred:', err.message);
    console.error('Stack trace:', err.stack);
    
    // Determine response based on error type
    const status = err.status || 500;
    const message = status === 404 
        ? 'The page you requested could not be found.' 
        : 'An unexpected error occurred. Please try again later.';
    
    // Send appropriate response to user
    res.status(status).send(message);
});

This centralized approach ensures that all errors in your application are handled consistently, logged appropriately, and presented to users in a professional manner. The global error handler acts as a safety net that catches any error that occurs anywhere in your application.

Why Four Parameters Matter

Express identifies error-handling middleware by the number of parameters. Regular middleware uses three parameters (req, res, next), while error middleware uses four (err, req, res, next). This signature tells Express to only invoke this middleware when an error occurs.

How Errors Propagate Through Your Application

Errors in Express applications are passed using the next(err) function, which can be triggered from any route or middleware. When you call next(err), Express skips all remaining non-error middleware and jumps directly to the first error handler it encounters. Keep in mind the err parameter must be an instance of Error or a custom error object with a status property to indicate the HTTP status code.

// Route that triggers an error
app.get('/example-error', (req, res, next) => {
    // Simulate a problem - perhaps database connection failed
    const err = new Error('Database connection failed');
    err.status = 500;
    
    // Forward error to global error handler
    next(err);
});

// This middleware won't run if the error above occurs
app.use((req, res, next) => {
    console.log('This will be skipped when errors occur');
    next();
});

// Global error handler catches the error
app.use((err, req, res, next) => {
    res.status(err.status || 500).send(err.message);
});

Express also handles thrown exceptions the same way, provided they occur in synchronous code. This means you can either explicitly call next(err) or simply throw an error, and Express will route it to your error handler.

Handling 404 Errors: The Catch-All Route

When the client requests a resource that does not exists, meaning there is no matching route defined in your application, this is a should result in a 404 Not Found error.

In Express, 404 responses are not the result of an error, so the error-handler middleware will not capture them unless you explicitly create and forward a 404 error.

The professional approach is to add a "catch-all" middleware after all your real routes that creates a 404 error and forwards it to your global error handler:

// All your normal, real routes go here such as
app.get('/', (req, res) => {
    res.send('Home page');
});

app.get('/about', (req, res) => {
    res.send('About page');
});

// Catch-all middleware for unmatched routes
app.use((req, res, next) => {
    const err = new Error('Page Not Found');
    err.status = 404;
    next(err); // Forward to global error handler
});

// Global error handler processes both 404s and 500s
app.use((err, req, res, next) => {
    console.error(err.stack);
    
    const status = err.status || 500;
    const message = status === 404
        ? 'The page you requested does not exist.'
        : 'An unexpected server error occurred.';
    
    res.status(status).send(message);
});

This pattern is crucial because it ensures that 404 errors flow through the same centralized handling system as all other errors. It is important to set the status code on the error object before calling next(err). This allows your global error handler to distinguish between different error types and respond appropriately.

Order Matters in Express

The catch-all route must come after all your real routes but before your error handler. Express processes middleware in the order you define it, so placing the catch-all too early would prevent your real routes from ever being reached.

Activity Instructions

Complete the following steps to add error handling to your application

Create your 404 error page

First, create a new folder errors in your src/views folder. Your folder structure should look like this:

src/
└── views/
    ├── errors/
    └── partials/

Next, create a new file for your 404 error page: 404.ejs in the src/views/errors folder.

Include the following content in the 404.ejs page:

<%- include('../partials/header') %>
<main>
    <h1>Page Not Found</h1>
    <p>The page you are looking for does not exist.</p>
    <p>Return to the <a href="/">homepage</a> to continue browsing.</p>
</main>
<%- include('../partials/footer') %>

Using the same header and footer

Notice that your error pages can use the same header and footer partials as the rest of your site. This helps maintain a consistent look and feel across all pages, including error pages.

Create your 500 error page

Next, you will create a file to render when an unexpected server error occurs.

Create a file named 500.ejs in your src/views/errors directory and include the following content:

<%- include('../partials/header') %>
<main>
    <h1>Something Went Wrong</h1>
    <p>We are experiencing technical difficulties. Our team has been notified and is working to fix the issue.</p>
    <p>Return to the <a href="/">homepage</a> to continue browsing.</p>
    
    <% if (NODE_ENV === 'development') { %>
        <details open>
            <summary>Error Details (Development Only)</summary>
            <pre><%= error %></pre>
            <pre><%= stack %></pre>
        </details>
    <% } %>
</main>
<%- include('../partials/footer') %>

This template shows a user-friendly message while hiding technical details from users in production. The details element creates an expandable section that only appears during development.

Implement the catch-all route

Add this middleware to your server.js file after all your regular routes but before you start the server. This creates a 404 error for any request that does not match your defined routes:

// Catch-all route for 404 errors
app.use((req, res, next) => {
    const err = new Error('Page Not Found');
    err.status = 404;
    next(err);
});

Because you placed this after all of your other routes, this middleware only runs when no other route matches, creating a 404 error and forwarding it to your global error handler. Setting err.status = 404 tells your error handler what type of error occurred.

Create the global error handler

Add this error handling middleware after your catch-all route. The four-parameter signature tells Express this is specifically for handling errors:

// Global error handler
app.use((err, req, res, next) => {
    // Log error details for debugging
    console.error('Error occurred:', err.message);
    console.error('Stack trace:', err.stack);
    
    // Determine status and template
    const status = err.status || 500;
    const template = status === 404 ? '404' : '500';
    
    // Prepare data for the template
    const context = {
        title: status === 404 ? 'Page Not Found' : 'Server Error',
        error: err.message,
        stack: err.stack
    };
    
    // Render the appropriate error template
    res.status(status).render(`errors/${template}`, context);
});

This handler catches all errors, logs them for developers, and renders the appropriate template based on the error's status code. The logging helps you debug issues while the templates provide a polished experience for users.

Test Your Error Handling

It can be difficult to test error handling because errors are often unexpected and rare during normal use. In order to help test, you will create a test route you can load to trigger a 500 error.

Add this route before your catch-all middleware:

// Test route for 500 errors
app.get('/test-error', (req, res, next) => {
    const err = new Error('This is a test error');
    err.status = 500;
    next(err);
});

This route simulates a server error by creating a new error and forwarding it to the global error handler.

Test your error handling

Start your server and visit the following URLs in your browser:

Both should display your custom error pages instead of default browser error messages. Check your server console to confirm errors are being logged properly.

Next Step

Complete the other Week 03 Learning Activities

After you have completed all the learning activities for this lesson, return to Canvas to submit a quiz.

Other Links: