The Debugging Tale of a Backend Crash Disguised as a Frontend Mystery
Last night, I lost three hours of my life to a single error message:
...blocked by CORS policy: No ‘Access-Control-Allow-Origin’ header is present...
If you’ve worked with frontend-backend communication across origins, you’ve probably met the dreaded CORS error. It’s like a rite of passage—an error message that usually means you forgot to set the right headers or misconfigured CORS middleware.
Except… sometimes it doesn’t mean that.
🧩 The Setup
I was building out a résumé analysis app using a React frontend (Vite) and a FastAPI backend, with a couple of AI models running under the hood. The backend was fully implemented and the UI laid out—I was just wiring up the frontend to hit my API.
I had three API endpoints that I wanted to reach with my frontend:
POST /upload-resume
POST /upload-job
POST /analyze
Also, I'd already configured CORS in FastAPI:
app.add_middleware(
CORSMiddleware,
allow_origins=["http://localhost:5173"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
I tested my upload endpoints, and everything worked fine. I could see in my server console that I was getting 200 OK
responses from my API, and I could see in DevTools that my React components were properly storing the returned data.
When I implemented an API call to my /analyze
route however, I got a CORS error. When that happened, I assumed I’d messed up the config.
But everything looked right. Through a litany of console log statements and DevTools analysis I triple-checked:
- Allowed origins were properly listed.
- Middleware was active.
- My request was well-formed.
- The network tab even showed the request reaching the server and being processed.
Still: CORS error.
🕵️ The Clue
What eventually tipped me off was this: other endpoints worked. Same domain. Same headers. Only /analyze
was choking.
I was baffled—why would only some routes throw a CORS error? That inconsistency turned out to be the key. Once I had triple-checked that my CORS middleware was set up properly, I honed in on the backend implementation of the /analyze
endpoint.
I tried a simple experiment. I stripped out all the logic from /analyze
and replaced it with a plain string return.
It worked. No CORS error.
Interesting.
💥 The Real Problem
I felt a thrill and relief from getting closer to the issue. Turns out the issue had nothing to do with CORS, but rather had something to do with my function implementation.
Specifically, my handler was returning a dictionary that included an embedding from a machine learning model. This embedding was a NumPy array, which—unbeknownst to me—contained numpy.float32
and numpy.int64
values.
What surprised me is that NumPy floats and ints look like native Python floats and ints, but they aren’t. FastAPI (via JSONResponse
or its default encoder) expects standard Python types—and NumPy’s versions aren’t JSON-serializable out of the box, meaning they can’t be converted into JSON.
Therefore, when FastAPI encountered these NumPy-specific numeric types, it quietly failed to serialize them. The result? A 500 Internal Server Error
, which aborted the response entirely.
Because the response never completed, the browser didn’t receive any CORS headers—so it flagged the issue as a CORS error, even though the real problem was deep in the backend.
Once I converted the NumPy floats and ints to standard Python types using float(x)
and int(x)
, everything worked fine.
‼️ The Core Takeaway
Not all CORS errors are CORS errors.
When FastAPI is unable to serialize data into JSON, the server crashes.
When the server crashes, no response is returned to the frontend.
And when no response is returned, naturally the frontend never receives the CORS headers that it expects. Therefore it registers a CORS error, even though CORS has nothing to do with the issue.
In summary: A backend server crash may register as a CORS error in the frontend.
📚 Lessons Learned
✍️ Not all CORS errors are CORS errors
Sometimes they're just backend failures with a CORS costume.
✍️ Isolate ruthlessly
Strip endpoints down to echoes. Meticulously track what's been verified to work. Rebuild upward until it breaks again.
✍️ DevTools and print statements beat intuition
Real data > vibes-based debugging.
✍️ ChatGPT is a powerful tool, but never replaces thoughtful debugging
It’s great for rubber-ducking, research, and writing helpers—but when it comes to debugging gnarly issues in a live system, you’re better off with print statements, DevTools, and your own patience. There's no shortcut to robust development.
✍️ Serialization matters
NumPy floats and ints look like native Python types, but they aren’t—and FastAPI can’t serialize them. This subtle mismatch caused the backend to crash without warning. Always convert NumPy types before returning them in a response.
✍️ Experience is a brutal but effective teacher
I felt totally drained by this debugging session, but I also feel grateful for having learned a subtle lesson that I'll never forget. This is the kind of wisdom and experience that's hard-fought, and which distinguishes hobbyist programmers from true engineers.
🧠 Reflections
I’ve been learning software development seriously for just over a year now, and moments like this remind me how much of engineering is earned through experience—not tutorials. There was no trick, no clever insight—just me, a browser console, and a backend silently crashing.
The whole saga was humbling and mentally draining—but honestly, I feel grateful. This kind of grind is where real engineering experience is forged.
Now that I’ve got this scar, maybe someone else won’t need to get the same one.