Currently viewing the human version
Switch to AI version

Your App Crashes Because Node 18+ Kills the Process

Your Node.js app worked fine in development. In production, it randomly dies with exit code 1 and this cryptic message:

UnhandledPromiseRejectionWarning: Error: connection timeout
(node:15287) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.

Then 30 seconds later, your process manager restarts it. PM2 logs show nothing useful.

Here's why: Node 18+ kills your entire process when you have unhandled promise rejections. Not just a warning anymore - the process actually exits with code 1.

Most developers learn this when their app dies in production. I've debugged this exact issue probably 50 times across different teams.

Why Your App Dies in Production

Missing try/catch everywhere:

// This crashes your server
app.get('/user/:id', async (req, res) => {
  const user = await database.findUser(req.params.id);
  res.json(user);
});

Database times out, query throws, no try/catch = dead server.

Fire-and-forget async work:

// Crashes server later
app.post('/process-data', async (req, res) => {
  res.json({ message: 'Processing started' });

  processLargeDataset(req.body.data); // No await, no catch
});

You sent the response already, but async work crashes the server.

Express 4 middleware problems:

// Middleware that kills your app
app.use(async (req, res, next) => {
  await authenticateUser(req); // Throws
  next(); // Never called
});

Express 4 doesn't catch async middleware errors. They become unhandled rejections that kill your server.

This broke in Express 4.16.0 when they added async support but forgot to catch middleware errors. Took me 4 hours to debug why our auth middleware kept crashing the server.

Fix #1: Async Wrapper for Express 4

const asyncHandler = (fn) => (req, res, next) => {
  Promise.resolve(fn(req, res, next)).catch(next);
};

// Now your routes won't crash
app.get('/user/:id', asyncHandler(async (req, res) => {
  const user = await database.findUser(req.params.id);
  res.json(user);
}));

This catches any promise rejection and sends it to your error handler. No more crashed servers.

Fix #2: Global Process Handlers

process.on('unhandledRejection', (reason, promise) => {
  console.error('Unhandled Rejection:', reason);
  process.exit(1); // Let PM2/Docker restart it
});

process.on('uncaughtException', (error) => {
  console.error('Uncaught Exception:', error);
  process.exit(1);
});

Don't try to recover from these errors. Your app is fucked. Log it and die gracefully. Let PM2 or Docker restart the process.

Fix #3: Error Handler Middleware

const errorHandler = (error, req, res, next) => {
  console.error('API Error:', error.message);

  let statusCode = 500;
  let message = 'Internal server error';

  if (error.name === 'ValidationError') {
    statusCode = 400;
    message = 'Invalid request data';
  } else if (error.status === 401) {
    statusCode = 401;
    message = 'Authentication required';
  }

  res.status(statusCode).json({
    success: false,
    error: message
  });
};

app.use(errorHandler);

Put this after all your routes. It catches errors and sends proper HTTP responses instead of crashing.

Express 5 vs Express 4

Express 5 automatically catches async route handler errors. You don't need the asyncHandler wrapper.

// Express 5 - this works
app.get('/user/:id', async (req, res) => {
  const user = await database.findUser(req.params.id);
  res.json(user);
});

// Express 4 - need wrapper
app.get('/user/:id', asyncHandler(async (req, res) => {
  const user = await database.findUser(req.params.id);
  res.json(user);
}));

But you still need global process handlers and error middleware in both versions.

Database Error Handling

Database connections fail all the fucking time. Handle it:

const dbQuery = async (text, params) => {
  let client;
  try {
    client = await pool.connect();
    const result = await client.query(text, params);
    return result;
  } catch (error) {
    console.error('Database error:', error.message);
    throw error;
  } finally {
    if (client) client.release();
  }
};

