How to Debug API Request Errors — Common Dev Errors Explained
Every developer hits the same handful of errors over and over again: CORS blocks the browser, process.env is undefined despite being set, a recursive function suddenly crashes the tab, and Python dicts throw KeyError on keys that seem to be right there. This guide covers how to debug API request errors and the most common dev mistakes — with exact fixes for each one.
CORS
#1 beginner API error in browsers
process.env
Top Node.js gotcha for new projects
5 min
Average fix time with the right approach
How to Fix CORS Error: No Access-Control-Allow-Origin
CORS (Cross-Origin Resource Sharing) is a browser security mechanism that blocks JavaScript from reading responses from a different origin than the current page. When you call an API from your frontend and see this error:
Access to fetch blocked by CORS policy
The fix is always server-side — you cannot fix CORS in your frontend JavaScript. The server needs to send the right response headers. Here is the most common case and how to fix it:
Missing CORS headers on the server
No CORS headers — browser blocks the response
// Express.js — no CORS headers (requests from browser will be blocked)
app.get('/api/data', (req, res) => {
res.json({ message: 'Hello' });
});Proper CORS middleware — browser can read the response
// Express.js — correct CORS setup with cors middleware
const cors = require('cors');
app.use(cors({
origin: 'https://yourfrontend.com', // or '*' for public APIs
methods: ['GET', 'POST', 'PUT', 'DELETE'],
allowedHeaders: ['Content-Type', 'Authorization'],
}));
app.get('/api/data', (req, res) => {
res.json({ message: 'Hello' });
});Key things to know about CORS errors:
- CORS errors only happen in browsers. The same request from cURL, Postman, or your backend code will succeed — it is purely a browser enforcement mechanism.
- The preflight request (OPTIONS) must also return the correct headers, not just the actual GET/POST.
- Credentials (cookies, Authorization header) require
Access-Control-Allow-Credentials: trueand theorigincannot be*. - If you do not control the server, use a backend proxy to forward the request from your own origin.
Test CORS headers without guessing
Why Is process.env Undefined in Node.js?
process.env.MY_VARIABLE returning undefined in Node.js is one of the most common gotchas for developers new to environment variables. There are four distinct causes, each with a different fix:
Forgot to call dotenv
The dotenv package reads your .env file into process.env, but only after you call require("dotenv").config() or import "dotenv/config". If you never call it, the file is never read.
Wrong .env file name or location
dotenv looks for .env in the current working directory by default — which is the directory you run node from, not the file location. Common mistake: running node from a parent directory.
Variable not exported in shell
If you set a variable in your shell with VAR=value (no export), child processes like Node.js will not inherit it. Use export VAR=value or add it to your shell profile.
Next.js server/client mismatch
In Next.js, environment variables are only available in server-side code unless they are prefixed with NEXT_PUBLIC_. Accessing a non-prefixed variable in a Client Component always returns undefined.
// At the very top of your entry file (index.js or server.js)
// Option 1: require style
require('dotenv').config();
// Option 2: ESM import style (Node 16+)
// import 'dotenv/config';
// Now process.env is populated from your .env file
console.log(process.env.DATABASE_URL); // works
// Common mistake: calling config() too late
// All imports run before your code — so if another module reads
// process.env at import time, dotenv must be configured first.Next.js: missing NEXT_PUBLIC_ prefix for client-side env vars
Missing NEXT_PUBLIC_ prefix — undefined in Client Components
// .env.local
API_URL=https://api.example.com
// components/MyComponent.tsx (Client Component)
const url = process.env.API_URL; // undefined in browserNEXT_PUBLIC_ prefix — available in both server and client
// .env.local
NEXT_PUBLIC_API_URL=https://api.example.com
// components/MyComponent.tsx (Client Component)
const url = process.env.NEXT_PUBLIC_API_URL; // works in browser
// Note: non-prefixed vars like DB_URL are still available
// in server components, API routes, and getServerSidePropsHow to Fix 'Maximum Call Stack Size Exceeded'
A RangeError: Maximum call stack size exceeded (or stack overflow) means a function is calling itself recursively without ever reaching a base case that stops the recursion. Each function call adds a frame to the call stack; when the stack fills up, JavaScript throws this error.
Root cause
This error is almost always caused by a function calling itself — either directly or indirectly through a chain of calls — without a proper base case that terminates the recursion.
// BROKEN: no base case — infinite recursion
function factorial(n) {
return n * factorial(n - 1); // never stops!
}
factorial(5); // RangeError: Maximum call stack size exceeded
// FIXED: base case stops the recursion
function factorial(n) {
if (n <= 1) return 1; // base case
return n * factorial(n - 1); // recursive case
}
factorial(5); // 120
// ALSO BROKEN: accidentally calling the function with wrong args
function processItems(items) {
if (items.length === 0) return;
processItems(items); // bug: should be items.slice(1), not items
}
// FIXED:
function processItems(items) {
if (items.length === 0) return;
processItems(items.slice(1)); // pass a smaller array each time
}For very deep recursion (e.g. traversing a large tree), even a correct recursive implementation may hit the stack limit. In those cases, convert the recursion to an iterative approach using an explicit stack (an array) instead of the call stack:
// Recursive tree traversal (may stack overflow on deep trees)
function sumTree(node) {
if (!node) return 0;
return node.value + sumTree(node.left) + sumTree(node.right);
}
// Iterative version using an explicit stack array
function sumTreeIterative(root) {
if (!root) return 0;
const stack = [root];
let total = 0;
while (stack.length > 0) {
const node = stack.pop();
total += node.value;
if (node.right) stack.push(node.right);
if (node.left) stack.push(node.left);
}
return total;
}How to Test API Requests Without Postman
Postman is a great tool but it requires an install, an account, and it can be overkill for quick one-off API tests. Here are the best alternatives for testing API requests without Postman:
Test API requests directly in the browser
Copy or write your API call
Start with the API endpoint URL, method (GET/POST/etc.), any required headers like Authorization or Content-Type, and the request body if needed.
curl 'https://api.example.com/users' \
-H 'Authorization: Bearer YOUR_TOKEN' \
-H 'Content-Type: application/json'Test with CORS Tester
Go to unblockdevs.com/cors-tester, enter the URL and headers, and fire the request. See the full response body, status code, and all response headers — including any missing CORS headers.
Check response headers
Look at the response headers in the output. Missing Content-Type, wrong status codes, or absent CORS headers become immediately obvious. This tells you whether the problem is in your request or the server response.
Convert to code
Once you have a working cURL command, paste it into unblockdevs.com/curl-converter to get the equivalent Python requests, JavaScript fetch, or Axios code for your project.
How to Handle KeyError in Python Dictionary
A KeyError in Python means you tried to access a dictionary key that does not exist. This is one of the most common Python errors when working with API responses — the JSON structure you expected is slightly different from what the API actually returned.
Direct dictionary access vs. safe access
Direct dict access — crashes if key is missing
# API response
data = {"user": {"name": "Alice", "email": "alice@example.com"}}
# Unsafe access — KeyError if 'phone' does not exist
phone = data["user"]["phone"] # KeyError: 'phone'
# Also unsafe — if the API skips 'user' entirely
name = data["user"]["name"] # KeyError: 'user'Safe .get() access — returns None or default value
# Safe access with .get() — returns None if key missing
phone = data.get("user", {}).get("phone") # None, not KeyError
# With a default value
phone = data.get("user", {}).get("phone", "N/A") # "N/A"
# Explicit check before access
if "user" in data and "phone" in data["user"]:
phone = data["user"]["phone"]import json
from collections import defaultdict
raw = '{"status": "ok", "data": {"id": 42}}'
response = json.loads(raw)
# Pattern 1: .get() with optional default
user_id = response.get("data", {}).get("id", 0)
print(user_id) # 42
username = response.get("data", {}).get("username", "anonymous")
print(username) # "anonymous" — key missing, no crash
# Pattern 2: try/except for complex access patterns
try:
nested_value = response["data"]["profile"]["avatar"]["url"]
except KeyError as e:
print(f"Missing key: {e}")
nested_value = None
# Pattern 3: defaultdict for building up results
from collections import defaultdict
counts = defaultdict(int)
for item in ["a", "b", "a", "c", "a"]:
counts[item] += 1 # no KeyError even on first access
print(dict(counts)) # {'a': 3, 'b': 1, 'c': 1}Debugging API response structure
print(json.dumps(response, indent=2)). This shows you exactly what the API returned — which is often subtly different from the documentation.