Founder & Lead Engineer, RAITHub
Don't build an MVP by making decisions that are cheap in week two and expensive in month six. The nine that sink first versions most often: splitting into services too early, letting screens design the database, storing money as floats, checking permissions only in the UI, writing your own login, trusting payment redirects, editing production data by hand, doing slow work inside requests, and shipping blind.
This is the engineering companion to two earlier posts. Five MVP mistakes founders make covers the product side: over-scoping, skipping QA, no analytics, no user feedback. How to validate your MVP covers what to test before any code exists. This post assumes you have validated the idea and are about to build. It is about the technical choices a founder often never sees being made, and which an agency or freelancer may make for you by default.
Why do engineering decisions matter so much in a first version?
Because an MVP is cheap to change in its features and expensive to change in its foundations. A button, a page or a pricing tier can be replaced in an afternoon. The shape of the database, where permissions are enforced and how money moves are load-bearing: every later feature is built on top of them, so changing them means touching everything at once.
The test for each decision below is simple. Ask: "If this turns out wrong, is it a one-file fix or a migration of live data?" The first kind you can safely postpone. The second kind deserves an hour of thought now, even in an MVP.
The nine decisions at a glance
| # | The decision that sinks you | What it costs later | The cheaper default |
|---|---|---|---|
| 1 | Splitting into microservices on day one | Cross-service changes, distributed bugs, extra hosting | One deployable app with clear internal modules |
| 2 | Letting the screens design the database | Duplicated data that drifts; painful reporting | Model the business entities first, then build screens on them |
| 3 | Money as floats, times without time zones | Rounding errors in invoices; bookings off by hours | Integer minor units or numeric; timestamptz in UTC |
| 4 | Checking permissions only in the UI | Any user can read or change others' data via the API | Check on the server for every request; add row-level security for tenants |
| 5 | Writing your own login system | Password resets, sessions and lockouts become your security problem | A maintained auth library or provider |
| 6 | Trusting the payment success redirect | Paid customers with no access, or access with no payment | Webhooks as the source of truth, processed idempotently |
| 7 | Editing the production database by hand | Environments drift; nobody can rebuild the schema | Versioned migrations in the repository, run by the deploy |
| 8 | Doing slow work inside the request | Timeouts, duplicate emails, half-finished jobs | A simple job queue with retries |
| 9 | Shipping with no error tracking or tested backups | Users find bugs first; a bad restore finds you last | Error tracking, structured logs and one rehearsed restore before launch |
1. Should an MVP use microservices?
Almost never. A new product does not yet know where its natural boundaries are, and services freeze boundaries early. Martin Fowler's Monolith First makes the case plainly: almost all the successful microservice stories he had heard of started with a monolith that grew too big and was broken up, and systems built as microservices from scratch often ended up in serious trouble. He adds that moving functionality between services is much harder than moving it inside a monolith.
The cheaper default is a modular monolith: one codebase and one deploy, organised into modules such as billing, accounts and orders, which only talk to each other through small, deliberate functions. If one module later needs to become a service, the seam is already there.
2. What goes wrong when the screens design the database?
Screen-first schemas copy data wherever a page needs it. The customer's address lives on the order, the invoice and the profile, each edited separately, and within months they disagree. Reporting then becomes an archaeology project.
Start from the nouns of the business instead: who the customers are, what they buy, what states an order moves through. Give each fact one home, link records with foreign keys, and let the database reject bad data with constraints rather than hoping every screen validates it. This is an hour of whiteboard work, and it is the decision that is hardest to reverse once real data exists.
3. How should an MVP store money and time?
Store money as an integer number of minor units, such as cents, or as a fixed-precision numeric, never as a floating-point number. Floats cannot represent most decimal amounts exactly, so totals drift by fractions of a cent and invoices stop matching payments. Store the currency beside every amount.
Store time as timestamptz in UTC and convert to the user's time zone only for display. A booking stored as a local time without a zone will be wrong for someone the day your first overseas customer signs up. A minimal PostgreSQL table that gets both right, and lets the database enforce the rules:
-- One fact, one home: prices in minor units, times in UTC, rules in the schema.
create table orders (
id bigint generated always as identity primary key,
customer_id bigint not null references customers (id),
status text not null default 'pending'
check (status in ('pending', 'paid', 'refunded', 'cancelled')),
total_minor bigint not null check (total_minor >= 0), -- 1999 = 19.99
currency char(3) not null, -- ISO 4217, e.g. 'USD'
created_at timestamptz not null default now(),
paid_at timestamptz
);
create index orders_customer_id_idx on orders (customer_id);
Every line in that table is a bug that cannot happen: no negative totals, no unknown statuses, no order pointing at a customer who does not exist.
4. Is it enough to hide buttons users should not press?
No. Hiding a button hides nothing from someone who calls your API directly, and every browser shows them how. Every request must be checked on the server: is this user signed in, and are they allowed to touch this specific record? The common failure is an endpoint like /api/orders/123 that checks the first question and forgets the second.
If the product serves several companies from one database, add a second layer in the database itself. PostgreSQL's row-level security lets you write policies that filter every query by tenant, and when it is enabled with no policy, the default is to deny access to every row. RAITHub's row-level security guide shows the pattern with tests; it is how Sundor Skin, a B2B wholesale platform RAITHub built, isolates data across 146 PostgreSQL tables with 12 staff roles.
5. Should you write your own login system?
Not for an MVP. Login looks like two fields and a button. Underneath are password hashing, reset tokens that expire, session revocation, email verification, rate limits on guessing, and account-recovery edge cases. Each is a security problem you then own.
Use a maintained library or a hosted provider, keep your own users table linked to it, and spend the saved week on the feature customers are paying for. This site, for example, uses NextAuth rather than a home-grown session system, and still needed rate limiting on its forms, which RAITHub built without adding Redis.
6. Why can't you trust the payment success page?
Because the customer's browser may never reach it: they close the tab, lose signal or the redirect fails. If access is granted on the redirect, some paying customers get nothing. If it is granted on a URL anyone can visit, some non-paying visitors get everything.
Grant access from the payment provider's webhook, verify its signature, and process each event exactly once, because providers retry. For calls you make to the provider, Stripe supports idempotency keys on every POST request, so a retried request after a network error does not charge twice; Stripe suggests V4 UUIDs and notes keys can be pruned after 24 hours. When a webhook returns 200 but the subscription still looks wrong, this checklist walks through out-of-order events. PropDesk, a property management platform RAITHub built, collects rent through Stripe behind a suite of 1,024 tests.
7. What is wrong with editing the production database directly?
It works once and breaks the second time. A column added by hand in production does not exist in staging or on a new developer's laptop, so the next deploy fails, or worse, succeeds against a different schema. After a few months nobody can rebuild the database from scratch, which also means nobody can prove a backup will restore.
The default is versioned migration files in the repository, applied by the deploy in the same order everywhere. If your first version came from an AI builder, stop editing production by hand explains how to move a Supabase project onto migrations without losing data.
8. What counts as "slow work" in a request?
Anything that talks to a slow or unreliable outside service, or does more than a moment of computation: sending email, generating PDFs, resizing images, calling an AI model, syncing to a CRM. Done inside the request, it makes pages slow, hits hosting timeouts, and leaves work half done when it fails. A user who clicks again sends the email twice.
Put a row in a jobs table, return to the user immediately, and let a worker process the job with retries. An MVP does not need a message broker for this; a PostgreSQL table and a scheduled worker are enough until volume proves otherwise.
9. What does "shipping blind" mean, and how do you avoid it?
It means launching without a way to see what fails. The first sign of a broken checkout is a customer email, days later. Before launch, an MVP needs three things: error tracking that alerts someone, logs that include a request or user ID so a complaint can be traced, and a backup that has actually been restored once into a scratch database. An untested backup is a hope.
This is different from product analytics, which the product-side post covers. Analytics tells you what users do. Error tracking tells you what the product did to them.
Which of these can an MVP safely skip?
Most of the rest. Scale work is the classic premature investment: caching layers, read replicas, Kubernetes, multi-region hosting and a design system can all wait for evidence. So can most admin tooling beyond what support needs on day one. The rule from the top of this post applies: postpone anything that is a one-file fix later, and decide now on anything that becomes a migration of live data. For how these choices affect the schedule, see how long an MVP takes to build.
Why RAITHub for an MVP build?
- The foundations are decided with you, in writing. The data model, permission model and payment flow are agreed before the build, as part of the fixed written quote.
- Tests from the first commit. PropDesk runs 1,024 tests; Sundor Skin 530+; TheSkinProof, the founder's own marketplace venture rather than a client project, 750+ across 217 API endpoints.
- Speed without skipping the load-bearing parts. BlockEstate, a multi-tenant listing platform, went from start to MVP in 6 weeks.
- You own it. Your repository, your cloud account and IP assigned to you, with an NDA as standard. See the MVP development service for what a build includes.
When you don't need us
- You have not validated the idea yet. None of the nine decisions matters for a landing page or a clickable prototype. Run the experiments first.
- A no-code tool fits the job. For internal tools and early demand tests, a hosted builder avoids most of these decisions entirely.
- You have a capable technical co-founder. Hand them this list as a checklist and keep the money.
For a first budget range before you talk to anyone, try the MVP cost estimator.
Sources checked on 29 September 2026.
If you are about to start a build, or suspect one of these decisions is already baked into yours, book the free 15-minute technical audit.
Frequently asked questions
What is the most expensive engineering mistake in an MVP?
Usually the data model, because every later feature is built on it and fixing it means migrating live data. Permissions enforced only in the UI come a close second, because the fix touches every endpoint at once.
Should an MVP be built as a monolith?
In most cases, yes: one codebase and one deploy, organised into clear internal modules. Service boundaries are hard to move once drawn, and a new product rarely knows where they belong. Split a module out when evidence says it needs to scale or deploy on its own.
Do I need automated tests in an MVP?
For the critical paths, yes: sign-up and login, permissions, payments and the core workflow. They are what lets you change the product quickly after launch without breaking the parts that earn money.
Is it fine to use floats for prices if I round them?
No. Rounding hides the error in one place and it reappears in totals, refunds and tax. Store integer minor units such as cents, or a fixed-precision numeric type, with the currency beside every amount.
What should an MVP have before launch besides features?
Error tracking that alerts someone, logs you can trace a complaint through, versioned database migrations, and a backup you have restored at least once. Together they take days, not weeks, to set up.
How do I know whether my agency is making these decisions well?
Ask to see the database schema, where permissions are checked, how payment webhooks are handled and how migrations run. A good team can answer each in a few minutes and show you the code.
Related posts
Ready to discuss your project?
Book a free 15-minute technical audit with our engineering team.