// Use it
app.get('/users/:id', asyncHandler(async (req, res) => {
  const result = await dbQuery('SELECT * FROM users WHERE id = $1', [req.params.id]);

  if (result.rows.length === 0) {
    const error = new Error('User not found');
    error.status = 404;
    throw error;
  }

  res.json(result.rows[0]);
}));

Always release database connections in the finally block. Always. I've seen production apps run out of database connections because someone forgot this.

Your app starts throwing ECONNREFUSED errors and you think Postgres is down. Nope - you just leaked all 100 connections from the pool. The fix is one line in a finally block, but it takes down prod for 2 hours.

Memory Leaks from Bad Error Handling

Clean up your shit when errors happen:

// This leaks memory
const cache = new Map();

app.get('/data/:id', async (req, res) => {
  try {
    let data = cache.get(req.params.id);
    if (!data) {
      data = await fetchExpensiveData(req.params.id);
      cache.set(req.params.id, data); // Never cleaned up on errors
    }
    res.json(data);
  } catch (error) {
    res.status(500).json({ error: 'Failed' });
  }
});

// Better - clean up on errors
app.get('/data/:id', async (req, res) => {
  const cacheKey = req.params.id;
  try {
    let data = cache.get(cacheKey);
    if (!data) {
      data = await fetchExpensiveData(cacheKey);
      cache.set(cacheKey, data);
      // Auto-expire after 5 minutes
      setTimeout(() => cache.delete(cacheKey), 300000);
    }
    res.json(data);
  } catch (error) {
    cache.delete(cacheKey); // Clean up failed requests
    throw error;
  }
});

The Real Problem

Every Node.js tutorial I've seen assumes your Postgres never times out and your users always send valid JSON. In production, everything breaks constantly:

  • Database connections drop
  • External APIs timeout
  • Users send malformed data
  • Network requests fail
  • Memory runs out
  • Disk fills up

Your error handling needs to expect failure, not success. When I review Node.js apps, the ones that stay up are the ones that assume everything will break.

Set up proper error handling before your app hits production. Trust me - debugging crashed servers at 3am sucks.

What Actually Works

Pattern

Express 4

Express 5

Worth It?

AsyncHandler Wrapper

Required

Optional

Hell yes

  • saved me countless 3am calls

Try/Catch Everywhere

Works

Works

You're fucked without this

Global Process Handlers

Required

Required

Absolutely fucking essential

Express 5 Built-in

N/A

Native

Worth upgrading just for this

Common Questions About Node.js Errors

Q

Why does my Node.js app randomly crash with UnhandledPromiseRejectionWarning?

A

Node 18+ kills your process when promise rejections aren't caught. Your async functions are throwing errors with no .catch() or try/catch.

Most common cause: database timeouts in route handlers without error handling.

Q

My app works locally but crashes in production. Why?

A

Production is where everything goes to shit. Your local Postgres never times out, external APIs respond in 50ms, and users always send perfect JSON.

Production has real network failures, database connection limits, and users sending garbage data. That's where async errors actually happen - when things break.

Q

Should I use try/catch or asyncHandler wrapper?

A

Express 4: Use asyncHandler. You'll forget try/catch blocks and your app will crash.

Express 5: Built-in async support works, but still use wrappers for complex middleware.

Q

What's the difference between unhandledRejection and uncaughtException?

A
  • unhandledRejection = Promise rejected, no .catch()
  • uncaughtException = Regular error, no try/catch

Both are fatal. Log and restart the process.

Q

My database queries randomly timeout. How do I handle this?

A

Database connections fail all the fucking time in production. Network hiccups, connection pool exhaustion, Postgres restarts - it's constant.

Retry timeout errors, don't retry syntax errors:

const dbQuery = async (query, params, retries = 3) => {
  for (let i = 0; i < retries; i++) {
    try {
      return await pool.query(query, params);
    } catch (error) {
      if (error.code === 'ETIMEDOUT' && i < retries - 1) {
        await new Promise(resolve => setTimeout(resolve, 1000));
        continue;
      }
      throw error;
    }
  }
};
Q

