Skip to main content
In this lesson we audit how an Express-based login demo handles exceptions. The goal is to trace error flows for critical failure paths, identify anti-patterns, and provide prioritized, minimal remediation snippets to standardize error handling across the app.
A presentation slide titled "Exception Flow Analysis" with a large "Demo" label on a dark curved shape at right. A small "© Copyright KodeKloud" appears in the lower-left corner.
This audit focuses on improving Express error handling, Node.js process safety, PostgreSQL connection resilience, JWT configuration, and observability (structured logging and metrics). The recommendations favor low-friction, drop-in fixes that can be prioritized by severity.

Objective

Trace error handling through the application for these critical paths:
  1. Database connection failure
  2. Third-party API timeout
  3. Invalid user input
  4. Authentication failure
  5. File system errors
For each path, verify:
  • Where the error is caught
  • How it is transformed
  • What gets logged
  • What the user sees
  • Whether the system state remains consistent
Also identify common anti-patterns (swallowed exceptions, catch-all handlers, errors-as-flow-control, missing boundaries, inconsistent error formats) and provide:
  • A standardized error response template
  • Prioritized remediation steps (favor small, drop-in improvements first)
Example CLI context observed during the audit:

Executive summary

The application contains multiple error handling gaps that can lead to service disruption, information disclosure, and inconsistent state. Key issues:
  • No global error middleware or consistent error response format.
  • PostgreSQL pool lacks connection limits and query timeouts.
  • Logging is console-only (no structured logs or centralized monitoring).
  • JWT creation/verification lacks explicit algorithm and startup secret validation.
  • No rate limiting or account lockout for authentication attempts.
  • No retry/circuit-breaker or graceful degradation strategies for dependencies.
Prioritize: 1) global error handler and process-level safety, 2) DB pool timeouts and query timeouts, 3) structured logging + JWT hardening, 4) rate limiting and lockout.

Application architecture (short)

  • Main server: server.js — no global error middleware, and no request-level timeouts configured.
  • Authentication route: routes/auth.js — single POST /api/auth/login handler; validation exists but is inconsistent.
  • Database config: config/database.js — creates a PostgreSQL pool without limits/timeouts.
Dependencies referenced: Note: No filesystem operations or third-party API clients were found in the scanned code. If added later, apply the same resilience patterns.

Audit findings (critical paths)

1) Database connection / query failure — Severity: 9/10

Error origin locations:
  • config/database.js:3-9 — pool created without connection/timeouts
  • routes/auth.js:30 — DB query executed without query-level timeout
Observed flow: PostgreSQL Connection Error -> pool.query() -> routes/auth.js catch -> error.code switch -> user-facing 5xx What’s good:
  • Some specific PostgreSQL error codes are mapped to 503 responses.
Gaps:
  • No query-level timeouts or server-side statement_timeout configured.
  • Pool has no max, connectionTimeoutMillis, idleTimeoutMillis.
  • No retry/circuit-breaker for transient DB errors.
  • Sensitive error details written to console.
Recommended minimal changes:
  • Add pool limits/timeouts.
  • Wrap queries with a timeout helper.
  • Consider server-side statement_timeout or client cancellation for long queries.

2) Third-party API timeouts — Not present (info)

No third-party HTTP clients detected. For any future integrations, require:
  • request-level timeouts
  • retry with exponential backoff
  • a circuit-breaker pattern (e.g., opossum)

3) Invalid user input — Severity: 6/10

Good:
  • Email format validation and normalization via express-validator in places.
  • Some password length checks.
Gaps:
  • No consistent max-length enforcement on inputs.
  • Output sanitization (XSS) not addressed if server reflects user input.
  • Validation error details exposed to clients (can leak internal structure).
  • No rate limiting for endpoints that validate input (DoS risk).
Fixes:
  • Enforce RFC-consistent max lengths (email <= 254) and password reasonable max.
  • Sanitize output where appropriate.
  • Return minimal, structured validation error responses.

4) Authentication failure handling — Severity: 7/10

Good:
  • Generic message for invalid credentials (reduces user enumeration).
  • Uses bcrypt.compare for password checks.
  • 401 status code returned for invalid credentials.
