TLDR: I was fetching all 859 facility rows from the database on every single chat message and filtering in JavaScript. One function call fixed it — ~8x less data over the wire, per turn.
The Setup
A chat app that helps patients locate verified cancer treatment facilities had been running fine in testing.
Then came the night audit.
I wasn't chasing a specific bug. I was just digging through the code at 11pm the way you do when something feels slightly off but you can't name it yet.
And then I found it.
The Wall
Every time a user sent a chat message, the code was pulling every single facility row in the database — all 859 of them — down from storage into the Node process.
Then it filtered.
In JavaScript.
On the server.
Every. Single. Turn.
So what? GREAT question. The filter itself wasn't wrong — the logic was correct, the verified flag was checked, the right facilities came back. But we were dragging all the hay through the network just to find a few needles on the other side.
That's not a bug. It's a habit. And it's surprisingly easy to miss.
What I Tried First (Honestly)
I'll be real — I didn't try anything wrong here first. This one was pure discovery.
The filter worked so it never screamed at me. The AI responses looked correct. No errors, no timeouts (yet), no user complaints.
That's the sneaky thing about this class of performance problem: it hides behind correct behavior until the data grows, or the load spikes, or you just happen to actually read the code on a Tuesday night.
The Fix That Worked
One new function: storage.getVerifiedFacilities().
Instead of fetching all facilities and filtering in JS, we push the verified = true condition down into the database query itself and only pull the rows we actually need.
// Before: fetch everything, filter in JS
const all = await storage.getFacilities()
const verified = all.filter(f => f.verified)
// After: filter at the DB layer
const verified = await storage.getVerifiedFacilities()
Result: ~8x less data over the wire per chat turn.
That's not a latency number I'm inventing — it's the ratio of verified facilities to total facilities in the DB. We went from pulling 859 rows to pulling the ~100 that actually matter.
Why This Matters to Me
The rule I've internalized from this one: if the filter is on a structured column — a boolean flag, a status, a date range, a UUID — it belongs in the query, not in JavaScript after the fetch.
SQL is exact. It's instant. It's free, in the sense that the database is already doing a scan anyway.
JS post-filtering is a second pass over data you've already paid to move across the network. You're taxing every request to compensate for a decision you made at query-authoring time.
The chat context makes this especially sharp. Every message is a new round-trip. Bloated fetches don't just cost once — they compound.
Write the narrowest query that returns exactly what you need. Let the database do what databases are for.