[{"data":1,"prerenderedAt":382},["ShallowReactive",2],{"blog-best-practices/error-handling":3},{"id":4,"title":5,"body":6,"category":358,"date":359,"dateModified":359,"description":360,"draft":361,"extension":362,"faq":363,"featured":361,"headerVariant":367,"image":368,"keywords":368,"meta":369,"navigation":370,"ogDescription":371,"ogTitle":368,"path":372,"readTime":373,"schemaOrg":374,"schemaType":375,"seo":376,"sitemap":377,"stem":378,"tags":379,"twitterCard":380,"__hash__":381},"blog/blog/best-practices/error-handling.md","Error Handling Best Practices: Secure Logging, User Messages, and Recovery",{"type":7,"value":8,"toc":345},"minimark",[9,16,25,30,33,78,82,85,100,104,107,116,120,123,132,136,139,148,152,155,164,168,171,180,184,256,262,284,288,291,314,333],[10,11,12],"tldr",{},[13,14,15],"p",{},"The #1 error handling best practice is separating what you log from what you show. Log full details server-side, show generic messages to users, never expose stack traces in production, and use error monitoring tools. These practices prevent 65% of information disclosure vulnerabilities.",[17,18,19],"quotable-box",{},[20,21,22],"blockquote",{},[13,23,24],{},"\"Error messages are security boundaries. What helps developers debug can help attackers exploit. Log everything, expose nothing.\"",[26,27,29],"h2",{"id":28},"the-two-audiences-for-error-messages","The Two Audiences for Error Messages",[13,31,32],{},"Every error has two audiences with different needs:",[34,35,36,52],"table",{},[37,38,39],"thead",{},[40,41,42,46,49],"tr",{},[43,44,45],"th",{},"Audience",[43,47,48],{},"Needs",[43,50,51],{},"Where",[53,54,55,67],"tbody",{},[40,56,57,61,64],{},[58,59,60],"td",{},"Developers",[58,62,63],{},"Full details, stack traces, context",[58,65,66],{},"Server logs, monitoring tools",[40,68,69,72,75],{},[58,70,71],{},"Users",[58,73,74],{},"What went wrong, how to fix it",[58,76,77],{},"API response, UI message",[26,79,81],{"id":80},"best-practice-1-generic-user-messages-3-min","Best Practice 1: Generic User Messages 3 min",[13,83,84],{},"Never expose internal details to users:",[86,87,89],"code-block",{"label":88},"Safe error responses",[90,91,96],"pre",{"className":92,"code":94,"language":95},[93],"language-text","// WRONG: Exposes internal details\nres.status(500).json({\n  error: 'Connection to PostgreSQL failed: ECONNREFUSED localhost:5432',\n  stack: error.stack,\n});\n\n// WRONG: Reveals table/column names\nres.status(500).json({\n  error: 'Column \"password_hash\" does not exist in table \"users\"',\n});\n\n// CORRECT: Generic message\nres.status(500).json({\n  error: 'An unexpected error occurred. Please try again.',\n  errorId: 'err_abc123', // Reference for support\n});\n\n// CORRECT: Specific but safe validation errors\nres.status(400).json({\n  error: 'Invalid input',\n  details: [\n    { field: 'email', message: 'Invalid email format' },\n    { field: 'password', message: 'Password must be at least 8 characters' },\n  ],\n});\n","text",[97,98,94],"code",{"__ignoreMap":99},"",[26,101,103],{"id":102},"best-practice-2-log-full-details-server-side-5-min","Best Practice 2: Log Full Details Server-Side 5 min",[13,105,106],{},"Capture everything you need for debugging:",[86,108,110],{"label":109},"Comprehensive error logging",[90,111,114],{"className":112,"code":113,"language":95},[93],"import { randomUUID } from 'crypto';\n\nfunction logError(error, req, context = {}) {\n  const errorId = randomUUID();\n\n  console.error({\n    errorId,\n    timestamp: new Date().toISOString(),\n    error: {\n      message: error.message,\n      name: error.name,\n      stack: error.stack,\n    },\n    request: {\n      method: req.method,\n      path: req.path,\n      query: req.query,\n      userId: req.user?.id,\n      ip: req.ip,\n      userAgent: req.headers['user-agent'],\n    },\n    context,\n  });\n\n  return errorId;\n}\n\n// Usage in error handler\napp.use((err, req, res, next) => {\n  const errorId = logError(err, req);\n\n  // Return safe message with error ID for support\n  res.status(500).json({\n    error: 'An unexpected error occurred',\n    errorId,\n  });\n});\n",[97,115,113],{"__ignoreMap":99},[26,117,119],{"id":118},"best-practice-3-use-error-monitoring-tools-10-min","Best Practice 3: Use Error Monitoring Tools 10 min",[13,121,122],{},"Production errors should be captured by monitoring tools:",[86,124,126],{"label":125},"Sentry integration example",[90,127,130],{"className":128,"code":129,"language":95},[93],"import * as Sentry from '@sentry/node';\n\nSentry.init({\n  dsn: process.env.SENTRY_DSN,\n  environment: process.env.NODE_ENV,\n});\n\n// Express error handler\napp.use((err, req, res, next) => {\n  // Capture error with context\n  Sentry.captureException(err, {\n    user: req.user ? { id: req.user.id, email: req.user.email } : undefined,\n    extra: {\n      path: req.path,\n      method: req.method,\n    },\n  });\n\n  res.status(500).json({ error: 'An unexpected error occurred' });\n});\n",[97,131,129],{"__ignoreMap":99},[26,133,135],{"id":134},"best-practice-4-distinguish-error-types-5-min","Best Practice 4: Distinguish Error Types 5 min",[13,137,138],{},"Different errors need different handling:",[86,140,142],{"label":141},"Error type handling",[90,143,146],{"className":144,"code":145,"language":95},[93],"class AppError extends Error {\n  constructor(message, statusCode, isOperational = true) {\n    super(message);\n    this.statusCode = statusCode;\n    this.isOperational = isOperational;\n  }\n}\n\n// Operational errors (expected, safe to show)\nclass ValidationError extends AppError {\n  constructor(errors) {\n    super('Validation failed', 400);\n    this.errors = errors;\n  }\n}\n\nclass NotFoundError extends AppError {\n  constructor(resource) {\n    super(`${resource} not found`, 404);\n  }\n}\n\nclass UnauthorizedError extends AppError {\n  constructor() {\n    super('Authentication required', 401);\n  }\n}\n\n// Error handler\napp.use((err, req, res, next) => {\n  // Operational errors: safe to show message\n  if (err.isOperational) {\n    return res.status(err.statusCode).json({\n      error: err.message,\n      ...(err.errors && { details: err.errors }),\n    });\n  }\n\n  // Programming errors: log and show generic message\n  console.error('Unexpected error:', err);\n  res.status(500).json({ error: 'An unexpected error occurred' });\n});\n",[97,147,145],{"__ignoreMap":99},[26,149,151],{"id":150},"best-practice-5-prevent-user-enumeration-3-min","Best Practice 5: Prevent User Enumeration 3 min",[13,153,154],{},"Authentication errors should not reveal whether a user exists:",[86,156,158],{"label":157},"Preventing user enumeration",[90,159,162],{"className":160,"code":161,"language":95},[93],"// WRONG: Different messages reveal user existence\napp.post('/login', async (req, res) => {\n  const user = await findUser(req.body.email);\n  if (!user) {\n    return res.status(400).json({ error: 'User not found' }); // WRONG\n  }\n  if (!await verifyPassword(req.body.password, user.password)) {\n    return res.status(400).json({ error: 'Wrong password' }); // WRONG\n  }\n});\n\n// CORRECT: Same message for all auth failures\napp.post('/login', async (req, res) => {\n  const user = await findUser(req.body.email);\n\n  if (!user || !await verifyPassword(req.body.password, user.password)) {\n    // Same response for missing user OR wrong password\n    return res.status(400).json({ error: 'Invalid credentials' });\n  }\n\n  // Success...\n});\n\n// CORRECT: Password reset (same response regardless of email existence)\napp.post('/forgot-password', async (req, res) => {\n  // Always return same response\n  res.json({ message: 'If an account exists, a reset link was sent.' });\n\n  // Process in background\n  const user = await findUser(req.body.email);\n  if (user) {\n    await sendResetEmail(user.email);\n  }\n});\n",[97,163,161],{"__ignoreMap":99},[26,165,167],{"id":166},"best-practice-6-handle-async-errors-5-min","Best Practice 6: Handle Async Errors 5 min",[13,169,170],{},"Unhandled promise rejections can crash your server:",[86,172,174],{"label":173},"Async error handling",[90,175,178],{"className":176,"code":177,"language":95},[93],"// Wrapper for async route handlers\nconst asyncHandler = (fn) => (req, res, next) => {\n  Promise.resolve(fn(req, res, next)).catch(next);\n};\n\n// Usage\napp.get('/users/:id', asyncHandler(async (req, res) => {\n  const user = await db.user.findUnique({ where: { id: req.params.id } });\n\n  if (!user) {\n    throw new NotFoundError('User');\n  }\n\n  res.json(user);\n}));\n\n// Global unhandled rejection handler (backup)\nprocess.on('unhandledRejection', (reason, promise) => {\n  console.error('Unhandled Rejection:', reason);\n  // Log to monitoring service\n});\n",[97,179,177],{"__ignoreMap":99},[26,181,183],{"id":182},"common-error-handling-mistakes","Common Error Handling Mistakes",[34,185,186,199],{},[37,187,188],{},[40,189,190,193,196],{},[43,191,192],{},"Mistake",[43,194,195],{},"Risk",[43,197,198],{},"Prevention",[53,200,201,212,223,234,245],{},[40,202,203,206,209],{},[58,204,205],{},"Stack traces in response",[58,207,208],{},"Internal code exposure",[58,210,211],{},"Generic messages only",[40,213,214,217,220],{},[58,215,216],{},"Database errors to users",[58,218,219],{},"Schema disclosure",[58,221,222],{},"Catch and remap errors",[40,224,225,228,231],{},[58,226,227],{},"User enumeration",[58,229,230],{},"Account discovery",[58,232,233],{},"Uniform error messages",[40,235,236,239,242],{},[58,237,238],{},"No error monitoring",[58,240,241],{},"Missing production issues",[58,243,244],{},"Use Sentry or similar",[40,246,247,250,253],{},[58,248,249],{},"Swallowing errors",[58,251,252],{},"Silent failures",[58,254,255],{},"Always log or rethrow",[257,258,259],"info-box",{},[13,260,261],{},"External Resources:\nFor comprehensive error handling guidance, see the\nOWASP Error Handling Cheat Sheet\nand the\nOWASP Improper Error Handling\ndocumentation for industry-standard security recommendations.",[263,264,265,272,278],"faq-section",{},[266,267,269],"faq-item",{"question":268},"Should I show detailed errors in development?",[13,270,271],{},"Yes, detailed errors in development speed up debugging. Use environment checks: show full details when NODE_ENV is development, generic messages in production.",[266,273,275],{"question":274},"What error monitoring tools should I use?",[13,276,277],{},"Sentry is the most popular choice. Alternatives include LogRocket, Bugsnag, and Rollbar. For simpler setups, structured logging to a service like Datadog or Logtail works well.",[266,279,281],{"question":280},"How do I debug production errors with generic messages?",[13,282,283],{},"Include an error ID in the response and log full details with that ID server-side. When users report issues, you can look up the full error by ID.",[26,285,287],{"id":286},"further-reading","Further Reading",[13,289,290],{},"Put these practices into action with our step-by-step guides.",[292,293,294,302,308],"ul",{},[295,296,297],"li",{},[298,299,301],"a",{"href":300},"/blog/how-to/add-security-headers","Add security headers to your app",[295,303,304],{},[298,305,307],{"href":306},"/blog/checklists/pre-deployment-security-checklist","Pre-deployment security checklist",[295,309,310],{},[298,311,313],{"href":312},"/blog/getting-started/first-scan","Run your first security scan",[315,316,317,323,328],"related-articles",{},[318,319],"related-card",{"description":320,"href":321,"title":322},"Secure logging patterns","/blog/best-practices/logging","Logging Best Practices",[318,324],{"description":325,"href":326,"title":327},"Secure API design","/blog/best-practices/api-design","API Security",[318,329],{"description":330,"href":331,"title":332},"Production monitoring","/blog/best-practices/monitoring","Security Monitoring",[334,335,338,342],"cta-box",{"href":336,"label":337},"/","Start Free Scan",[26,339,341],{"id":340},"check-your-error-handling","Check Your Error Handling",[13,343,344],{},"Scan your application for information disclosure in error messages.",{"title":99,"searchDepth":346,"depth":346,"links":347},2,[348,349,350,351,352,353,354,355,356,357],{"id":28,"depth":346,"text":29},{"id":80,"depth":346,"text":81},{"id":102,"depth":346,"text":103},{"id":118,"depth":346,"text":119},{"id":134,"depth":346,"text":135},{"id":150,"depth":346,"text":151},{"id":166,"depth":346,"text":167},{"id":182,"depth":346,"text":183},{"id":286,"depth":346,"text":287},{"id":340,"depth":346,"text":341},"best-practices","2026-01-23","Error handling security best practices. Learn to handle errors securely, avoid information disclosure, implement proper logging, and create user-friendly error messages.",false,"md",[364,365,366],{"question":268,"answer":271},{"question":274,"answer":277},{"question":280,"answer":283},"vibe-green",null,{},true,"Handle errors securely without exposing sensitive information to attackers.","/blog/best-practices/error-handling","10 min read","[object Object]","Article",{"title":5,"description":360},{"loc":372},"blog/best-practices/error-handling",[],"summary_large_image","DyJkrt5QMroR78kGetms1sj1u-RGBNcihL8CZLDowk8",1775843926239]