TLDR: If your Supabase database column can be NULL, your Swift
Codablestruct MUST declare itOptional. One mismatched type fails the entire query decode — silently — and your app will confidently lie to you about it.
The Setup
I was building a native iPhone CRM for an ecommerce client — an app that talks to a Supabase (Postgres-as-a-backend) database — trying to get a first build into TestFlight (Apple's beta distribution service) with real contact data.
Except the data wasn't there.
Or rather: it was there, in the database. The app just wasn't telling me that.
What I Chased First
My first instinct was auth.
I was using the wrong Supabase publishable key format. (There are two. I had the wrong one.) Fixed it.
Still no contacts. Still no data.
So I went after symptoms — oversized UI, empty relationship owner dropdowns, saves that felt like they worked. I was debugging effects. I hadn't found the cause.
The Double Lie
The real break came when I added a fix to await server confirmation before dismissing forms.
And that's when the truth came out.
Forms had been dismissing optimistically — before the insert actually resolved. A failed save looked like a success. The catch swallowed the error. The UI said "great!" The data was quietly not saved.
That was lie #1.
But why were the inserts failing in the first place?
The Root Cause
Swift's Codable (the native protocol for JSON encoding/decoding) is strict about this stuff.
When it tries to decode a Supabase row and hits a null for a field declared as non-optional — say, String instead of String? — it throws a DecodingError.valueNotFound.
That throw gets swallowed somewhere up the call stack. The whole query returns nothing. No error message. No partial results. Just silence.
And one bad row fails the entire query decode, not just that field.
That was lie #2.
The WIN Audit
I went through every Codable struct in the app and cross-checked it against the actual database schema.
Every column that could be NULL in Postgres had to be Optional in Swift. Fields like bio and relationship_owner were the main offenders — real-world nullable data hitting non-optional Swift types.
One bonus gotcha I found along the way: I had a keyDecodingStrategy set on the decoder AND explicit CodingKeys on the same struct. Don't do that — they conflict with each other. Pick one.
Why This Matters to Me
The silence is the dangerous part. Not the type mismatch itself.
If the error had surfaced visibly, I would have found this in five minutes. Instead it got swallowed, the form dismissed with a smile, and I spent hours chasing symptoms.
Two rules I'm keeping from this:
- DB nullable = Swift
Optional. Always. Model reality, not wishful thinking. - Never dismiss a form before the server responds. Await confirmation. Show the error. Let the user know whether something actually happened — or didn't.
Silent failures are indistinguishable from success. That's exactly what makes them so expensive.
P.S. The commit message for the fix was literally "WIN audit." It felt like one.