/how-to-build-lovable

How to build Medical records app with Lovable?

Learn how to build a secure, HIPAA-ready medical records app with Lovable. Step-by-step guide for features, integrations, and deployment.

Matt Graham, CEO of Rapid Developers

Book a call with an Expert

Starting a new venture? Need to upgrade your web app? RapidDev builds application with your growth in mind.

Book a free No-Code consultation

How to build Medical records app with Lovable?

You can build a production-ish Medical Records app inside Lovable by using Chat Mode to create React pages, wiring Supabase for auth + storage via Lovable Cloud Secrets, testing in Lovable Preview, and Publish (or export to GitHub) when ready. Do everything in chat edits — no terminal needed — and use the Supabase Dashboard UI to create the database schema (so no CLI required). Below are exact Lovable prompts you can paste into Chat Mode to implement the app incrementally.

 

What we’re building / changing

 

Quick patient medical-records web app (React) with Supabase for authentication, patient record CRUD, and document upload/storage. Users sign in, see a list of records, create / view records and upload PDFs/images to Supabase Storage.

  • Frontend files: auth + records pages and a supabase client.
  • Backend/data: Supabase Postgres table (created in Supabase Dashboard) and Storage bucket.

 

Lovable-native approach

 

Use Chat Mode to create/modify files (create src/lib/supabaseClient.ts, src/pages/Login.tsx, src/pages/RecordsList.tsx, src/pages/RecordView.tsx, update src/App.tsx). Configure SUPABASE_URL and SUPABASE_ANON\_KEY via Lovable Cloud Secrets UI. Use Preview to test interactions. Use Publish to push the app live or use GitHub export/sync if you want production CI. No terminal is required for these steps.

 