Gaps:
  • No startup validation that JWT_SECRET exists.
  • JWT signing lacks explicit algorithm and secure defaults (expiresIn).
  • No express-rate-limit or account lockout to mitigate brute-force.
  • JWT expiry values not validated at startup.
Fixes:
  • Validate JWT secret on boot.
  • Sign tokens with algorithm and expiresIn.
  • Add rate limiting and user lockout after failed attempts.

5) File system errors — Not present

No fs usage found. Future FS operations should include permission checks, try/catch, and path validation.

Error handling anti-patterns (summary)

  1. Generic catch-all handlers that swallow context — Severity: 8/10
  2. Console-only logging; no structured logs — Severity: 7/10
  3. Missing global error handler / middleware — Severity: 9/10
  4. No error boundaries or circuit breakers — Severity: 8/10
  5. Inconsistent error response formats — Severity: 5/10
Example of console-only logging that should be replaced:
Replace ad-hoc console.* calls with a logger and centralized error handling.

Error flow (conceptual)

Input Validation Authentication Database JWT
(400 errors) (401 errors) (503 errors) (500)
| v TRANSFORMATION LAYER Validation Details Auth Check Generic Msg DB Error Code Switch | v FINAL HANDLING HTTP Status Code Error JSON Response Structured Log (sensitive data redacted) | v RECOVERY MECHANISMS ✖ NONE PRESENT
Missing: Retry logic, circuit breakers, graceful degradation

Structured findings (prioritized)


Detailed remediation guide (practical snippets)

Priorities: Fix global handler and DB timeouts first, then logging, JWT hardening, and rate limiting.

1) Add a global error handler (Immediate)

Place this after all route registrations in server.js:
Handle process-level failures (place early in server startup). Warning: practice caution with process.exit in production; prefer graceful shutdown with drains and alerts.
Be careful using process.exit in production. Instead, try to perform a graceful shutdown: stop accepting new requests, finish inflight requests, flush logs, and then exit. Uncontrolled exits can cause cascading failures in some orchestrated environments.

2) Harden database pool and add query timeouts (High priority)

Update config/database.js to set pool limits and timeouts:
Add a small helper to enforce a query timeout around pool.query():
Note: Promise.race will reject after the timeout but does not cancel the in-flight server-side query. To stop long-running queries on the DB, configure PostgreSQL’s statement_timeout or use client cancellation APIs where available. Replace direct pool.query(...) calls with queryWithTimeout(pool, sql, params, 10000) or configure server-side statement_timeout for per-connection or per-query protection.

3) Validate JWT secret and sign tokens securely

Fail fast if the JWT secret is missing:
Sign tokens with algorithm and expiry:
Verify tokens using the same algorithms option:

4) Add rate limiting for authentication endpoints

Install and register express-rate-limit:
Consider additional account-level lockout logic (persist failed attempt counts) and exponential backoff.

5) Structured logging (replace console.*)

Create a logger (example using winston) and use it across the app:
Replace console.error / console.log with logger.error() / logger.info() and avoid logging sensitive data like plaintext passwords or secrets.

6) Improve input validation and sanitization

Use express-validator and return minimal validation errors:
Do not expose full validation stacks in production; return concise, actionable messages.

7) Standardized error response helper

Centralize error response formatting:
Use this helper everywhere for consistent client-facing responses.

Testing suggestions (quick checks)

  • Simulate long DB queries to verify timeouts:
  • Simulate connection exhaustion / concurrent login attempts:
  • Validate input validation behavior:
  • Check rate limiting by rapid requests:
Automated integration tests that simulate DB outages, slow queries, and bursts of authentication attempts will help validate resilience changes. Add smoke checks for expected error formats and status codes.

Final notes on resilience and fault tolerance

  • Introduce circuit breakers and retry policies for external dependencies (DB and future APIs). Libraries like opossum can help.
  • Cap concurrency via pool sizes and worker counts; fail fast and degrade gracefully if a subsystem is failing.
  • Centralize logs, metrics, and trace data for alerting and faster root cause analysis.
  • Validate critical environment variables (like JWT_SECRET and DB config) at startup to fail fast.
  • Start with the prioritized fixes: global error handler, DB timeouts, structured logging, JWT hardening, and rate limiting. These provide the largest risk reduction for the smallest changes.
References and further reading:

Watch Video