Implementing Error Handling in Express
In this assignment, you will build a professional error handling system for your Express application. Instead of letting users see confusing technical messages or blank screens when something goes wrong, you will create custom error pages that maintain your site's design and help users understand what happened.
You will implement the global error handler pattern you learned about, create dedicated error templates for different situations, and set up the catch-all route that ensures no request goes unanswered. By the end, your application will handle problems gracefully and provide a polished user experience even when things do not work as expected.
Organizing Your Error Templates
Professional applications keep error templates in a dedicated directory for clarity and maintainability. If you have not already, create an errors folder inside your views directory. If you already have this directory from previous steps, simply continue adding your error templates there.
Your directory structure should look like this:
src/
└── views/
├── errors/
├── partials/
└── [your existing pages]
Assignment Instructions
1. Create Your 404 Error Page
Start by creating the template users will see when they request a page that does not exist. Find an engaging 404 image or animated GIF online, save it in your public/images/ directory, and create a new file called 404.ejs in your views/errors directory.
<%- include('../partials/header') %>
<main>
<h1>Page Not Found</h1>
<p>The page you are looking for does not exist.</p>
<img src="/images/your-404-image.gif" alt="404 Not Found">
<p>Return to the <a href="/">homepage</a> to continue browsing.</p>
</main>
<%- include('../partials/footer') %>
Replace your-404-image.gif with your actual image filename and update the alt text to describe your image. Choose something that fits your site's personality – humor often works well for 404 pages.
2. Create Your 500 Error Page
When your application encounters unexpected problems, users need to see a helpful message rather than technical details. Create 500.ejs in your views/errors directory:
<%- 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><a href="/">Return to homepage</a></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.
3. Implement the Catch-All Route
Add this middleware to your server.js file after all your real 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);
});
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.
4. Create the Global Error Handler
Add this error handling middleware after your catch-all route. This represents one approach to centralized error handling in Express. By using a single error handler with the four-parameter signature (err, req, res, next), we can catch errors from anywhere in our application and present them consistently to users:
// Global error handler
app.use((err, req, res, next) => {
// Prevent infinite loops, if a response has already been sent, do nothing
if (res.headersSent || res.finished) {
return next(err);
}
// 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: NODE_ENV === 'production' ? 'An error occurred' : err.message,
stack: NODE_ENV === 'production' ? null : err.stack,
NODE_ENV // Our WebSocket check needs this and its convenient to pass along
};
// Render the appropriate error template with fallback
try {
res.status(status).render(`errors/${template}`, context);
} catch (renderErr) {
// If rendering fails, send a simple error page instead
if (!res.headersSent) {
res.status(status).send(`<h1>Error ${status}</h1><p>An error occurred.</p>`);
}
}
});
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.
Key features of this approach:
- Uses the error's status code or defaults to 500
- Hides sensitive error details in production environments
- Includes a callback to catch rendering errors, preventing recursive failures
- If the error template itself fails, falls back to a simple HTML response
5. Test Your Error Handling
Create a test route 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);
});
Now test both error types:
-
404 Error: Visit any non-existent page like
http://127.0.0.1:3000/does-not-exist -
500 Error: Visit
http://127.0.0.1:3000/test-error
Both should display your custom error pages instead of default browser error messages. Check your server console to confirm errors are being logged properly.
The catch-all route must come after your real routes but before your error handler. Express processes middleware in order, so placing these incorrectly will prevent them from working properly.
Key Concepts Summary
You have successfully implemented a professional error handling system that transforms how your application responds to problems. Instead of confusing users with technical messages or crashing entirely, your application now provides helpful, branded error pages that maintain user trust and guide them back to working parts of your site.
The global error handler pattern centralizes all error processing, making your application more maintainable and ensuring consistent responses. The catch-all route ensures that 404 errors flow through the same system as other errors, creating a unified approach to error management.
This foundation prepares you for more advanced error handling techniques, including logging errors to external services, creating different error responses for API requests versus web pages, and implementing user-friendly error recovery features.
The code we provided for you in this assignment includes a significant side effect you should be aware of. As currently written, it only supports 404 and 500 errors explicitly. If you throw any other error in the 400–599 range, it will be treated as a 500 error, which can hide the actual problem and make debugging more difficult.
Additionally, if your error template throws an error while rendering (e.g. due to a bug or missing data), the server will crash completely. This kind of failure isn't handled by our code and can take down the whole application in production.
Experiment and Extend
Now that your error handling system works, try these enhancements using the concepts you just learned:
- Test Different Error Types: Create additional test routes that trigger different kinds of errors and observe how your global handler processes them.
- Customize Your Error Pages: Add more styling, helpful links, or search functionality to make your error pages more useful.
- Add More Detailed Logging: Enhance your error logging to include timestamps, request URLs, and user agent information.