Meta-prompts to paste into Lovable

 

  • Prompt 1: Goal: Add Supabase client and secret wiring. Exact files: create src/lib/supabaseClient.ts. Acceptance: app exports a ready-to-use Supabase client instance that reads from environment via process.env (Lovable Secrets). Secrets: add SUPABASE_URL and SUPABASE_ANON\_KEY in Lovable Cloud Secrets UI. // Paste this prompt into Lovable Chat Mode:
    // Create file src/lib/supabaseClient.ts
    // Use @supabase/supabase-js client for browser usage
    
    import { createClient } from '@supabase/supabase-js'
    
    // Read from environment (Lovable Cloud Secrets will map to process.env)
    const SUPABASE_URL = process.env.SUPABASE_URL!
    const SUPABASE_ANON_KEY = process.env.SUPABASE_ANON_KEY!
    
    export const supabase = createClient(SUPABASE_URL, SUPABASE_ANON_KEY)
    

    Done when: src/lib/supabaseClient.ts exists and Preview shows no import errors for supabase.}

  • Prompt 2: Goal: Add auth page and routing. Exact files: create src/pages/Login.tsx, modify src/App.tsx to include routes (/login, /records, /records/:id). Acceptance: You can open /login in Preview, sign in using Supabase email-link or credentials (configured in Supabase). // Paste:
    // Create file src/pages/Login.tsx
    // Simple email sign-in using supabase.auth.signInWithOtp or signInWithPassword
    
    import React, { useState } from 'react'
    import { supabase } from '../lib/supabaseClient'
    
    export default function Login() {
      const [email, setEmail] = useState('')
      const [loading, setLoading] = useState(false)
    
      async function handleSignIn(e) {
        e.preventDefault()
        setLoading(true)
        // Use signInWithOtp for magic link (adjust if you prefer password)
        await supabase.auth.signInWithOtp({ email })
        setLoading(false)
        alert('Check your email for a sign-in link.')
      }
    
      return (
        <div>
          <h1>Sign in</h1>
          <form onSubmit={handleSignIn}>
            <input value={email} onChange={e => setEmail(e.target.value)} placeholder="[email protected]" />
            <button type="submit" disabled={loading}>{loading ? 'Sending…' : 'Send sign-in link'}</button>
          </form>
        </div>
      )
    }
    
    // Modify src/App.tsx
    // Add routes: /login, /records, /records/:id. Use your app's router (React Router assumed).
    // If your project uses a different router adjust accordingly.
    
    import React from 'react'
    import { BrowserRouter, Routes, Route, Link } from 'react-router-dom'
    import Login from './pages/Login'
    import RecordsList from './pages/RecordsList'
    import RecordView from './pages/RecordView'
    
    export default function App() {
      return (
        <BrowserRouter>
          <nav>
            <Link to="/records">Records</Link>
            <Link to="/login">Login</Link>
          </nav>
          <Routes>
            <Route path="/login" element={<Login />} />
            <Route path="/records" element={<RecordsList />} />
            <Route path="/records/:id" element={<RecordView />} />
          </Routes>
        </BrowserRouter>
      )
    }
    

    Done when: /login loads in Preview and no import errors.}

  • Prompt 3: Goal: Implement record list, create, view and upload. Exact files: create src/pages/RecordsList.tsx, src/pages/RecordView.tsx and src/api/records.ts. Acceptance: From Preview you can create a record with title+notes and upload a file; record appears in list and you can view details and download file. Setup steps: In Supabase Dashboard create table "medical_records" with columns: id (uuid, primary key), user_id (text), title (text), notes (text), file_path (text), inserted_at (timestamp). Create a Storage bucket "records". // Paste:
    // Create file src/api/records.ts
    // Helpers for CRUD
    
    import { supabase } from '../lib/supabaseClient'
    
    export async function listRecords() {
      const user = supabase.auth.getUser ? (await supabase.auth.getUser()).data.user : null
      // // If using older client, adjust to supabase.auth.user()
      const { data, error } = await supabase.from('medical_records').select('*').eq('user_id', user.id).order('inserted_at', { ascending: false })
      if (error) throw error
      return data
    }
    
    export async function createRecord({ title, notes, file }) {
      const user = (await supabase.auth.getUser()).data.user
      let file_path = null
      if (file) {
        const filename = `${user.id}/${Date.now()}_${file.name}`
        const { error: upErr } = await supabase.storage.from('records').upload(filename, file)
        if (upErr) throw upErr
        file_path = filename
      }
      const { data, error } = await supabase.from('medical_records').insert([{ user_id: user.id, title, notes, file_path }])
      if (error) throw error
      return data[0]
    }
    
    // Create file src/pages/RecordsList.tsx
    // List and create form (very minimal)
    
    import React, { useEffect, useState } from 'react'
    import { listRecords, createRecord } from '../api/records'
    import { Link } from 'react-router-dom'
    
    export default function RecordsList() {
      const [records, setRecords] = useState([])
      const [title, setTitle] = useState('')
      const [notes, setNotes] = useState('')
      const [file, setFile] = useState(null)
    
      async function load() {
        const data = await listRecords()
        setRecords(data)
      }
    
      useEffect(() => { load() }, [])
    
      async function handleCreate(e) {
        e.preventDefault()
        await createRecord({ title, notes, file })
        setTitle(''); setNotes(''); setFile(null)
        await load()
      }
    
      return (
        <div>
          <h1>Records</h1>
          <form onSubmit={handleCreate}>
            <input value={title} onChange={e=>setTitle(e.target.value)} placeholder="Title" />
            <textarea value={notes} onChange={e=>setNotes(e.target.value)} placeholder="Notes" />
            <input type="file" onChange={e=>setFile(e.target.files[0])} />
            <button type="submit">Create</button>
          </form>
          <ul>
            {records.map(r=>(
              <li key={r.id}><Link to={`/records/${r.id}`}>{r.title}</Link></li>
            ))}
          </ul>
        </div>
      )
    }
    
    // Create file src/pages/RecordView.tsx
    // Show details and download link for file
    
    import React, { useEffect, useState } from 'react'
    import { useParams } from 'react-router-dom'
    import { supabase } from '../lib/supabaseClient'
    
    export default function RecordView() {
      const { id } = useParams()
      const [record, setRecord] = useState(null)
    
      useEffect(() => {
        async function load() {
          const { data } = await supabase.from('medical_records').select('*').eq('id', id).single()
          setRecord(data)
        }
        load()
      }, [id])
    
      if (!record) return <div>Loading…</div>
    
      async function download() {
        if (!record.file_path) return
        const { data, error } = await supabase.storage.from('records').download(record.file_path)
        if (error) { alert('Download error'); return }
        const url = URL.createObjectURL(data)
        window.open(url)
      }
    
      return (
        <div>
          <h1>{record.title}</h1>
          <p>{record.notes}</p>
          {record.file_path && <button onClick={download}>Download file</button>}
        </div>
      )
    }
    

    Done when: create + list + download flow works in Preview (records show up).}

 

