Skip to main content
Skip to main content
Still in beta — questions, comments or suggestions? aramb@aramb.dev

Add Better Error Responses and User-Friendly Failure States

Learn how to return consistent JSON error responses from Lambda and handle them gracefully in your frontend to improve user experience.

30 min
Introductory
AWS Free TierFREE TIER

All services used in this lesson are covered by the AWS Free Tier.

AWS Services Used

LambdaAlways free tierAPI Gateway12-month free tier

Learning Outcomes

By the end of this lesson, you will be able to:

  1. Return consistent JSON error responses from Lambda.
  2. Choose appropriate HTTP status codes for common dashboard failures.
  3. Explain the difference between a handled application error and an unhandled integration error.
  4. Update the frontend to show user-friendly failure messages.
  5. Avoid malformed responses that cause 502 Bad Gateway errors.

Why This Lesson Matters

A production-ready app should fail clearly when something goes wrong. In a Lambda proxy integration, your function's response shape is your API contract. If your code crashes or returns the wrong format, API Gateway defaults to a generic 502 Bad Gateway with a vague "Internal server error" message. Hardening means making these failures intentional and informative.

Handled vs. Unhandled Error Flows

The Response Shape You Must Respect

For Lambda proxy integration, your function must return a structured object. If you return a raw dictionary as the body instead of a string, or if you miss the required fields, the API will fail.

The Safe Pattern (Python):

return {
    "statusCode": 400,
    "headers": {"Content-Type": "application/json"},
    "body": json.dumps({
        "error": {
            "code": "MISSING_BUCKET",
            "message": "The 'bucket' query parameter is required."
        }
    })
}

SituationStatus CodeRecommended Action
Missing query/body field400User should fix the request
Auth missing/invalid401User should sign in
File metadata missing404Show "Not Found" state in UI
Rate limiting/Throttling429User should wait and retry
Unexpected backend crash500Log the error and tell user to try later

A Simple Backend Pattern

Use a helper function to ensure every error follows the same structure. This makes your frontend code much simpler to write.

import json

def error_response(status_code, code, message):
    return {
        "statusCode": status_code,
        "headers": {"Content-Type": "application/json"},
        "body": json.dumps({
            "error": {
                "code": code,
                "message": message
            }
        })
    }

# Usage:
if not bucket_name:
    return error_response(400, "INVALID_INPUT", "Missing bucket name")

Frontend Failure Handling

Don't just log errors to the console. Update your UI to handle the specific codes your backend now provides.

async function apiRequest(url, options = {}) {
  const response = await fetch(url, options);
  const data = await response.json();

  if (!response.ok) {
    // Look for our custom error structure first
    const message = data?.error?.message || "An unexpected error occurred.";
    throw new Error(message);
  }

  return data;
}

Lab Checklist

StepSuccess Condition
Input ValidationLambda returns 400 for missing parameters
Item LookupLambda returns 404 if a metadata record doesn't exist
Safe Proxy ShapeAll returns use json.dumps() for the body
UI FeedbackFrontend shows the specific error message from the API
Global CatchLambda wraps logic in try/except to return a handled 500

Micro-activity 1: Mapping Failures

Think about it

Pick one route in your project and decide how it should fail: What status code and error code should a missing file return (400)? A bad search (404)? A server crash (500)? What user-friendly message would you show for each?


Micro-activity 2: Reflection

Think about it

Why is a handled 404 better than letting the route crash and return a 502? Why should the frontend try to parse the JSON body even when response.ok is false?


Summary

In this lesson, you polished the edges of your application. Production-grade software is defined as much by how it handles failure as by how it handles success. By returning consistent, structured error responses, you've built a more resilient and user-friendly dashboard.


Quiz

Knowledge Check
1 / 5

What happens if a Lambda proxy integration returns a Python dictionary as the body instead of a string?