Skip to main content
MindStudio
Pricing
Blog About
My Workspace

How to Build a Spec-Driven App with a Real Backend, Database, and Auth

Spec-driven development compiles annotated markdown into full-stack apps with real backends and databases — no drag-and-drop, no prompt-and-hope.

MindStudio Team RSS
How to Build a Spec-Driven App with a Real Backend, Database, and Auth

The Problem With How Most AI App Builders Handle Backends

Most AI-powered app builders have a dirty secret: the “apps” they produce aren’t really apps. They’re impressive-looking prototypes — polished frontends with fake data, no persistent storage, and authentication that disappears the moment you close the tab.

Ask them to add a real database. Ask them to wire up actual user sessions. Ask them to handle role-based access or email verification. That’s where they fall apart. Many AI-generated apps fail in production precisely because these systems were never designed to produce full-stack software — only the appearance of it.

Spec-driven development takes a different approach. Instead of prompting an AI and hoping it assembles something coherent, you write a structured spec — annotated markdown that defines exactly what the app does, what data it stores, how users authenticate, and what the business rules are. Then you compile it. What comes out is a real backend, a real SQL database, real auth with email verification and sessions, and a working frontend. All from a single source document.

This guide walks through what that process actually looks like, step by step.


What Spec-Driven Development Actually Means

Before getting into the how, it’s worth being precise about what spec-driven development is — and what it isn’t.

It’s not vibe coding. It’s not throwing a prompt at an AI and seeing what sticks. And it’s definitely not no-code drag-and-drop. The difference between spec-driven development and vibe coding comes down to structure and precision: a spec is a document with real semantic meaning, not a conversation.

A spec has two layers:

  1. Readable prose — Human-legible descriptions of what the app does, who uses it, what flows exist.
  2. Annotations — Inline markup that carries precision: data types, validation rules, required fields, edge cases, access controls.

The result looks like a markdown document, but it functions like a programming language. An AI compiler reads it and produces TypeScript for the backend, a typed SQL database schema, an auth system, a frontend UI, and deployment configuration.

The spec is the source of truth. Code is compiled output. This shift in abstraction — away from writing code and toward writing intent — is what makes the whole approach different.


Step 1: Define What the App Actually Does

Every spec starts with a plain-English overview of the application. This isn’t a user story or a feature list in the usual sense. It’s a clear statement of:

  • What the app is
  • Who uses it and how
  • What the core data objects are
  • What actions users can take

For example, a simple project tracker spec might start like this:

# Project Tracker

A multi-user web app for tracking projects and tasks.
Users can create projects, add tasks to those projects,
assign tasks to themselves or teammates, and mark tasks
as complete.

This prose establishes the domain. Everything downstream — database tables, backend methods, auth rules, UI views — derives from it.

The key discipline here is being specific without being exhaustive. You’re not describing every pixel. You’re describing the contract the app must fulfill. If you’re not sure what makes a good app spec, start with the nouns (entities) and verbs (actions), then layer in the rules.


Step 2: Define Your Data Model in the Spec

The database schema in a spec-driven app isn’t configured through a GUI or written as raw SQL. It’s described in the spec itself, using annotations that the compiler translates into typed table definitions.

Here’s what a basic data model block might look like:

## Data Model

### User
- id: uuid, primary key
- email: string, unique, required
- name: string, required
- role: enum(admin, member), default: member
- created_at: timestamp

### Project
- id: uuid, primary key
- name: string, required
- owner_id: uuid, foreign key → User.id
- created_at: timestamp

### Task
- id: uuid, primary key
- title: string, required
- description: string, optional
- project_id: uuid, foreign key → Project.id
- assigned_to: uuid, foreign key → User.id, optional
- status: enum(todo, in_progress, done), default: todo
- due_date: date, optional

This is readable as documentation and precise enough to generate a real schema from. The compiler turns this into SQL table definitions with proper types, constraints, and indexes. It also handles migrations automatically when the schema changes.

If you want to understand how database schemas work at a deeper level, this explainer on database schema structure covers the fundamentals.

What the Compiler Does With It

When Remy reads that data model block, it doesn’t guess. It generates:

  • SQL table definitions with proper column types and constraints
  • Foreign key relationships with referential integrity
  • TypeScript type definitions that match the schema
  • Query helpers that enforce those types at the code level

The database is real SQLite running with WAL journaling. Schema migrations run automatically on deploy. You don’t manage migration files manually.


Step 3: Spec Out Your Backend Methods

A full-stack app has a backend — server-side logic that enforces business rules, handles data access, and keeps the frontend from doing anything it shouldn’t. In spec-driven development, that backend is described in the spec as a set of methods.

Backend methods are the verbs of your application. Each one defines:

  • What it does
  • What inputs it accepts
  • What it returns
  • Who’s allowed to call it
  • Any validation or business rules that apply