How to verify in Lovable Preview

 

  • Open Preview, go to /login, sign in via Supabase email link (check email).
  • Open /records, create a record with a small PDF/image. It should appear in the list.
  • Open a record detail and click Download to verify stored file serves from Supabase.

 

How to Publish / re-publish

 

  • Use Lovable Publish button to deploy the Cloud app. Ensure Lovable Secrets (SUPABASE_URL and SUPABASE_ANON\_KEY) are set before publishing.
  • If you need CI/CD or migrations, enable GitHub export/sync from Lovable and complete DB migration in Supabase Dashboard (no terminal required) or via your CI after export.

 

Common pitfalls (and how to avoid them)

 

  • Missing Secrets: Add SUPABASE_URL and SUPABASE_ANON\_KEY in Lovable Cloud Secrets UI — Preview will fail without them.
  • No DB schema: Create the medical\_records table and Storage bucket in Supabase Dashboard before creating records.
  • Using server-only keys in client: Never place SUPABASE_SERVICE_ROLE in client-side secrets; use server functions if you need elevated ops (that requires external deployment patterns).
  • CORS / Allowed origins: Ensure your Lovable app URL is allowed in Supabase Auth settings if sign-in links fail.

 

Validity bar

 

  • This uses Lovable Chat Mode file edits, Preview, Publish, and Lovable Cloud Secrets. No invented Lovable features are used. If you need DB migrations or server-edge functions, use Supabase Dashboard or GitHub export + external CI (terminal outside Lovable may be required for advanced deployment).

Want to explore opportunities to work with us?

Connect with our team to unlock the full potential of no-code solutions with a no-commitment consultation!

Book a Free Consultation

How to add an immutable audit log for patient record changes

This prompt helps an AI assistant understand your setup and guide to build the feature

AI AI Prompt

How to add smart server-side patient validation in Lovable

This prompt helps an AI assistant understand your setup and guide to build the feature

AI AI Prompt

How to add Advanced Patient Search in Lovable

This prompt helps an AI assistant understand your setup and guide to build the feature

AI AI Prompt

Want to explore opportunities to work with us?

Connect with our team to unlock the full potential of no-code solutions with a no-commitment consultation!

Book a Free Consultation
Matt Graham, CEO of Rapid Developers

Book a call with an Expert

Starting a new venture? Need to upgrade your web app? RapidDev builds application with your growth in mind.

Book a free No-Code consultation

Best Practices for Building a Medical records app with AI Code Generators

You should treat a medical records app as a privacy-first, auditable system and only use AI code generation where it reduces developer work without exposing protected health information (PHI). Use strong data minimization (redaction/structuring), encrypt-at-rest/in-transit, strict access controls and audit logging, prefer on-premise or HIPAA-compliant model providers (or signed BAAs), and use Lovable’s chat-first workflow (Chat Mode edits, diffs, Preview, Secrets UI, Publish, and GitHub sync) to build, test, and deploy — never rely on running CLI commands inside Lovable.

 

Threat model & compliance first

 

Identify PHI (names, SSNs, MRNs, full DOB, addresses, contact info, clinical notes). Decide whether you will process PHI with third-party AI. If you will, get a BAA and document it. If you cannot get a BAA, never send raw PHI to external LLMs — only send redacted or structured/synthetic data.

  • Minimize what you send to models: strip identifiers, send only relevant structured fields or templated summaries.
  • Audit: log who requested data, what was sent to the model, and what the model returned.
  • Encrypt sensitive data at rest (DB) and in transit (TLS).

 

Lovable workflow & practical constraints

 

Use Chat Mode edits and diffs to iterate UI and backend code. Use Preview to test logic against mocked data. Store secrets with Lovable’s Secrets UI (SUPABASE_URL, SUPABASE_KEY, OPENAI_API_KEY) — you cannot run CLI to set env vars, so Secrets UI is mandatory. When you need deeper CI or deployment, sync to GitHub and manage deployment pipelines there.

  • No terminal: run migrations or one-off scripts by exporting to GitHub and running CI, or by using Supabase web UI for migrations.
  • Preview with synthetic/masked datasets — never preview with real PHI in Lovable unless your workspace is approved for it.
  • Publish from Lovable Cloud after adding Secrets and testing; confirm runtime env contains necessary secrets in Lovable before publishing.

 