How do I handle errors in fire-and-forget background tasks?

A

Background tasks are the #1 source of unhandled rejections because there's no response to send errors to.

// Wrong - unhandled rejection
app.post('/upload', (req, res) => {
  res.json({ message: 'Processing started' });
  processUploadedFile(req.file); // Crashes server if it fails
});

// Right - catch and log
app.post('/upload', (req, res) => {
  res.json({ message: 'Processing started' });
  processUploadedFile(req.file).catch(error => {
    console.error('File processing failed:', error.message);
  });
});
Q

Why do I get "Cannot set headers after they are sent"?

A

You're sending multiple responses. Your error handler sends a response, then your route tries to send another one.

Use return after sending error responses, or let error middleware handle it.

Q

My error logs are useless. How do I log better?

A

Bad: console.error(error) - just dumps stack trace

Good: Include context that helps you debug:

logger.error('Database query failed', {
  error: error.message,
  query: query,
  user: req.user?.id,
  endpoint: req.originalUrl,
  duration: Date.now() - startTime
});
Q

Should I always return 500 for server errors?

A

No. Different errors need different status codes:

  • 400 - Bad request data
  • 401 - Auth required
  • 404 - Not found
  • 500 - Actual server error
  • 503 - Service temporarily down
Q

How do I test error handling?

A

Mock your dependencies to throw errors:

it('handles database timeout', async () => {
  const timeoutError = new Error('timeout');
  timeoutError.code = 'ETIMEDOUT';

  jest.spyOn(db, 'query').mockRejectedValue(timeoutError);

  const response = await request(app).get('/users').expect(503);
});
Q

Why does my app still crash with error middleware?

A

Error middleware only catches request/response errors. It doesn't catch the shit that happens outside HTTP:

  • Background task errors (file processing, email sending)
  • WebSocket errors (connection drops, message parsing)
  • Event listener errors (database events, Redis pub/sub)

These bypass Express entirely. Use global process handlers:

process.on('unhandledRejection', (reason) => {
  console.error('Unhandled rejection:', reason);
  process.exit(1);
});
Q

How do I handle WebSocket errors?

A

WebSockets don't have HTTP middleware. Handle errors directly:

ws.on('error', (error) => {
  console.error('WebSocket error:', error.message);
  if (ws.readyState === WebSocket.OPEN) {
    ws.send(JSON.stringify({ error: 'Connection error' }));
  }
});

ws.on('message', async (data) => {
  try {
    const message = JSON.parse(data);
    await processMessage(message);
  } catch (error) {
    console.error('Message processing failed:', error.message);
    ws.send(JSON.stringify({ error: 'Processing failed' }));
  }
});

Related Tools & Recommendations

tool
Similar content

Node.js Error Handling That Actually Works in Production

Because debugging "Cannot read property 'id' of undefined" at 3am is not a career I recommend.

Node.js
/tool/node.js/error-handling-monitoring
96%
tool
Similar content

Node.js Memory Leaks and Debugging - Stop Your App From Crashing at 3am

Learn to identify and debug Node.js memory leaks, prevent 'heap out of memory' errors, and keep your applications stable. Explore common patterns, tools, and re

Node.js
/tool/node.js/debugging-memory-leaks
96%
tool
Similar content

Node.js Production Deployment - How to Not Get Paged at 3AM

Optimize Node.js production deployment to prevent outages. Learn common pitfalls, PM2 clustering, troubleshooting FAQs, and effective monitoring for robust Node

Node.js
/tool/node.js/production-deployment
95%
tool
Similar content

Node.js - The Runtime That Actually Works

JavaScript that escaped browser jail and took over backends everywhere. Fast, event-driven, and doesn't create a new thread for every damn request.

Node.js
/tool/node.js/overview
94%
tool
Similar content

Node.js Microservices - Why Your Team Probably Fucked It Up

