Skip to main content

Command Palette

Search for a command to run...

Errors and How to Handle them.

Updated
5 min readView as Markdown
N

Hello, I’m Nripesh

I am an aspiring programmer, continuously learning and building projects one step at a time.

This blog will serve as a record of my journey, where I document the coding challenges I encounter each week and the solutions I work towards. My intention is not only to track my own growth but also to provide clarity for others who may face similar difficulties.

What you can expect here:

Weekly summaries of the issues I struggled with

Solutions, explanations, and key insights I gained from them

References and links to helpful resources, documentation, or my GitHub repositories

Occasional notes on personal projects I am developing

I am still in the early stages of my learning path and have much to improve, but I approach each challenge with confidence and persistence. This space is both a personal archive and an attempt to contribute—however modestly—to the wider learning community.

If you are also navigating the world of programming, I welcome you to connect and share perspectives.

A backend engineer is bound to face some errors sooner or later so it is necessary to create a fault tolerant backend system and handle the Errors.

Types of Errors:

1. Logical Errors

Cause: Flaws in reasoning, misunderstood requirements, or incorrect algorithm design.

  • Misinterpreting requirements

  • Incorrect algorithm / condition logic

  • Ignoring edge cases

Example:

if (age > 18) allowAccess();

Bug: misses age == 18 → should be >= 18


2. Database Errors

Cause: Issues interacting with the database layer.

  • Connection errors: DB not reachable

  • Constraint violations: unique/foreign key failure

  • Query errors: malformed SQL or wrong assumptions


3. External Service Errors

Cause: Dependency on third-party services (you’re effectively a client).

  • Network failures

  • Connection timeouts

  • DNS resolution failures

  • Network partitions

  • Rate limiting (e.g., Clerk, Auth0)

Example:

await axios.get("https://api.auth0.com/user");

Fails with 429 Too Many Requests

Mitigation:

  • Retry with exponential backoff (trying after 1min then 2 then 4 then 8 ...)

  • Fallback mechanisms


4. Input Validation Errors

Cause: Invalid or malformed user input (most controllable category).

  • Format validation (email, phone)

  • Range validation (age < 0)

  • Missing required fields

Example:

{ "email": "not-an-email" }

Rejected due to invalid format

Note: Easiest to prevent via schema validation (e.g., Joi, Zod)


5. Configuration Errors

Cause: Incorrect environment or system configuration.

  • Missing environment variables

  • Wrong API keys / DB URLs

  • Misconfigured ports/services

Example:

DATABASE_URL=undefined

App crashes on startup

Best Practice:

  • Fail fast at startup, not during runtime

  • Validate config using schemas before boot


Proactive Error Handling (Before Errors Happen)

The most effective error handling isn’t reactive — it’s preventive. Strong backend systems are designed to detect, isolate, and surface issues before they impact users.

1. Health Checks (Service-Level Monitoring)

Expose endpoints like:

  • /health

  • /status

These are continuously pinged by load balancers or monitoring tools to verify:

  • Service is running

  • Dependencies are reachable

  • System is responsive


2. Database Health Checks

Monitor database behaviour, not just availability:

  • Query latency (slow queries act like an early warning)

  • Connection pool saturation (long job queues).

  • Failed/blocked queries

Purpose: Catch degradation before full outages.


3. External Service Health Checks

Since third-party services are outside your control, actively validate them:

  • Dummy API calls to auth providers

  • Sending test emails via email services

  • Simulating lightweight transactions

Purpose: Ensure integrations (Auth, Payments, Notifications) are operational.


4. Configuration Validation (Fail Fast)

Misconfiguration should crash the app at startup, not during runtime.

  • Validate environment variables

  • Check API keys, DB URLs, ports

  • Schema-based config validation

Purpose: If the system can’t run correctly, don’t let it start.


5. Core Functionality Checks

Before deployment or during startup:

  • Ensure critical flows are functional

  • Verify dependencies are wired correctly

  • Run smoke tests on key features


6. Logging & Monitoring (Early Detection)

Proactive systems don’t just prevent errors — they detect and diagnose instantly:

  • Centralised logging (trace failures quickly)

  • Real-time monitoring & alerts

  • Track performance metrics (latency, throughput, not just errors)

Note: Performance degradation is often the first signal of an upcoming failure.

(We’ll cover logging, monitoring, and performance tracking in more depth afterwards.)


Handling Errors Gracefully (When They Do Happen)

Even with proactive systems, failures are inevitable. The goal is to minimise user impact, recover where possible, and fail intelligently where not.


1. Immediate Responses (Recoverable vs Non-Recoverable)

Recoverable Errors: System can retry or provide fallback.

  • Example: Email service down → Queue the email + retry later → Respond: “Action successful, email will be sent shortly”

Non-Recoverable Errors: Cannot complete the operation → contain damage.

  • Example: Payment processing failure → Abort transaction, rollback state → Respond: “Payment failed, please try again”

Key Idea:

  • Recover silently when possible

  • Fail transparently when necessary


2. Graceful Degradation

Instead of full failure, reduce functionality but keep system usable.

  • Recommendation service down → show generic content

  • Analytics failure → skip tracking, don’t block request

Goal: Maintain core user experience even under partial failure.


3. Error Recovery Strategies

Structured approaches to handle transient failures:

  • Retries with exponential backoff

  • Fallback mechanisms (cached data, secondary service)

  • Queue-based recovery (async jobs for later retry)

Example:

retry(fn, { retries: 3, backoff: exponential });

4. Error Propagation Control (Bubbling Up)

Errors should bubble up cleanly, not leak or get swallowed.

  • Lower layers → throw structured errors

  • Upper layers → decide response/action

Example:

// service layer
if (!user) throw new NotFoundError("User not found");

// controller layer
catch (err) {
  next(err); // pass to global handler
}

Principle:

  • Don’t handle errors where you can’t act meaningfully

5. Global Error Handling (Final Safety Net)

A centralised middleware that catches all unhandled errors.

It ensures to:

  • Standardise error responses

  • Hide internal details (no stack leaks)

  • Log errors for debugging

Example :

app.use((err, req, res, next) => {
  console.error(err);

  res.status(err.status || 500).json({
    message: err.message || "Internal Server Error",
  });
});

This layer ensures that: no request crashes silently, consistent API responses are sent and to centralisation of Logging and increasing the observability


Conclusion

Building resilient backend systems isn’t about eliminating errors—it’s about anticipating, detecting, and handling them with control and clarity. From proactive health checks to graceful degradation and centralised error handling, robustness comes from designing for failure at every layer.

In the next section, we’ll take this further by diving into Production-Grade Configuration Management—because many failures don’t start in code, they start in config.