Here’s an example:

## Backend Methods

### createTask
Creates a new task within a project.

Inputs:
- title: string, required, max 200 chars
- project_id: uuid, required
- assigned_to: uuid, optional
- due_date: date, optional

Access: authenticated users who are members of the project

Rules:
- The calling user must be a member of the project
- If assigned_to is provided, that user must also be a member
- title must not be blank after trimming whitespace

Returns: the created task object

The compiler reads this and generates:

  • A TypeScript function on the backend with the right signature
  • Input validation matching the rules
  • An authorization check that enforces the access constraint
  • A typed return value

This is the part where spec-driven apps are structurally different from what most AI app builders produce. Those tools generate frontend code that directly reads and writes data through client-side calls. There’s no enforcement layer. When an app doesn’t have a real backend, authorization is just a UI checkbox someone can work around.

A spec-compiled app has a real backend. The business rules are enforced on the server, not in the browser.


Step 4: Define Authentication in the Spec

Auth is consistently where AI-generated apps fall shortest. Most tools either skip it entirely, simulate it in the frontend, or generate code that looks right until someone inspects the session tokens. The reasons AI app builders struggle with databases and auth come down to one thing: auth is stateful, and stateful systems require a real backend and a real database.

In a spec-driven app, auth is a first-class part of the spec. You describe how users sign in and what verification looks like, and the compiler generates a complete auth system from that description.

A typical auth block in a spec looks like this:

## Authentication

Method: email + verification code (passwordless)

Flow:
1. User enters their email address
2. System sends a 6-digit code to that email, valid for 10 minutes
3. User enters the code
4. System creates a session and sets a secure HTTP-only cookie

Sessions:
- Duration: 30 days
- Stored in the database as session records linked to user
- Invalidated on sign out

New accounts:
- Created automatically on first successful verification
- Name collected after first login

From this, the compiler generates:

  • A users table and a sessions table in the database
  • Backend methods for initiating login, verifying codes, and signing out
  • Session middleware that validates the cookie on every authenticated request
  • Email delivery for the verification code

This is how user authentication actually works in a production system — server-side sessions, secure cookies, real verification. Not a localStorage.setItem('loggedIn', true) hack.

Role-Based Access in the Spec

If your app needs roles — admins vs. regular members, for example — you describe that in the data model (using an enum field on the user) and reference it in the access rules of each backend method.

### deleteProject
Deletes a project and all its associated tasks.

Access: admin users only, or the project owner

Rules:
- Hard delete: removes the project and cascades to tasks
- The calling user must be authenticated

The compiler generates the check that enforces this at the server level. It’s not UI-level gating. It’s enforced in the backend method before any database operation runs. For apps that need complex permission structures, building multi-user apps with roles and permissions gets into how that plays out at scale.


Step 5: Describe the Frontend Behavior

The frontend section of a spec describes user flows and UI structure — not pixel layouts, but the logical flow of how a user moves through the app.

## Frontend

### Views

#### /login
- Email input field
- "Send Code" button
- After submission: show code verification input
- On success: redirect to /dashboard

#### /dashboard
- List of projects the user is a member of
- "New Project" button → opens modal with project name field
- Clicking a project → navigate to /projects/:id

#### /projects/:id
- Project name as heading
- Task list grouped by status (todo, in_progress, done)
- "Add Task" button → opens task creation form
- Each task row shows title, assignee, due date, status dropdown

The compiler uses this to generate the frontend components, route structure, and the wiring between frontend calls and backend methods. The UI isn’t beautifully designed out of the box — that’s on you to polish — but the functional structure is there.


Step 6: Compile and Review

Once the spec is complete, you compile it. In Remy, this means running the spec through the AI compiler, which:

  1. Parses the spec structure and annotations
  2. Generates TypeScript backend code with all methods, validation, and auth logic
  3. Generates the database schema and migration files
  4. Generates frontend components and routing
  5. Wires everything together
  6. Deploys on push to main branch

The output is real, readable code. You can open any file and see exactly what was generated. You can edit it directly if you need to change something the spec didn’t capture perfectly. And when you update the spec and recompile, the code follows.

This is an important point: the spec doesn’t lock you out of the code. You own the code, you can read it, and you can modify it. The spec is just the higher-level source of truth. If you want to understand the relationship between these layers, the abstraction ladder from assembly to TypeScript to spec explains it well.


How Remy Handles This End to End

Remy is the tool that implements this spec-driven workflow. You write the spec in an annotated markdown document. Remy compiles it into a full-stack application: TypeScript backend, SQLite database, email-based auth, React frontend, all deployed to a live URL.

The environment is browser-based. You open a tab, write your spec, and build. There’s no local setup to configure, no separate database to provision, no auth library to integrate manually. It’s all derived from the spec.