Learn why Node.js microservices projects often fail and discover practical strategies to build robust, scalable distributed systems. Avoid common pitfalls and e

Node.js
/tool/node.js/microservices-architecture
94%
tool
Similar content

Node.js Version Management - Survive the Upgrade Hell

Master Node.js versions across projects without the 3am "it works on my machine" disasters. Handle major version migrations, compatibility nightmares, and npm p

Node.js
/tool/node.js/version-management
86%
tool
Similar content

Node.js Deployment: Stop Breaking Production at 3AM

Master Node.js deployment strategies, from traditional servers to modern serverless and containers. Learn to optimize CI/CD pipelines and prevent production iss

Node.js
/tool/node.js/deployment-strategies
86%
tool
Similar content

Node.js Ecosystem Integration 2025 - When JavaScript Took Over Everything

Node.js went from "JavaScript on the server? That's stupid" to running half the internet. Here's what actually works in production versus what looks good in dem

Node.js
/tool/node.js/ecosystem-integration-2025
81%
integration
Similar content

Claude API + Express.js - Production Integration Guide

Stop fucking around with tutorials that don't work in production

Claude API
/integration/claude-api-nodejs-express/complete-implementation-guide
67%
tool
Popular choice

jQuery - The Library That Won't Die

Explore jQuery's enduring legacy, its impact on web development, and the key changes in jQuery 4.0. Understand its relevance for new projects in 2025.

jQuery
/tool/jquery/overview
60%
troubleshoot
Similar content

Fix MongoDB "Topology Was Destroyed" Connection Pool Errors

Production-tested solutions for MongoDB topology errors that break Node.js apps and kill database connections

MongoDB
/troubleshoot/mongodb-topology-closed/connection-pool-exhaustion-solutions
59%
integration
Similar content

Claude API Code Execution Integration - Advanced Tools Guide

Build production-ready applications with Claude's code execution and file processing tools

Claude API
/integration/claude-api-nodejs-express/advanced-tools-integration
58%
tool
Similar content

Express.js - The Web Framework Nobody Wants to Replace

It's ugly, old, and everyone still uses it

Express.js
/tool/express/overview
58%
tool
Popular choice

Hoppscotch - Open Source API Development Ecosystem

Fast API testing that won't crash every 20 minutes or eat half your RAM sending a GET request.

Hoppscotch
/tool/hoppscotch/overview
57%
integration
Similar content

MongoDB + Express + Mongoose Production Deployment

Deploy Without Breaking Everything (Again)

MongoDB
/integration/mongodb-express-mongoose/production-deployment-guide
57%
tool
Popular choice

Stop Jira from Sucking: Performance Troubleshooting That Works

Frustrated with slow Jira Software? Learn step-by-step performance troubleshooting techniques to identify and fix common issues, optimize your instance, and boo

Jira Software
/tool/jira-software/performance-troubleshooting
55%
tool
Popular choice

Northflank - Deploy Stuff Without Kubernetes Nightmares

Discover Northflank, the deployment platform designed to simplify app hosting and development. Learn how it streamlines deployments, avoids Kubernetes complexit

Northflank
/tool/northflank/overview
52%
tool
Similar content

Mongoose - Because MongoDB's "Store Whatever" Philosophy Gets Messy Fast

Master Mongoose for MongoDB. Learn how it brings structure to your data, prevents common pitfalls, and saves you from debugging nightmares. Understand its featu

Mongoose
/tool/mongoose/overview
50%
tool
Popular choice

LM Studio MCP Integration - Connect Your Local AI to Real Tools

Turn your offline model into an actual assistant that can do shit

LM Studio
/tool/lm-studio/mcp-integration
50%
tool
Similar content

React Production Debugging - When Your App Betrays You

Five ways React apps crash in production that'll make you question your life choices.

React
/tool/react/debugging-production-issues
49%

Recommendations combine user behavior, content similarity, research intelligence, and SEO optimization