Backends that don't fall over
APIs, databases, queues, auth, and payments built type-safe end to end on Next.js, Postgres, Stripe, and Vercel — so the front-end can move fast without breaking the back.
What a backend actually has to survive
Most backends don't fail on the happy path. They fail on the second Tuesday of the month, when a payment provider retries a webhook it already sent, a customer double-clicks a submit button on a flaky hotel connection, and a background job that has been quietly failing for nine days finally fills a disk.
A backend is the part of your product nobody sees until it's wrong. When it's wrong, it's wrong expensively: a charge that goes through twice, a signup that creates two accounts for one person, an order that never reaches your warehouse because a queue swallowed it and nothing was watching. Those aren't exotic bugs. They're the standard failure modes of systems that were built to work rather than built to survive being retried, interrupted, and hit twice at once.
So when I say "backends that don't fall over," I mean five specific things: APIs, databases, queues, auth, and payments, each built so that doing the same thing twice produces the same result once, and so that a failure in one of them doesn't take the rest down with it.
Type-safe end to end, and why that isn't a style preference
The pitch for a type-safe stack usually gets made as a matter of taste. It isn't. It's a matter of where you find your bugs.
In an untyped setup, the contract between your database, your API, and your interface lives in three places: the schema, the handler, and the component that renders the result. Nothing keeps them in sync. You rename a column, the query still compiles, and you find out in production that a field renders as undefined for every user created before last March.
When the types flow from the database schema through the API layer and into the components, that whole class of mistake moves from runtime to your editor. Rename the column, and every place that reads it lights up red before you commit. This buys two concrete things:
- Fewer runtime bugs of the dumbest kind. Not logic errors — those still take thought — but the shape errors: missing fields, nullable values treated as non-null, a number that was actually a string all along. Those are the ones that hit real users at 2am.
- Faster iteration, honestly faster. Refactoring stops being scary. When the compiler enumerates every call site for you, changing a data model on day forty of a project costs about what it cost on day four. That's what lets the front-end move fast without breaking the back.
It also makes the codebase legible to whoever comes after me. Types are the documentation that can't drift out of date, because the build fails when it does.
The stack: Next.js, Postgres, Stripe, Vercel
I default to one combination for backend work, and I default to it for reasons, not habit.
Next.js puts the API and the interface in one codebase, sharing one set of types and one deploy. For the size of product I usually build — a real business system with dozens to hundreds of screens, not a thousand-service platform — the split between "the API repo" and "the front-end repo" costs more in coordination than it ever returns. One repo, one deploy, one type graph.
Postgres because relational data is what most businesses actually have. Customers have orders, orders have line items, line items reference products. That's a relational shape, and Postgres enforces it with foreign keys, unique constraints, and transactions instead of hoping the application layer remembers to. It also handles the things you'd otherwise reach for another system to do: JSON columns when a shape is genuinely loose, full-text search when you need it, pgvector when the product grows an AI feature. Fewer moving pieces means fewer things to be down at once.
Stripe because payments are the one part of the system where a bug converts directly into money moving the wrong way, and Stripe's model — idempotency keys on every request, signed webhooks, a full event log you can replay — is built for exactly the retry-heavy reality that breaks naive implementations.
Vercel because the deploy story should be boring. Push, preview URL, promote to production, roll back in one click if it's wrong. Rollback speed matters more than almost any other operational property: the fastest fix for a bad deploy is not shipping a patch, it's undoing it in thirty seconds.
None of this is exotic. That's the point. An exotic stack is a hiring problem and a debugging problem you inherit for free.
The failure modes this avoids
Three keep showing up in backends I'm asked to fix.
Payment webhook races. Payment providers guarantee at least once delivery, not exactly once. Which means your webhook handler will, eventually, receive the same event twice — sometimes seconds apart, sometimes concurrently. A handler written as "look up the order, if it isn't marked paid, mark it paid and fulfil it" will happily fulfil twice when two copies arrive at the same moment and both read the pre-update state. The fix is structural: a unique constraint on the provider's event ID, so the database rejects the duplicate rather than your code trying to notice it. I wrote the full pattern up separately in the guide on idempotent Stripe webhooks rather than repeating it here.
Auth edge cases. Session expiry mid-request. A token refreshed in one browser tab while another tab is already using the old one. A password reset link that stays valid after the password was reset. An invite accepted twice, creating two memberships. None of these show up in the demo. All of them show up once you have real users on real networks. They're handled the same way as payments: make the state transition the thing the database enforces, not the thing the code remembers to check.
Silent queue loss. A background job fails, the error is swallowed, and nothing alerts anyone. The work simply stops happening — and because nothing errored loudly, you find out from a customer. Jobs need retries with backoff, a dead-letter destination for what fails permanently, and something that actually complains when that destination isn't empty.
The common thread: correctness under retry and concurrency has to be designed in. You cannot test it in afterwards, because the conditions that trigger it don't happen on your laptop.
How this gets built
Same terms as everything else I do: $1,000 a day, flat. A full day on your backend, not an hour here and an hour there. Design, schema, API, auth, payments, deploy, and the CI that keeps it honest — all of it by me, so there's no handoff seam between the person who designed the data model and the person who has to live in it.
Backends rarely stop at their own edges. If yours has to talk to Salesforce, HubSpot, NetSuite, or a system somebody built in 2009 that only speaks CSV over SFTP, that's enterprise integrations — same principles, more counterparties, and a lot more attention paid to what happens when the other side goes down.
Tell me what you're building.
Book a call