A few years ago, building a SaaS product usually started with opening an IDE, creating a project, choosing a framework, wiring up a database, and writing everything piece by piece.
Now you can open an AI coding tool and type something like:
Build me a SaaS dashboard with authentication, subscriptions, a settings page, and an admin panel.
A few minutes later, something appears.
It has buttons.
It has pages.
It might even have a working login screen.
That feels incredible.
And it is.
But this is also where a lot of people make a dangerous assumption:
If the app looks finished, the software must be finished.
It usually isn't.
Vibe coding can get you from idea → prototype unbelievably fast.
But getting from:
prototype → reliable SaaS
is still an engineering problem.
And the difference between those two stages is much bigger than it looks.
This article is about how to use AI coding properly: how to prompt better, keep architecture simple, avoid generating a giant unmaintainable codebase, test what AI gives you, and gradually turn a vibe-coded prototype into something you can actually put in front of paying users.
First: What Do We Actually Mean by Vibe Coding?
The phrase is generally used to describe a style of development where you explain what you want in natural language and let AI produce much of the code.
Instead of thinking:
I need a controller, service, DTO, repository and validation layer.
you might think:
Users should be able to upload an invoice and see the extracted information on this page.
Then AI figures out much of the implementation.
That shift is powerful.
You start thinking more about the outcome and less about every individual line of code.
But there are two very different ways to use this approach.
Version A
You prompt:
Add authentication.
Then:
Add Stripe.
Then:
Add an admin panel.
Then:
Fix this error.
Then:
Rewrite this page.
Then:
The database isn't working. Fix it.
Then:
Now the login broke. Fix that too.
Eventually, neither you nor the AI has a clear understanding of how the system fits together.
Version B
You define the product first.
You decide what the system needs to do.
You make a few architectural decisions.
You divide the work into small parts.
Then you use AI to implement those parts one at a time.
That is still vibe coding.
But now you're using the AI inside an engineering process.
That distinction matters.
The Biggest Mistake: Prompting the Whole Product at Once
Suppose you want to build a project management SaaS.
A tempting first prompt is:
Build a complete project management platform with organizations, teams, projects, tasks, comments, notifications, billing, analytics, AI features and an admin dashboard.
The AI may generate something impressive.
But you've already created a problem.
You asked it to make dozens of decisions at the same time.
It has to guess:
- your database structure
- your permission model
- your billing logic
- your API design
- your UI structure
- your error behavior
- your organization hierarchy
- your notification architecture
- your authentication assumptions
- your naming conventions
- your testing approach
When requirements are vague, the model has to fill in the missing details itself.
That's convenient for a prototype.
It is dangerous for a real system.
The better pattern is:
Decide first. Generate second.
Use AI as a Very Fast Engineer, Not a Mind Reader
The quality of AI-generated software is strongly influenced by the quality of the context and success criteria you provide.
Modern prompt-engineering guidance increasingly emphasizes defining what success actually means before trying to optimize the wording of a prompt. Anthropic's documentation, for example, recommends starting with clear success criteria and ways to test them rather than assuming every problem is solved by better prompting.
That's exactly how I think about coding prompts.
A useful implementation prompt should answer five questions:
1. What are we building?
2. What already exists?
3. What constraints must we respect?
4. What should not change?
5. How will we know the task is finished?
That is much more useful than trying to discover some magical sentence that makes the AI produce perfect code.
A Better Prompt Structure
Here's a simple pattern I like:
CONTEXT
We have a SaaS application using:
- React
- TypeScript
- NestJS
- PostgreSQL
Authentication already exists.
Organizations already exist.
Do not change the authentication flow.
TASK
Add organization invitations.
A logged-in organization admin should be able to:
1. Enter an email address
2. Choose a role
3. Send an invitation
4. See pending invitations
5. Cancel an invitation
CONSTRAINTS
- Keep the existing architecture
- Reuse the current email service
- Do not add another state management library
- Do not change unrelated files
- Invitation tokens must expire
- Only organization admins can send invitations
ACCEPTANCE CRITERIA
- Non-admin users receive 403
- Expired tokens cannot be accepted
- An email cannot have multiple active invitations for the same organization
- Accepted invitations cannot be reused
- Tests cover the main success and failure cases
BEFORE CODING
Explain:
1. Which files you expect to change
2. Any database migration needed
3. Security edge cases
4. Your implementation plan
Wait for approval before writing code.
Notice something important.
There is nothing clever about this prompt.
That's the point.
Good software prompts often aren't clever.
They're specific.
Prompt Engineering Is Really Requirement Engineering
This is one of the most useful lessons I've learned from AI-assisted development.
A lot of what people call "prompt engineering" is really the old engineering skill of explaining requirements clearly.
Consider these two prompts.
Prompt A
Add subscriptions.
Prompt B
Add one monthly Pro subscription using Stripe. Free users can create three projects. Pro users can create unlimited projects. Subscription state must come from our database after Stripe webhook verification, not from the browser. Handle active, past_due and canceled states. Do not implement annual billing yet.
The second prompt isn't better because it contains special AI vocabulary.
It's better because you've made decisions.
And every decision you make explicitly is one less decision the AI has to invent.
This Is Where KISS Becomes Extremely Important
One of the biggest dangers of AI coding is that generating code is almost free.
Need another abstraction?
AI can create it.
Need a new service?
AI can create it.
Need three interfaces, a factory and an adapter?
AI can create those too.
Before long, you can have 20,000 lines of code solving a problem that needed 2,000.
That is why I think the KISS principle — Keep It Simple — matters even more when coding with AI.
When code is expensive to write manually, developers naturally feel some friction before adding more.
AI removes that friction.
So you need to deliberately reintroduce discipline.
Before accepting complexity, ask:
Do we actually need this yet?
Don't Build for Imaginary Scale
Imagine you're building your first SaaS with 20 users.
You probably don't need:
API Gateway
↓
Authentication Service
↓
User Service
↓
Organization Service
↓
Billing Service
↓
Notification Service
↓
Message Queue
↓
Event Bus
↓
Analytics Pipeline
You might need:
Web App
↓
Backend
↓
Database
And perhaps:
Background Worker
when you actually have background work.
That's enough.
The ability to generate microservices instantly doesn't mean your product needs microservices.
Use complexity when a real problem demands it.
Not because the AI knows how to create it.
My Rule: Start Boring
For a new SaaS, boring is usually good.
You want boring authentication.
Boring database tables.
Boring HTTP endpoints.
Boring validation.
Boring error handling.
Boring deployment.
Why?
Because your product itself already contains uncertainty.
You don't yet know:
- whether people want it
- which features they'll use
- where traffic will come from
- which workflows will matter
- what you'll need to change next month
Adding unnecessary technical novelty gives you another category of uncertainty for no benefit.
Build Vertically, Not Horizontally
Another common mistake is asking AI to build all the infrastructure first.
For example:
Step 1: Create every database table
Step 2: Create every API
Step 3: Create every service
Step 4: Create every frontend page
Step 5: Connect everything
The problem is that you can spend a long time building without having one complete working feature.
Instead, build a vertical slice.
Suppose the product has tasks.
Build this:
Create Task
↓
Validate Request
↓
Save to Database
↓
Return Result
↓
Display Task
↓
Test It
Now one feature actually works from beginning to end.
Then do:
Edit Task
Then:
Delete Task
Then:
Assign Task
This makes AI-generated development much easier to control.
If something breaks, the surface area is small.
Give Every Feature a Definition of Done
One sentence changed the way I use coding assistants:
What would prove that this feature works?
Before generating the feature, define that.
For example:
Feature: Password reset
Done means:
- user can request a reset
- existing account receives an email
- unknown emails don't reveal whether an account exists
- token expires
- token can only be used once
- password rules are enforced
- previous sessions are handled according to our policy
- success redirects to login
- tests cover invalid, expired and reused tokens
Now the AI has something concrete to build toward.
Without this, "working" often means:
The happy path worked once on my laptop.
That's not the same thing.
Happy Paths Are Where Vibe Coding Looks Best
AI is usually impressive when everything goes correctly.
User enters a value.
API succeeds.
Database responds.
Page updates.
Done.
Production SaaS gets difficult because of everything that doesn't go correctly.
What happens when:
- the request is sent twice?
- the user refreshes?
- the payment succeeds but your server times out?
- the webhook arrives twice?
- the file is too large?
- the database is unavailable?
- the external API returns malformed data?
- the email provider is down?
- two users update the same record?
- a user tries to access another organization's data?
- an API key has expired?
Real software is mostly about handling the second half of that list.
Ask AI for Failure Cases Before Asking for Code
This is a very effective pattern.
Before implementing a feature, prompt:
Do not write code yet.
Review this feature as a senior backend engineer.
List:
- expected failure cases
- security risks
- concurrency issues
- invalid states
- retry/idempotency concerns
- database constraints we may need
- edge cases I'm probably missing
Now you're using AI for something more valuable than typing code.
You're using it to expand your thinking.
Then decide which concerns actually matter.
Then implement.
Keep Your Database Rules Strong
One of the worst places to rely only on application logic is data integrity.
Suppose your app says:
A user can only have one membership in an organization.
Don't rely only on:
if (!existingMembership) {
createMembership()
}
If that rule matters, consider enforcing it in the database too.
For example:
UNIQUE(user_id, organization_id)
The AI-generated service can have a bug.
Two requests can arrive simultaneously.
A future developer can bypass the service.
The database constraint still protects the rule.
Think of important business rules as invariants.
Ask:
What must never become false?
Then enforce those rules as close to the data as reasonably possible.
Don't Let AI Invent Your Authorization Model
Authentication answers:
Who are you?
Authorization answers:
What are you allowed to do?
Those are not the same problem.
A SaaS can have perfectly working authentication while leaking another customer's data.
Imagine this endpoint:
GET /projects/123
The dangerous implementation is:
Find project 123
Return project
The real question should be:
Find project 123
↓
Which organization owns it?
↓
Does the current user belong to that organization?
↓
Does their role allow this action?
↓
Return project
Authorization should be deliberate.
Not something you hope the AI remembers to add.
OWASP's secure-code-review guidance explicitly calls out authentication, authorization, data flow, input validation, business logic, error handling and deployment configuration as areas that should be reviewed.
Security Is Not a "Later" Task
A common vibe-coding workflow is:
Make it work
↓
Launch
↓
Add security later
That's risky.
Security decisions can be architectural decisions.
OWASP's Secure-by-Design work specifically recommends thinking about security during planning and architecture instead of attempting to retrofit everything after implementation.
You don't need a giant enterprise security program for your first SaaS.
But there are basic things that should be normal from the beginning.
A Minimum Security Checklist for AI-Built SaaS
Before launch, I would at least review:
Authentication
- Are passwords handled by a trusted authentication solution?
- Are sessions/tokens expired correctly?
- Is account recovery secure?
Authorization
- Does every protected operation check ownership or role?
- Can one tenant access another tenant's resources?
Inputs
- Is user input validated on the server?
- Are file size/type limits enforced?
- Are identifiers validated?
- Are unexpected values rejected?
OWASP provides dedicated input-validation guidance and recommends validating data before your application trusts it.
Secrets
Never let AI casually put this into source code:
const stripeKey = "sk_live_..."
Use environment variables or proper secret management.
Tools such as GitHub secret scanning can detect many credentials committed to repositories, including API keys, tokens and passwords.
Dependencies
Know what packages AI added.
If a generated solution introduces eight dependencies, ask:
Why do we need each one?
Logging
Record useful failures and security events.
Do not dump passwords, tokens or sensitive customer data into logs.
OWASP's secure coding guidance recommends logging events such as validation, authentication and access-control failures while avoiding sensitive information in logs.
One of the Best AI Prompts: "Why Is This Dependency Here?"
AI tools love solving problems by installing things.
Sometimes that's correct.
Sometimes you'll suddenly notice:
{
"dependencies": {
"some-library": "...",
"another-library": "...",
"yet-another-library": "..."
}
}
and have no idea why half of them exist.
Ask:
Review all dependencies introduced by this feature.
For each dependency explain:
1. Why it is required
2. What functionality we use
3. Whether the same result can be achieved with what we already have
4. Whether it runs in production or only development
Recommend removing unnecessary dependencies.
Remember KISS.
Every dependency is something you'll eventually update, debug, audit or replace.
Don't Accept "I Fixed It" as Verification
This happens constantly with AI coding tools.
You paste an error.
AI changes something.
Then says:
The issue has been fixed.
Maybe.
But the model's confidence is not a test result.
Have it actually verify the change.
A good workflow looks more like:
Implement
↓
Type Check
↓
Lint
↓
Tests
↓
Build
↓
Review Diff
↓
Run Feature
Where possible, make these checks automatic.
Tests Become More Important When Code Is Cheap
If AI lets you produce code 5x faster, but your ability to understand that code stays the same, review becomes the bottleneck.
Tests help you manage that gap.
You don't need to generate thousands of meaningless tests.
Test the behavior that matters.
For a SaaS, that usually includes:
Business rules
Free plan can create maximum 3 projects.
Permissions
Member cannot access another organization.
Payments
Duplicate webhook does not create duplicate purchase.
State transitions
Canceled invitation cannot be accepted.
Critical integrations
Failed external request does not leave the database in an invalid state.
When asking AI to create tests, don't say:
Add tests.
Say:
Create tests for the business behavior of this feature.
Cover:
- successful path
- permission failure
- invalid input
- duplicate request
- boundary conditions
- relevant database constraints
Avoid tests that only verify implementation details.
Let AI Review AI
This sounds strange, but it works surprisingly well as an additional layer.
After implementing a feature, start a fresh review context.
Give the reviewer the requirements and the diff.
Prompt:
Act as a strict code reviewer.
Do not rewrite the code yet.
Compare this implementation against the original requirements.
Look specifically for:
- missing requirements
- authorization bugs
- data leakage
- race conditions
- unnecessary complexity
- poor error handling
- missing validation
- security problems
- code paths without tests
Rank findings by severity.
Why a fresh context?
Because the AI that created the implementation is often biased toward explaining why its own choices make sense.
A fresh review has fewer assumptions.
But don't treat AI review as a replacement for actual security tooling or human judgment. OWASP's GenAI guidance highlights the risks of overreliance on model-generated outputs, particularly when incorrect output appears credible.
Keep Changes Small
Here's another practical rule:
Never let AI change 50 files when the task should require 5.
Large AI-generated diffs are difficult to review.
And reviewability matters.
Before implementation, ask:
What is the minimum set of files required for this feature?
After implementation:
List every modified file and explain why it was necessary.
If the explanation doesn't make sense, inspect it.
Small commits are also much easier to roll back when an AI-generated change takes the project in the wrong direction.
Commit at Every Working Checkpoint
Don't spend three hours prompting and then make one enormous commit:
finished app
Do something closer to:
feat: add organization invitation schema
feat: add invitation creation endpoint
test: cover invitation permissions
feat: add invitation acceptance flow
feat: add invitation UI
Now if the UI generation goes badly, your working backend doesn't disappear with it.
Git becomes your safety net.
Use it aggressively.
AI Needs Context, But Not Your Entire Repository Every Time
There's a common reaction when AI makes mistakes:
Give it more context.
Sometimes that's correct.
But more context isn't automatically better.
A task involving subscription cancellation probably needs:
- subscription model
- billing service
- relevant webhook handler
- authorization rules
- existing tests
It probably doesn't need:
- your landing page
- notification templates
- analytics dashboard
- unrelated admin components
The goal is not maximum context.
It's relevant context.
Create a Small Project Guide for the AI
One of the easiest ways to improve consistency is to maintain a short project document.
Something like:
# Project Rules
Architecture:
- React frontend
- NestJS API
- PostgreSQL
- REST endpoints
General:
- Prefer existing utilities
- Don't add packages unless necessary
- Keep services small
- Validate requests server-side
- Never expose database models directly
Multi-tenancy:
- All customer resources belong to an organization
- Every protected query must be organization-scoped
Database:
- snake_case columns
- UUID primary keys
- timestamps stored in UTC
Frontend:
- Reuse existing components
- Don't duplicate API logic
- Handle loading, empty and error states
Testing:
- Test business behavior
- Test authorization for protected resources
The important thing is that this document stays short enough to remain useful.
Don't turn it into a 100-page constitution no one reads.
Again:
KISS.
Separate Product Decisions From Coding Decisions
Suppose you're adding file uploads.
These are product decisions:
- What file types do we support?
- Maximum size?
- Can free users upload?
- How many files?
- Can files be deleted?
- Are files private?
These are implementation decisions:
- Which storage service?
- Presigned URLs or server upload?
- Database schema?
- Retry behavior?
- Thumbnail generation?
Don't ask AI to silently make product decisions while it's writing implementation code.
Make the product decisions first.
Then ask it to implement them.
Don't Build Features Because AI Makes Them Easy
This is an underrated problem.
Before AI, adding a feature had a cost.
So you asked:
Is this worth building?
Now you can type:
Add a team chat.
Ten minutes later, there's a chat.
Then:
Add AI summaries.
Then:
Add reactions.
Then:
Add threads.
Congratulations.
Your invoicing SaaS now contains Slack.
Feature generation is cheap.
Product complexity is not.
Every feature creates:
- UI complexity
- bugs
- support requests
- documentation
- maintenance
- permissions
- database state
- testing requirements
So keep asking:
Does this make the core job easier?
If not, don't build it simply because AI can.
Build the Smallest Complete Product
I like this distinction:
Small product
Has very few features.
Incomplete product
Has features that aren't reliable.
You want the first one.
Your first production version can be:
Sign Up
↓
Create Project
↓
Perform Core Job
↓
Save Result
↓
Pay
That's okay.
What matters is that those five things actually work.
Your SaaS Is More Than the UI
Vibe-coded apps often look great very quickly.
That's because UI is visible.
You immediately notice:
- beautiful cards
- gradients
- animations
- dashboards
- charts
Users also care about invisible things.
They care that:
Their payment isn't charged twice.
They care that:
Their data doesn't disappear.
They care that:
Another customer cannot see their files.
They care that:
Password reset actually arrives.
They care that:
The app still works next Tuesday.
Production quality is mostly invisible until something goes wrong.
Don't Forget Observability
Imagine a user tells you:
I tried generating my report yesterday and it didn't work.
Can you answer:
- which request failed?
- which user?
- when?
- what error occurred?
- which external service was called?
- how long did it take?
- did it retry?
- did the database write succeed?
If not, debugging production is going to be painful.
At minimum, think about:
- application logs
- error tracking
- request IDs
- external API failures
- important background jobs
- payment/webhook failures
- performance of critical endpoints
OWASP also recommends structured application logging as part of secure software operation.
Have a Failure Strategy for External APIs
Modern SaaS products depend on dozens of external systems.
Payments.
Email.
Storage.
AI APIs.
Analytics.
Search.
What happens when one is unavailable?
Don't write code that assumes:
Call external service
↓
It always works
Think:
Call external service
↓
Success? ───── Yes ─────→ Continue
│
No
↓
Can retry safely?
│
├── Yes → Retry with limit
│
└── No → Record failure
↓
Recover/notify
And be careful with retries.
Retrying:
GET report
is different from blindly retrying:
CHARGE CUSTOMER $500
Idempotency matters.
Don't Let Your AI Tool Perform Unlimited Surgery
AI coding tools can be incredibly proactive.
That's useful until you ask:
Fix this build error.
and the solution includes:
- changing your framework version
- replacing your authentication package
- rewriting 12 files
- deleting tests
- changing environment variables
- modifying build configuration
The build works.
But you now have a different application.
Give explicit boundaries:
Fix this error with the smallest possible change.
Do not:
- change package versions
- change architecture
- remove tests
- suppress TypeScript errors
- use `any`
- modify unrelated modules
If the fix requires one of these, stop and explain why.
This single pattern saves a lot of trouble.
"Don't Hide the Error" Is Another Great Rule
Sometimes AI fixes errors like this:
try {
await doImportantThing();
} catch {
// ignore
}
Technically, the error disappeared.
So did your ability to know something failed.
Similarly:
value as any
can make TypeScript stop complaining.
That doesn't necessarily mean the program is correct.
Tell your coding assistant:
Fix the root cause. Do not silence the symptom.
Ask for Explanations Where the Risk Is High
You don't need the AI to explain every CSS class.
But if it generates:
- payment logic
- authorization
- cryptography
- database migrations
- background processing
- caching
- concurrency handling
ask it to explain the implementation before accepting it.
If you cannot understand a critical piece of generated code, you now have code you cannot confidently maintain.
That is technical debt on day one.
Production Is a Checklist, Not a Feeling
Your application isn't production-ready because:
It feels finished.
Create a checklist.
Here's a simple starting point.
Before Launching an AI-Built SaaS
Product
- [ ] Core user journey works from start to finish
- [ ] Empty states make sense
- [ ] Error states are understandable
- [ ] User can recover from common mistakes
- [ ] Pricing and limits match actual behavior
Authentication
- [ ] Sign up works
- [ ] Sign in works
- [ ] Sign out works
- [ ] Password/account recovery works
- [ ] Sessions expire correctly
Authorization
- [ ] Tenant data is isolated
- [ ] Roles are enforced server-side
- [ ] Sensitive endpoints require permission checks
- [ ] Object ownership is verified
Database
- [ ] Important relationships have constraints
- [ ] Required uniqueness is enforced
- [ ] Migrations work against a clean database
- [ ] Backups exist
- [ ] Destructive changes have been reviewed
API
- [ ] Inputs are validated
- [ ] Errors have predictable formats
- [ ] Pagination exists where needed
- [ ] Rate limits exist where abuse matters
- [ ] Duplicate requests are safe where necessary
Payments
- [ ] Webhooks are verified
- [ ] Duplicate webhooks are handled
- [ ] Failed payments have defined behavior
- [ ] Subscription cancellation is tested
- [ ] Production and test credentials are separated
Security
- [ ] No secrets committed to repository
- [ ] Dependencies reviewed
- [ ] Input validation exists
- [ ] File uploads are restricted
- [ ] Authorization tested
- [ ] Sensitive information isn't logged
Automated code scanning can provide another layer here. GitHub's CodeQL tooling, for example, is designed to analyze code for potential vulnerabilities and coding errors.
Reliability
- [ ] Errors are tracked
- [ ] Logs are useful
- [ ] External API failures are handled
- [ ] Background jobs have retry/failure behavior
- [ ] Critical actions are observable
Testing
- [ ] Main user flow tested
- [ ] Critical business rules tested
- [ ] Authorization tested
- [ ] Payment behavior tested
- [ ] Production build succeeds
Deployment
- [ ] Environment variables documented
- [ ] Database migration strategy defined
- [ ] Rollback is possible
- [ ] Domain/SSL configured
- [ ] Production configuration differs safely from development
A Simple AI Development Workflow That Actually Works
If I were starting a SaaS today with AI, I wouldn't stop vibe coding.
I would structure it.
Something like this:
Idea
↓
Define User Problem
↓
Define Core Workflow
↓
Reduce Scope
↓
Choose Simple Architecture
↓
Define Data Model
↓
Build One Vertical Slice
↓
Test
↓
Review
↓
Commit
↓
Build Next Slice
↓
Security Review
↓
Production Checklist
↓
Deploy
↓
Observe
↓
Improve
AI can participate in nearly every step.
But you remain responsible for the system.
The Prompt Loop I Recommend
For each meaningful feature:
Step 1 — Plan
Here is the feature.
Do not code yet.
Explain the simplest implementation that fits our current architecture.
List:
- files that need to change
- data changes
- API changes
- edge cases
- security concerns
- tests required
Prefer the smallest solution.
Step 2 — Challenge the plan
Can this be simpler?
Identify anything that is:
- premature abstraction
- unnecessary dependency
- overengineering
- solving a future problem we don't currently have
Step 3 — Implement
Implement only the approved plan.
Do not modify unrelated code.
Keep the implementation small.
Follow existing project conventions.
Step 4 — Verify
Run or describe the required verification:
- type checking
- linting
- tests
- build
Do not claim the feature works unless verification succeeds.
Step 5 — Review
Review the final diff against the requirements.
Find:
- missing behavior
- bugs
- security issues
- authorization issues
- unnecessary complexity
- missing tests
Step 6 — Commit
Then move on.
This feels slower than:
Build everything.
In practice, it can save enormous amounts of cleanup later.
A Bad Prompt vs. a Production Prompt
Let's take a real example.
Suppose you're building an invoice upload feature.
Vibe Prompt
Add invoice uploads with AI.
You may get something visually impressive.
But what does that actually mean?
Now compare it with this:
Production-Oriented Prompt
We need to add invoice upload to the existing SaaS.
USER FLOW
1. User opens the invoices page.
2. User uploads a PDF or image.
3. Maximum file size is 10 MB.
4. File is stored privately.
5. A background job extracts invoice data.
6. UI shows processing state.
7. When complete, user sees:
- vendor
- invoice number
- invoice date
- currency
- subtotal
- tax
- total
- line items
8. User must confirm/edit extracted values before saving.
SECURITY
- Users can only access invoices from their own organization.
- Storage objects must not be public.
- Validate MIME type and size server-side.
- Do not trust the filename.
- Do not expose storage credentials.
FAILURE BEHAVIOR
- Unsupported file → clear validation error.
- Extraction failure → mark invoice as failed.
- External AI timeout → retry up to defined limit.
- Duplicate job execution must not create duplicate invoices.
CONSTRAINTS
- Reuse the current storage service.
- Reuse the current background worker.
- Do not add a new queue library.
- Keep extraction logic separate from invoice persistence.
BEFORE IMPLEMENTATION
Propose the database changes and API endpoints first.
That's the same product idea.
But the quality of the engineering conversation is completely different.
You Don't Need to Stop Vibe Coding
This is important.
I don't think the lesson is:
Vibe coding is bad.
It isn't.
For exploration, AI-generated code is one of the most powerful prototyping tools we've ever had.
Want to test whether an interaction feels right?
Generate it.
Want to see three dashboard layouts?
Generate them.
Want to test a product idea this afternoon instead of next month?
Generate it.
The mistake is assuming that the same process used for rapid exploration is automatically enough for operating production software.
Different stage.
Different discipline.
Think in Two Modes
I find it useful to separate development into two modes.
Exploration Mode
Goal:
Learn quickly.
Optimize for:
- speed
- experimentation
- prototypes
- disposable code
- UI ideas
- testing assumptions
Breaking things is acceptable.
Then switch.
Production Mode
Goal:
Make this dependable.
Optimize for:
- simplicity
- correctness
- maintainability
- security
- tests
- observability
- predictable behavior
The biggest mistake is never switching modes.
AI Makes Engineering Judgment More Valuable, Not Less
If writing syntax becomes easier, then the bottleneck moves.
The difficult questions become:
What should we build?
What should we not build?
What is the simplest architecture?
Which data rules matter?
Where are the trust boundaries?
Which failure cases are dangerous?
What must be tested?
When is this actually ready?
Those are engineering questions.
AI can help answer them.
But someone still has to care about the answers.
There's a Bigger Shift Happening
We're gradually moving from:
Developer writes every line
toward:
Developer defines intent
↓
AI proposes solution
↓
Developer constrains solution
↓
AI implements
↓
Tools verify
↓
Developer reviews outcome
That changes what a good developer looks like.
Knowing syntax still matters.
Understanding systems matters more.
What We've Been Exploring Ourselves
This way of working has also influenced some of the ideas we're exploring with Xenition.
The interesting problem isn't simply giving someone another place to generate code.
It's making the surrounding workflow easier:
Understand the job
↓
Use the right tools
↓
Generate
↓
Review
↓
Verify
↓
Continue working
Coding is only one part of building a product.
There are also specifications, research, forms, data, content, documents, integrations, automation and the rest of the work surrounding the code.
I think the more capable AI becomes, the more useful it will be to connect those pieces instead of treating every task as an isolated prompt.
But regardless of which tools you use, the principle is the same:
AI should reduce execution friction without removing engineering discipline.
The KISS Method for AI Development
If you remember nothing else from this article, use this:
Keep the product small.
Solve one real problem well.
Keep the architecture small.
Don't design for imaginary scale.
Keep prompts specific.
Make decisions instead of asking AI to guess.
Keep changes small.
Small changes are easier to review and revert.
Keep dependencies small.
Every dependency has a maintenance cost.
Keep context relevant.
More isn't automatically better.
Keep tests focused.
Test business behavior, permissions and critical failures.
Keep responsibility human.
AI can generate code.
You still own what reaches production.
Final Thought
Vibe coding changes the economics of software development.
An idea that once required weeks of implementation can sometimes become interactive in hours.
That's a huge shift.
But speed introduces a strange new problem:
We can now create complexity faster than we can understand it.
So the answer isn't to stop using AI.
The answer is to pair AI speed with engineering discipline.
Use the model to generate.
Use clear requirements to constrain it.
Use KISS to control complexity.
Use tests to verify behavior.
Use security reviews to protect users.
Use logs to understand production.
Use Git so mistakes are reversible.
And most importantly:
Don't ask AI to build your entire SaaS.
Ask it to help you build the next small, well-defined, verifiable piece.
Then repeat.
That's how vibe coding stops being a demo trick.
And starts becoming a serious way to build software.
Vibe coding can get you moving.
Engineering is what lets you keep moving after real users arrive.

Top comments (0)