Here’s what Remy produces from a complete spec:

  • Backend: TypeScript functions running in a Node environment, accessible to the frontend via typed API calls
  • Database: SQLite with WAL journaling, automatic schema migrations on deploy, typed query helpers
  • Auth: Email-based verification codes, server-side sessions stored in the database, HTTP-only cookies
  • Frontend: React + Vite by default, but any framework that runs a build command works
  • Deployment: Deploy on push to main branch, live on a real URL

If you’re curious about the range of things you can build with this workflow, there are 10 solid examples of spec-compiled apps worth looking at. From internal dashboards to SaaS tools to multi-user trackers.

You can try Remy at mindstudio.ai/remy.


Common Mistakes When Writing Your First Spec

Being Too Vague in Annotations

“Users can manage their data” tells the compiler almost nothing. “Users can create, read, update, and delete their own task records; they cannot access tasks belonging to other users” is actionable. Precision in annotations is what separates a spec that compiles well from one that produces something generic.

Skipping Edge Cases

The best specs think through failure paths: what happens when a verification code expires? What if a user tries to join a project they’re already a member of? What if a task is deleted while someone is viewing it? These aren’t edge cases in the technical sense — they’re business rules. If you don’t specify them, the compiler has to guess.

Conflating Frontend and Backend Logic

A common mistake is speccing frontend behavior that implies business logic (“the button is disabled if the user isn’t an admin”). The button state is a frontend concern. Whether the underlying action is allowed is a backend concern. Spec both separately and explicitly.

Over-speccing the UI

You don’t need to describe every modal and tooltip in the spec. The frontend section describes user flows and structure. Visual design is separate. Focus on what the user can do and where they go next, not what color the button is.


FAQ

What is spec-driven development and how is it different from writing code?

Spec-driven development is a programming approach where you write a structured document — the spec — that describes what an application does, and an AI compiler generates the full-stack code from it. The spec uses annotated markdown: readable prose for the human understanding, annotations for machine precision. The key difference from traditional coding is that the spec is the source of truth, not the code. Code is a compiled artifact, just like binary is compiled from TypeScript.

Can I really get a real backend and database from a spec, not just a frontend?

Yes, and this is the main point. The spec explicitly defines backend methods with input validation, access controls, and business rules. It defines a database schema with typed fields and relationships. The compiler generates real server-side code from these definitions. You end up with a TypeScript backend, a SQL database with proper constraints, and auth that enforces rules at the server level — not just the UI.

How does authentication work in a spec-driven app?

You describe the auth flow in the spec: the verification method, session duration, storage mechanism, and new account behavior. The compiler generates a complete auth system from that description — including database tables for users and sessions, backend methods for login and logout, email delivery for verification codes, and session middleware that validates every authenticated request. Sessions are server-side and stored in the database. There’s no client-side session simulation.

Do I need to know how to code to write a spec?

You need to understand what you want the app to do, how data flows through it, and what the rules are. That’s closer to product thinking than programming. Familiarity with concepts like data types, foreign keys, and authentication flows will help you write better annotations. But you don’t need to write TypeScript or SQL directly. If you want a deeper read on how to write a software spec from scratch, there’s a practical guide that covers the process.

What happens when I need to change the app after it’s compiled?

You update the spec and recompile. The compiler generates updated code that reflects the changes. If the database schema changes, migration files are generated and run automatically on deploy. You can also edit the generated code directly for fine-grained adjustments — you own the code and can read and modify it freely. The spec stays as the source of truth for the overall structure; the code handles the details.

Is this approach better than using a platform like Supabase or Firebase directly?

It depends on what you’re optimizing for. Platforms like Supabase give you managed backend infrastructure you configure and integrate yourself. Spec-driven development generates the integration layer for you — the backend methods, schema, auth wiring — from a single document. It’s faster to get to a working app. The tradeoff is that highly customized backend logic may require editing the generated code rather than speccing it. For most full-stack web apps — SaaS tools, internal dashboards, multi-user trackers — spec-driven development covers the whole surface.


Key Takeaways

  • Spec-driven development produces real full-stack apps — not frontend prototypes. The spec defines backend methods, database schema, and auth. The compiler generates all of it.
  • Precision in annotations is what makes the spec useful. Vague specs produce generic output. Specific specs produce working apps.
  • Auth in a spec-compiled app is server-side and real: sessions stored in the database, verification codes delivered by email, enforcement in the backend layer.
  • The spec is the source of truth. Code is derived from it and can be read, edited, and extended — but the spec stays as the authoritative description of what the app does.
  • Spec-driven development fits best for full-stack web apps where you want to move from idea to deployed product without manually wiring together a backend, database, auth, and frontend stack.

If you want to see what this looks like in practice, try Remy at mindstudio.ai/remy. Write a spec, compile it, and see what comes out.

Presented by MindStudio

No spam. Unsubscribe anytime.