DEV Community

Cover image for Vibe Coding Gets You an MVP. Engineering Gets You a SaaS.
Darun Karas Abir
Darun Karas Abir

Posted on

Vibe Coding Gets You an MVP. Engineering Gets You a SaaS.

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.
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

You might need:

Web App
   ↓
Backend
   ↓
Database
Enter fullscreen mode Exit fullscreen mode

And perhaps:

Background Worker
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Now one feature actually works from beginning to end.

Then do:

Edit Task
Enter fullscreen mode Exit fullscreen mode

Then:

Delete Task
Enter fullscreen mode Exit fullscreen mode

Then:

Assign Task
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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()
}
Enter fullscreen mode Exit fullscreen mode

If that rule matters, consider enforcing it in the database too.

For example:

UNIQUE(user_id, organization_id)
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

The dangerous implementation is:

Find project 123
Return project
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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_..."
Enter fullscreen mode Exit fullscreen mode

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": "..."
  }
}
Enter fullscreen mode Exit fullscreen mode

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.
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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.
Enter fullscreen mode Exit fullscreen mode

Permissions

Member cannot access another organization.
Enter fullscreen mode Exit fullscreen mode

Payments

Duplicate webhook does not create duplicate purchase.
Enter fullscreen mode Exit fullscreen mode

State transitions

Canceled invitation cannot be accepted.
Enter fullscreen mode Exit fullscreen mode

Critical integrations

Failed external request does not leave the database in an invalid state.
Enter fullscreen mode Exit fullscreen mode

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.
Enter fullscreen mode Exit fullscreen mode

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.
Enter fullscreen mode Exit fullscreen mode

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?
Enter fullscreen mode Exit fullscreen mode

After implementation:

List every modified file and explain why it was necessary.
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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.
Enter fullscreen mode Exit fullscreen mode

They care that:

Their data doesn't disappear.
Enter fullscreen mode Exit fullscreen mode

They care that:

Another customer cannot see their files.
Enter fullscreen mode Exit fullscreen mode

They care that:

Password reset actually arrives.
Enter fullscreen mode Exit fullscreen mode

They care that:

The app still works next Tuesday.
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Think:

Call external service
       ↓
Success? ───── Yes ─────→ Continue
   │
   No
   ↓
Can retry safely?
   │
   ├── Yes → Retry with limit
   │
   └── No  → Record failure
              ↓
          Recover/notify
Enter fullscreen mode Exit fullscreen mode

And be careful with retries.

Retrying:

GET report
Enter fullscreen mode Exit fullscreen mode

is different from blindly retrying:

CHARGE CUSTOMER $500
Enter fullscreen mode Exit fullscreen mode

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.
Enter fullscreen mode Exit fullscreen mode

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
}
Enter fullscreen mode Exit fullscreen mode

Technically, the error disappeared.

So did your ability to know something failed.

Similarly:

value as any
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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.
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Step 3 — Implement

Implement only the approved plan.

Do not modify unrelated code.

Keep the implementation small.

Follow existing project conventions.
Enter fullscreen mode Exit fullscreen mode

Step 4 — Verify

Run or describe the required verification:

- type checking
- linting
- tests
- build

Do not claim the feature works unless verification succeeds.
Enter fullscreen mode Exit fullscreen mode

Step 5 — Review

Review the final diff against the requirements.

Find:
- missing behavior
- bugs
- security issues
- authorization issues
- unnecessary complexity
- missing tests
Enter fullscreen mode Exit fullscreen mode

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.
Enter fullscreen mode Exit fullscreen mode

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.
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

toward:

Developer defines intent
        ↓
AI proposes solution
        ↓
Developer constrains solution
        ↓
AI implements
        ↓
Tools verify
        ↓
Developer reviews outcome
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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)