Back to Blog
Lesson 48 of the Node.js: Build Your First Server & CLI course
Node.jsSeptember 5, 20264 min read

Global Error Handler Refinement: Dev vs Prod Error Messages

Refine your Express global error handler to safely distinguish between dev and prod environments. Log detailed errors while keeping API responses clean.

Node.jsExpressError HandlingAPI DevelopmentSecurity
Simple and minimalist image showcasing the word 'ERROR' on a white background.

Previously in this course, we implemented Advanced Error Handling to catch exceptions in a single location. While that setup works, it currently treats every error the same, potentially leaking sensitive stack traces to users when things break.

In this lesson, we will refine our global error handler to provide meaningful, environment-specific feedback. We’ll ensure our developers get the information they need to debug, while our API consumers receive sanitized, professional error messages.

Why Separate Dev and Prod Error Messages?

When an error occurs in development, you want the full stack trace: file paths, line numbers, and the chain of function calls. This is your primary diagnostic tool.

However, exposing that same data in production is a massive security risk. Attackers can use stack traces to map out your file structure, identify library versions, or find vulnerable logic. We need a way to detect the environment and toggle the output accordingly.

The Environment-Aware Middleware

We use the NODE_ENV variable (which we covered in Environment Variables) to decide how to respond.

JAVASCRIPT
// middleware/errorMiddleware.js

const globalErrorHandler = (err, req, res, next) => {
  // Default to 500 if no status code is set
  const statusCode = err.statusCode || 500;

  // Log the full error for server-side monitoring
  console.error(CE9178">`[Error] ${statusCode} - ${err.message}`);
  console.error(err.stack);

  if (process.env.NODE_ENV === CE9178">'development') {
    // Detailed response for devs
    res.status(statusCode).json({
      status: CE9178">'error',
      message: err.message,
      stack: err.stack,
      error: err
    });
  } else {
    // Sanitized response for production
    res.status(statusCode).json({
      status: CE9178">'error',
      message: statusCode === 500 ? CE9178">'Something went wrong' : err.message
    });
  }
};

module.exports = globalErrorHandler;

Logging Detailed Errors

Close-up of colorful JavaScript code displayed on a computer monitor, ideal for tech-themed projects.

While the API response is sanitized, your server logs (often viewed in services like Render or Datadog) should remain verbose. Think of these logs as your "black box" flight recorder.

In production, avoid console.log for errors. Use a structured logger like winston or pino if you are building a production-grade system. For now, continuing to log the err.stack inside our middleware ensures that even when a user sees a generic "Something went wrong" message, you have the exact file and line number waiting for you in your dashboard.

Comparing Error Responses

FeatureDevelopment ResponseProduction Response
Status CodeOriginal (e.g., 400, 500)Original
MessageSpecific Error DetailSanitized Message
Stack TraceIncludedHidden
Primary GoalDebuggingSecurity / Privacy

Hands-on Exercise: Refine the Handler

  1. Open your existing middleware/errorMiddleware.js file.
  2. Update the logic to include the if (process.env.NODE_ENV === 'development') condition as shown above.
  3. Test your setup:
    • Set NODE_ENV=development in your .env file and trigger an error—you should see the stack trace in Postman.
    • Change it to NODE_ENV=production and restart your server—you should see a clean JSON response without the stack trace.

Common Pitfalls

  • Forgetting to call next(err): If you don't pass the error to next(), your custom middleware will never be triggered.
  • Leaking Database Details: Ensure that errors from Mongoose or your database driver are caught and mapped to a generic message before being sent to the client. Never pass raw database errors (which might contain connection strings or table names) directly to res.json().
  • Logging in the Wrong Place: Only log the error once in your central middleware. Avoid logging it in every controller, or your logs will become noisy and repetitive.

FAQ

Q: Should I ever show the stack trace in production? A: Never. It provides a roadmap for attackers to exploit your infrastructure.

Q: What if I need to debug production? A: That is exactly why we log the full error stack to the server console. Use your cloud provider's log viewer to inspect the server-side logs, not the API response.

Q: How do I handle 404 errors with this? A: 404s are technically errors. You can create a middleware that runs after all your routes to catch requests that didn't match anything, then call next(new Error('Not Found')) to feed them into this handler.

Recap

We've evolved our error handling from a basic catch-all to a professional-grade system. By splitting our output based on NODE_ENV, we balance the need for developer visibility with the absolute requirement for production security.

Up next: We will shift focus toward performance by implementing request caching strategies.

Similar Posts