Agentic Coding

AI-Assisted SaaS Development: A Practical Guide

Build SaaS products faster with AI-assisted development: use Claude Code and Cursor to scaffold features, write tests, generate migrations, and ship reliably.

May 31, 2026 10 min read
AI-Assisted SaaS Development: A Practical Guide

AI-assisted SaaS development isn't about replacing your engineering team. It's about eliminating the parts of building SaaS that are high-effort but low-judgment. Scaffolding a CRUD resource, writing migration files, generating API client code, adding test cases for known edge cases: these tasks follow patterns you've done a hundred times. An AI coding tool should handle the pattern execution while your engineers focus on the decisions that actually require product intuition.

At Laxaar, we've shipped SaaS products with AI-assisted workflows across the full stack. The gain isn't uniform: some tasks see 5x speedup, others see none. This tutorial covers the areas where AI assistance genuinely pays off in SaaS development, and how to set up the workflow so it produces code you'd actually ship.

Prerequisites: A SaaS project (we'll use a Next.js App Router + Prisma + PostgreSQL stack), Claude Code CLI installed (npm install -g @anthropic-ai/claude-code), and Cursor 0.45+. Node.js 20+.

What you'll build

Step 1: Configure AI tooling

Good AI assistance in a SaaS project starts with giving the tools accurate context about your stack. Without this, you'll spend more time correcting the output than you saved generating it.

For Claude Code, create a CLAUDE.md at your project root. This file is injected into every Claude Code session:

# Project context

This is a multi-tenant SaaS application. Stack: Next.js 15 (App Router), TypeScript strict,
Prisma ORM with PostgreSQL, NextAuth.js for authentication, Stripe for billing, Vitest for tests.

## Conventions
- Server actions live in `src/app/actions/`. They're async functions marked with `"use server"`.
- API routes live in `src/app/api/`. Each route exports named handlers (GET, POST, etc.).
- Database models: Prisma schema in `prisma/schema.prisma`. Run `npx prisma generate` after schema changes.
- Every model has a `tenantId` field (UUID). Always filter by `tenantId` in queries — never return cross-tenant data.
- Error handling: throw `AppError` (from `src/lib/errors.ts`) for known errors. Never throw raw strings.

## What to check before editing a file
1. Read the file first.
2. Check if the pattern already exists elsewhere in the codebase.
3. Run `npm run typecheck` after changes.

For Cursor, the equivalent goes in .cursor/rules. Keep both files consistent; they serve the same purpose for different tools.

The multi-tenant tenantId note is non-negotiable. We've seen AI tools generate perfectly formatted Prisma queries that forget tenant isolation entirely. One bad query and you have a data leak. Explicit rules prevent this.

Step 2: Scaffold a feature

Let's scaffold a "Projects" feature: a Prisma model, server actions, and a basic page. Without AI assistance, this is 30-60 minutes of copy-paste and adaptation. With Claude Code:

claude "Add a Projects feature to this SaaS app. Read the existing User and Organization models in prisma/schema.prisma, then:
1. Add a Project model with: id, tenantId, name, description (optional), status (enum: ACTIVE/ARCHIVED), createdById, createdAt, updatedAt
2. Create server actions in src/app/actions/projects.ts: createProject, listProjects, updateProject, archiveProject
3. Create a basic page at src/app/(dashboard)/projects/page.tsx that lists projects
4. Run npm run typecheck after each file and fix any errors"

Claude Code will read the existing schema, match your naming conventions, and produce output that fits the project. The instruction to run typecheck after each file is critical: it forces the agent to verify its own output rather than moving on with silent errors.

Expected output structure:

prisma/schema.prisma          (updated — Project model added)
src/app/actions/projects.ts   (new — 4 server actions)
src/app/(dashboard)/projects/page.tsx  (new — list page)

Run the migration:

npx prisma migrate dev --name add-projects

Then verify the types pass:

npm run typecheck
# Should report 0 errors

This is where the discipline matters: don't skip the typecheck. AI-generated Prisma queries frequently have minor type mismatches that only show up under strict TypeScript. Fix them now, not when a user hits a runtime error.

Step 3: Database migrations

Migrations are one of the best use cases for AI assistance in SaaS development. The schema changes are declarative, the migration SQL is predictable, and mistakes are costly (data loss, downtime). AI can generate the migration, and you review the SQL before running it.

Scenario: you need to add a plan field to the Organization model with a default value, and backfill existing rows.

claude "In prisma/schema.prisma, add a 'plan' field to the Organization model:
- Type: String
- Default: 'free'
- Not optional
Read the current schema first, then make the change.
After editing the schema, generate a migration with: npx prisma migrate dev --name add-org-plan --create-only
Show me the generated SQL before we run it."

The --create-only flag generates the SQL without running it. Claude Code will show you the migration file. Review it:

-- Migration: add-org-plan
ALTER TABLE "Organization" ADD COLUMN "plan" TEXT NOT NULL DEFAULT 'free';

That's safe. DEFAULT 'free' means the column is backfilled automatically. If the AI had generated NOT NULL without a default, you'd have a migration that fails on a table with existing rows. Catching this before running it is the point.

Once you've reviewed and approved:

npx prisma migrate deploy

For production, always run migrate deploy (not migrate dev) and always review migration files before deploying. AI helps you write them; you own the review.

Step 4: Writing tests

Tests are where AI assistance is underused in SaaS development. Most developers use AI to generate feature code but write tests manually. That's backwards. Tests follow patterns (arrange, act, assert) and AI excels at pattern application.

claude "Read src/app/actions/projects.ts and src/lib/errors.ts, then write a test file at src/app/actions/projects.test.ts using Vitest.
Cover these cases:
1. createProject — success, missing name (should throw AppError), wrong tenantId format
2. listProjects — returns only projects for the given tenantId
3. archiveProject — sets status to ARCHIVED, doesn't delete the record
Use a mock Prisma client (not the real database). Follow the pattern in any existing .test.ts files you find."

The "follow the pattern in any existing .test.ts files" instruction is important: it prevents the agent from inventing a test setup that doesn't match what your project already uses.

For the tenant isolation test, the agent should produce something like:

describe("listProjects", () => {
  it("returns only projects belonging to the given tenant", async () => {
    const tenantA = "tenant-a-uuid";
    const tenantB = "tenant-b-uuid";

    mockPrisma.project.findMany.mockResolvedValue([
      { id: "p1", tenantId: tenantA, name: "Alpha", status: "ACTIVE" },
    ]);

    const result = await listProjects({ tenantId: tenantA });

    expect(mockPrisma.project.findMany).toHaveBeenCalledWith({
      where: { tenantId: tenantA },
    });
    expect(result).toHaveLength(1);
    expect(result[0].tenantId).toBe(tenantA);
  });
});

Run the tests:

npx vitest run src/app/actions/projects.test.ts

If the agent used wrong import paths or the wrong mock setup, fix those specifically. Don't regenerate the whole file; targeted edits are faster.

Laxaar's opinionated take: the test file is more valuable than the feature file in SaaS development. The feature code will change; the tests document the contract. Use AI to get the first draft fast, then invest human time in making the tests thorough.

Step 5: API and client code

SaaS products almost always have an external API, for webhooks, integrations, or mobile clients. Generating API route handlers and matching client SDKs is tedious and error-prone to do manually. It's a near-perfect AI task.

claude "Read the Projects server actions in src/app/actions/projects.ts.
Create a REST API for Projects at src/app/api/v1/projects/route.ts:
- GET /api/v1/projects — list projects, authenticate via Bearer token, return JSON
- POST /api/v1/projects — create project, validate body with zod
Also generate a TypeScript client in src/lib/api-client/projects.ts with typed functions: getProjects(), createProject(data).
Use fetch, no external dependencies."

Expected output for the API route:

// src/app/api/v1/projects/route.ts
import { NextRequest, NextResponse } from "next/server";
import { z } from "zod";
import { listProjects, createProject } from "@/app/actions/projects";
import { authenticateApiToken } from "@/lib/auth";

export async function GET(req: NextRequest) {
  const auth = await authenticateApiToken(req);
  if (!auth.ok) return NextResponse.json({ error: "Unauthorized" }, { status: 401 });

  const projects = await listProjects({ tenantId: auth.tenantId });
  return NextResponse.json({ data: projects });
}

const CreateProjectSchema = z.object({
  name: z.string().min(1).max(100),
  description: z.string().optional(),
});

export async function POST(req: NextRequest) {
  const auth = await authenticateApiToken(req);
  if (!auth.ok) return NextResponse.json({ error: "Unauthorized" }, { status: 401 });

  const body = await req.json();
  const parsed = CreateProjectSchema.safeParse(body);
  if (!parsed.success) {
    return NextResponse.json({ error: parsed.error.flatten() }, { status: 400 });
  }

  const project = await createProject({ ...parsed.data, tenantId: auth.tenantId });
  return NextResponse.json({ data: project }, { status: 201 });
}

Review the auth pattern carefully. The agent should be calling your existing authenticateApiToken function, not inventing a new auth approach. If it invents something, tell it explicitly which function to use.

Common pitfalls

AI-generated code that ignores tenant isolation. This is the biggest risk in AI-assisted SaaS development. Always check that Prisma queries include where: { tenantId }. Add a lint rule or a custom ESLint plugin that flags findMany calls without a tenantId filter. It's automatable, and it's worth the setup time.

Accepting generated tests that don't actually test anything. AI will sometimes produce tests that pass trivially, asserting expect(result).toBeDefined() instead of checking actual values. Read every generated test assertion and make sure it would fail if the code were wrong.

Not running typecheck between agent steps. Each agent step can introduce type errors that cascade into the next step. Run npm run typecheck after every file edit, not just at the end.

Over-relying on AI for product decisions. AI assistance accelerates execution, not product judgment. What features to build, how to price tiers, what the UX should feel like: these require human thinking. Use AI for the how, not the what.

Frequently Asked Questions

Which AI tool is better for SaaS development — Claude Code or Cursor?

They solve different problems. Claude Code is better for scripted, multi-step automation: generating a full feature, running tests, fixing errors, repeating. Cursor is better for interactive development: exploring an unfamiliar codebase, making targeted edits, getting inline suggestions. Use both. Claude Code for "build this feature," Cursor for "help me understand and fix this bug."

How do I prevent AI from introducing security vulnerabilities?

Run a static analysis tool (Semgrep, CodeQL) on AI-generated code as part of CI. Don't rely on manual review alone. It's too easy to miss an injection vulnerability or a missing auth check in a 200-line file. Also add explicit security rules to your CLAUDE.md: "always validate user input with zod," "never trust client-supplied tenantId," etc.

Does AI assistance work for existing codebases, or only greenfield?

It works for both, but existing codebases require more upfront investment in context files. The more context you give (codebase conventions, existing patterns, which libraries to use), the better the output. For large existing codebases, consider adding a CLAUDE.md per domain directory, not just one at the root.

How do I handle AI-generated code in code review?

Treat it the same as human-written code. Review for correctness, security, and adherence to conventions. Some teams add an "AI-assisted" label to PRs so reviewers know to look more carefully at edge cases. AI tools are good at happy paths but sometimes miss error handling.

What's the ROI on AI-assisted SaaS development?

In our experience at Laxaar, AI assistance cuts the time on scaffolding and boilerplate by 60-80%. It has minimal impact on architecture decisions, complex debugging, and product thinking. The teams that get the most value are those who invest in context configuration upfront and treat AI output as a first draft, not a finished product.

The Laxaar team has built SaaS products across fintech, healthtech, and enterprise software using AI-assisted workflows. If you want a team that knows how to ship AI-augmented SaaS products reliably, talk to us or see our services.

SaaS DevelopmentAI-Assisted CodingAgentic Coding
Grow your business with us

Take your business to the next level.

Tell us what you're building. We'll come back inside one business day with a fixed scope, timeline, and team — or an honest “this isn't a fit”.

ENGINEERING PHILOSOPHY

Code is useless if it's not comprehensible to those who maintain it. We write code the next person can actually understand.