Data flow best practices

 

  • Fetch minimal data from Supabase: request only required fields.
  • Redact or tokenize PII before any model call.
  • Structure data into strict JSON schemas to avoid free-text leakage.
  • Apply post-processing: validate model output, detect hallucinations, and never auto-write back to records without human review.

 

Example: server API that redacts before calling an LLM

 

// API route: /api/patient-summary
import { createClient } from '@supabase/supabase-js';

// create Supabase client using Lovable Secrets stored as env vars
const supabase = createClient(process.env.SUPABASE_URL, process.env.SUPABASE_KEY);

// small redaction helper: remove SSN-like and long numeric IDs
function redactPII(text) {
  // redact SSN patterns and long digit strings
  return text.replace(/\b\d{3}-\d{2}-\d{4}\b/g, '[REDACTED_SSN]').replace(/\b\d{6,}\b/g, '[REDACTED_ID]');
}

export default async function handler(req, res) {
  const { patientId } = req.body;
  // fetch minimal columns
  const { data, error } = await supabase
    .from('patients')
    .select('id,age,gender,clinical_notes')
    .eq('id', patientId)
    .single();

  if (error) return res.status(500).json({ error: error.message });

  const payload = redactPII(JSON.stringify(data));

  // call OpenAI (store OPENAI_API_KEY in Lovable Secrets)
  const r = await fetch('https://api.openai.com/v1/chat/completions', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      Authorization: `Bearer ${process.env.OPENAI_API_KEY}`
    },
    body: JSON.stringify({
      model: 'gpt-4',
      messages: [
        { role: 'system', content: 'Summarize clinical notes into a short, factual summary. Do not add new clinical facts.' },
        { role: 'user', content: `Patient data: ${payload}` }
      ],
      max_tokens: 500
    })
  });

  const json = await r.json();
  const summary = json.choices?.[0]?.message?.content || null;

  // never auto-save summary; return for human review
  res.json({ summary });
}

 

Testing, monitoring, and deployment

 

  • Test with synthetic data in Lovable Preview; add unit tests and run CI after exporting to GitHub.
  • Monitor model usage, latency, and the logs for data sent to LLMs. Keep an approval flow for any outputs that will be written back to records.
  • Escalation: plan for incident response and data breach procedures.

 

Final practical notes: Use Lovable’s Secrets UI for keys, iterate with Chat Mode and diffs, Preview with mocks, Publish only after review, and export to GitHub for DB migrations or operations requiring CLI. Always document your decisions about PHI, BAAs, and model usage — regulators will ask.

Client trust and success are our top priorities

When it comes to serving you, we sweat the little things. That’s why our work makes a big impact.

Rapid Dev was an exceptional project management organization and the best development collaborators I've had the pleasure of working with. They do complex work on extremely fast timelines and effectively manage the testing and pre-launch process to deliver the best possible product. I'm extremely impressed with their execution ability.

CPO, Praction - Arkady Sokolov

May 2, 2023

Working with Matt was comparable to having another co-founder on the team, but without the commitment or cost. He has a strategic mindset and willing to change the scope of the project in real time based on the needs of the client. A true strategic thought partner!

Co-Founder, Arc - Donald Muir

Dec 27, 2022

Rapid Dev are 10/10, excellent communicators - the best I've ever encountered in the tech dev space. They always go the extra mile, they genuinely care, they respond quickly, they're flexible, adaptable and their enthusiasm is amazing.

Co-CEO, Grantify - Mat Westergreen-Thorne

Oct 15, 2022

Rapid Dev is an excellent developer for no-code and low-code solutions.
We’ve had great success since launching the platform in November 2023. In a few months, we’ve gained over 1,000 new active users. We’ve also secured several dozen bookings on the platform and seen about 70% new user month-over-month growth since the launch.

Co-Founder, Church Real Estate Marketplace - Emmanuel Brown

May 1, 2024 

Matt’s dedication to executing our vision and his commitment to the project deadline were impressive. 
This was such a specific project, and Matt really delivered. We worked with a really fast turnaround, and he always delivered. The site was a perfect prop for us!

Production Manager, Media Production Company - Samantha Fekete

Sep 23, 2022