Technology6 min read

Schema-First AI App Building With OpenAPI and Zod Contracts

P
PatAuthor
Schema-First AI App Building With OpenAPI and Zod Contracts

Keep product speed without letting schemas drift

Schema drift is one of the fastest ways for AI-generated or rapidly iterated apps to become fragile. A form field changes in React, the backend accepts something slightly different, Row Level Security (RLS) rules assume a column that was renamed, and Stripe sends an event payload your webhook handler only partly understands. A schema-first approach treats contracts as the single source of truth so that UI generation, database access patterns, and external integrations stay aligned even as you move quickly.

In practice, this means designing around explicit contracts: OpenAPI for HTTP boundaries and Zod for runtime validation and shared types inside your TypeScript codebase. When you combine those contracts with Supabase policies and Stripe webhook verification, you reduce “it worked yesterday” failures and make automated generation safer.

Define the core contract once with OpenAPI

OpenAPI works best when it describes the true public boundary of your system: routes, request bodies, responses, auth requirements, and error shapes. The goal is not documentation for its own sake; it’s to create an artifact that can drive code generation, testing, and UI scaffolding.

What to include in a schema-first OpenAPI spec

  • Stable resource models (User, Project, Workspace, Subscription) with clear required fields and formats.
  • Explicit error responses (400 validation, 401 unauthenticated, 403 unauthorized, 409 conflicts) so UI logic doesn’t guess.
  • Auth scheme aligned with how your app actually authenticates (e.g., bearer tokens for Supabase JWTs).
  • Idempotency and webhook endpoints documented as first-class routes, not “special cases.”

Once the spec is reliable, generated clients become trustworthy. Your frontend stops hardcoding paths and payloads, and your backend can enforce the same shapes with automated validation.

Use Zod as the runtime contract inside TypeScript

OpenAPI is great at the boundary, but many teams still need strong runtime validation inside Node/Edge functions and shared types across UI components. Zod is a pragmatic choice because it validates at runtime and infers TypeScript types, which reduces the gap between “what the code compiles” and “what the API actually received.”

Two common patterns for OpenAPI and Zod together

  • OpenAPI-first: author OpenAPI, then generate types and/or Zod schemas from it. This is useful when the API boundary is the main source of truth.
  • Zod-first: author Zod schemas, then generate OpenAPI for documentation and clients. This can be simpler for teams living mostly in TypeScript.

Either way, the key is choosing one direction and making it consistent. Mixed, ad-hoc conversions are what reintroduce drift.

Generate React UI safely from the contract

“Generated UI” doesn’t have to mean a single monolithic form generator. It can be smaller wins: field definitions, validation rules, and error rendering that are derived from the same schemas as your API.

What to derive from schemas in React

  • Form field constraints: required vs optional, string length, enums, formats (email, URL), numeric ranges.
  • Client-side validation: Zod-based validation that mirrors backend validation so users get the same errors earlier.
  • Typed API hooks: query/mutation hooks that accept and return typed payloads matching OpenAPI.
  • Error mapping: consistent parsing of problem details so UI doesn’t special-case every endpoint.

For AI-assisted building, the benefit is direct: if the generator is pointed at the contract, it can create or adjust UI with fewer hidden assumptions. Tools like lovable.dev are easiest to trust when your project has clear boundaries—React components and pages can evolve quickly while staying anchored to contract-driven types and validations.

Keep Supabase RLS aligned with the same data model

Supabase encourages pushing authorization down into Postgres with Row Level Security. That’s powerful, but it creates a new failure mode: your application layer evolves, while policies still reflect an old understanding of tenancy, ownership, or roles.

Contract thinking applied to RLS

  • Make tenancy explicit in the schema: standardize columns like workspace_id and owner_id across tables where relevant.
  • Reflect roles in a predictable shape: for example, a membership table with role enum values that are also represented in Zod/OpenAPI.
  • Treat policy changes as contract changes: a policy tightening can break UI flows just like an API change.

RLS is also easier to test when you have formalized contracts. For example, you can write contract tests that run queries under different JWT claims and confirm that the database enforces the access patterns your API advertises. If you already use contract tests elsewhere, the same mindset applies; this connects well with approaches like contract tests and shadow runs for catching mismatches early.

Stripe webhooks as a strict boundary, not “best effort” JSON

Stripe events are versioned payloads with a signature verification requirement. Teams often implement webhooks quickly and later discover that silent parsing failures or missing idempotency cause billing edge cases.

Make webhook handling schema-first

  • Verify signatures first: treat unverified payloads as untrusted input and fail fast.
  • Validate event shapes: even if you use Stripe’s official library types, add Zod validation for the subset of fields you actually depend on.
  • Model event-to-state transitions: define what “subscription created,” “invoice paid,” or “payment failed” means in your own domain model.
  • Enforce idempotency: store processed event IDs and make handlers safe to retry.

OpenAPI may not fully describe Stripe’s upstream payloads, but it should describe your internal webhook endpoint behavior: response codes, retry semantics, and any expected side effects. This is also where schema discipline prevents “billing logic lives in five places” problems—one validated handler, one state model, one set of DB writes governed by RLS-aware tables.

Operational workflow that keeps everything in sync

Schema-first only works if the workflow makes drift inconvenient. A practical setup is lightweight but strict:

  • Contracts live in version control: OpenAPI file(s) and Zod schemas are reviewed like production code.
  • Generate clients and types in CI: fail the build when generated artifacts are out of date.
  • Add contract tests: validate that API responses conform to OpenAPI/Zod and that RLS policies match expected access patterns.
  • Track schema changes as system changes: when you update a contract, update UI, RLS, and webhook logic in the same PR whenever possible.

This is especially important in fast-moving AI-assisted projects, where the speed of iteration can outpace manual coordination. If you already manage compliance or audit artifacts, the discipline resembles producing traceability between controls and systems. The same “single source of truth” thinking shows up in workflows like building a control-to-system traceability matrix, where consistency matters more than prose.

FAQ
How does lovable.dev fit into a schema-first workflow?

Should I write OpenAPI or Zod first when building with lovable.dev?

How do I prevent Supabase RLS policies from drifting as the product changes in lovable.dev?

What’s the safest way to validate Stripe webhooks in a lovable.dev project?

Can OpenAPI help with Stripe webhooks even though Stripe defines the payload?