/how-to-build-lovable

How to build Recommendations engine with Lovable?

Learn how to build a scalable recommendations engine with Lovable using practical steps, code examples, evaluation tips, and deployment best practices

Book a free  consultation
4.9
Clutch rating 🌟
600+
Happy partners
17+
Countries served
190+
Team members
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 Recommendations engine with Lovable?

Build a simple, production-practical recommendations engine in Lovable by keeping the recommendation logic server-side (an API route in your app) that queries Supabase (items + interactions tables), using Lovable Cloud Secrets for the Supabase keys, and adding a small client component that calls that API. You’ll do everything through Lovable Chat Mode edits, Preview, and Publish — no terminal. Use Supabase SQL editor (outside Lovable) to create tables; keep keys in Lovable Secrets and never paste them into chat.

 

What we’re building / changing (plain English)

 

We’ll add: a server API endpoint /api/recommendations that reads a user’s interactions from Supabase, computes simple content-based recommendations by tag overlap, and returns ranked items. A client hook and UI component will call that endpoint and render recommendations. Interactions are recorded via a separate /api/interaction route. Supabase tables (items, interactions) must be created in the Supabase dashboard (SQL provided).

 

Lovable-native approach

 

  • Use Chat Mode edits to create files and API routes in the project (no terminal).
  • Store SUPABASE_URL and SUPABASE_SERVICE_ROLE_KEY using Lovable Cloud Secrets UI (so server API can call Supabase).
  • Preview to test endpoints and UI, then Publish to deploy changes from Lovable Cloud.
  • If you need to run migrations/SQL, run them in the Supabase dashboard (outside Lovable).

 

Meta-prompts to paste into Lovable

 

Paste each prompt into Lovable chat one at a time. Each prompt tells Lovable exactly which files to create or update.

 

Prompt 1 — Add server recommendation API

 

Goal: Create /api/recommendations server route that returns ranked recommendations for a given user\_id.

Files to create: src/pages/api/recommendations.ts

Acceptance criteria (done when):

  • The endpoint accepts POST JSON { "user\_id": "..." , "limit": 10 }.
  • The endpoint reads interactions and items from Supabase via SUPABASE_URL and SUPABASE_SERVICE_ROLE_KEY from env, computes tag-overlap scores, returns top items excluding already-interacted ones.
  • It returns JSON { items: [...] }.

Secrets/Integration: Requires SUPABASE_URL and SUPABASE_SERVICE_ROLE_KEY in Lovable Cloud Secrets (see next prompt).

Code to create (paste into Lovable so it can write the file):

// create file src/pages/api/recommendations.ts
import type { NextApiRequest, NextApiResponse } from 'next'

// Helper: fetch Supabase REST
async function supabaseFetch(path: string, opts: any) {
  const url = `${process.env.SUPABASE_URL}/rest/v1/${path}`
  const res = await fetch(url, {
    ...opts,
    headers: {
      'apikey': process.env.SUPABASE_SERVICE_ROLE_KEY as string,
      'Authorization': `Bearer ${process.env.SUPABASE_SERVICE_ROLE_KEY}`,
      'Content-Type': 'application/json',
      ...opts?.headers
    }
  })
  return res
}

export default async function handler(req: NextApiRequest, res: NextApiResponse) {
  if (req.method !== 'POST') return res.status(405).end()
  const { user_id, limit = 10 } = req.body
  if (!user_id) return res.status(400).json({ error: 'user_id required' })

  // 1) fetch interactions
  const intRes = await supabaseFetch(`interactions?user_id=eq.${user_id}`, { method: 'GET' })
  const interactions = await intRes.json()

  const interactedIds = interactions.map((i: any) => i.item_id)

  // 2) fetch interacted items to collect tags
  const tagSet = new Set<string>()
  if (interactedIds.length) {
    const ids = interactedIds.join(',')
    const itemsRes = await supabaseFetch(`items?id=in.(${ids})`, { method: 'GET' })
    const items = await itemsRes.json()
    items.forEach((it: any) => (it.tags || []).forEach((t: string) => tagSet.add(t)))
  }

  // 3) if no tags, fallback to latest items
  if (tagSet.size === 0) {
    const latest = await supabaseFetch(`items?select=*&order=created_at.desc&limit=${limit}`, { method: 'GET' })
    return res.status(200).json({ items: await latest.json() })
  }

  // 4) fetch candidate items that share any tag
  const tagsArray = Array.from(tagSet).map(t => encodeURIComponent(`{${t}}`))
  // PostgREST text[] filters usage: use cs=contains; using simple contains via LIKE fallback
  const candidatesRes = await supabaseFetch(`items?select=*&limit=100`, { method: 'GET' })
  const candidates = await candidatesRes.json()

  // 5) score by overlap
  function scoreItem(it: any) {
    const tags = it.tags || []
    let score = 0
    tags.forEach((t: string) => { if (tagSet.has(t)) score++ })
    return score
  }
  const scored = candidates
    .filter((it: any) => !interactedIds.includes(it.id))
    .map((it: any) => ({ ...it, score: scoreItem(it) }))
    .sort((a: any, b: any) => b.score - a.score)
    .slice(0, limit)

  return res.status(200).json({ items: scored })
}

 

Prompt 2 — Add interaction recording API

 

Goal: Create /api/interaction to record user interactions.

Files to create: src/pages/api/interaction.ts

Acceptance criteria:

  • POST { user_id, item_id } inserts a row into interactions table with created\_at default.

Code to create:

// create file src/pages/api/interaction.ts
import type { NextApiRequest, NextApiResponse } from 'next'

export default async function handler(req: NextApiRequest, res: NextApiResponse) {
  if (req.method !== 'POST') return res.status(405).end()
  const { user_id, item_id } = req.body
  if (!user_id || !item_id) return res.status(400).json({ error: 'user_id and item_id required' })

  const url = `${process.env.SUPABASE_URL}/rest/v1/interactions`
  const insertRes = await fetch(url, {
    method: 'POST',
    headers: {
      'apikey': process.env.SUPABASE_SERVICE_ROLE_KEY as string,
      'Authorization': `Bearer ${process.env.SUPABASE_SERVICE_ROLE_KEY}`,
      'Content-Type': 'application/json',
      'Prefer': 'return=representation'
    },
    body: JSON.stringify({ user_id, item_id })
  })
  const payload = await insertRes.json()
  return res.status(insertRes.ok ? 200 : 500).json(payload)
}

 

Prompt 3 — Add client UI and hook

 

Goal: Create a Recommendations component that calls /api/recommendations and renders items; add a TrackInteraction helper to call /api/interaction.

Files to create/modify: create src/components/Recommendations.tsx and modify src/App.tsx to show for preview.

Acceptance criteria:

  • Visually renders a list of recommended item titles in Lovable Preview when userId provided.
  • Clicking an item calls /api/interaction to record it (for subsequent recommendation changes).

Code snippets to create/modify:

// create file src/components/Recommendations.tsx
import React, { useEffect, useState } from 'react'

export default function Recommendations({ userId }: { userId: string }) {
  const [items, setItems] = useState<any[]>([])

  useEffect(() => {
    if (!userId) return
    fetch('/api/recommendations', { method: 'POST', headers: {'Content-Type':'application/json'}, body: JSON.stringify({ user_id: userId, limit: 6 }) })
      .then(r => r.json()).then(j => setItems(j.items || []))
  }, [userId])

  function track(itemId: string) {
    fetch('/api/interaction', { method: 'POST', headers: {'Content-Type':'application/json'}, body: JSON.stringify({ user_id: userId, item_id: itemId }) })
  }

  return (
    <div>
      <h3>Recommendations</h3>
      <ul>
        {items.map(it => <li key={it.id}>
          <button onClick={() => track(it.id)}>{it.title}</button>
        </li>)}
      </ul>
    </div>
  )
}
// update src/App.tsx — add import and render for preview
import Recommendations from './components/Recommendations'

// inside App render area:
<Recommendations userId="demo-user-1" />

 

Supabase schema (outside Lovable)

 

Run this SQL in the Supabase SQL editor (outside Lovable):

// create tables
create table items (
  id uuid primary key default gen_random_uuid(),
  title text,
  description text,
  tags text[],
  created_at timestamptz default now()
);

create table interactions (
  id uuid primary key default gen_random_uuid(),
  user_id text,
  item_id uuid references items(id),
  created_at timestamptz default now()
);

 

How to verify in Lovable Preview

 

  • Set Secrets: In Lovable Cloud Secrets UI add SUPABASE_URL and SUPABASE_SERVICE_ROLE_KEY.
  • Preview: Open Preview, fill sample items in Supabase (via dashboard), then open the app — Recommendations should show for userId "demo-user-1".
  • Click an item and confirm a new row appears in interactions table in Supabase (verify in dashboard).

 

How to Publish / re-publish

 

  • Use Lovable Publish button to deploy changes. Secrets are respected on Publish.
  • If you sync to GitHub, validate that server files are committed. After Publish, test the live URL.

 

Common pitfalls in Lovable (and how to avoid them)

 

  • Missing Secrets: API returns 401/500. Fix by adding SUPABASE_URL and SUPABASE_SERVICE_ROLE_KEY in Lovable Secrets UI and re-preview.
  • DB schema not created: No data or errors. Run the SQL in Supabase dashboard (this is outside Lovable).
  • Exposing keys client-side: Keep Supabase SERVICE_ROLE_KEY only in server API; do not embed it in client code. Use server API routes to protect keys.
  • Large candidate set performance: This example fetches candidates then scores client-side; for production move scoring into a Supabase SQL function or external service (requires GitHub export and external deployment).

 

Validity bar

 

  • This workflow uses Lovable Chat Mode edits, Preview and Publish, and Lovable Cloud Secrets — all Lovable-native. Creating DB schema and running SQL must be done in Supabase (outside Lovable).

 

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 audit logging to a Lovable recommendations engine

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

AI AI Prompt

How to add Lovable-native rate limiting to a recommendation engine

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

AI AI Prompt

How to add deterministic tag-based caching to Recommendations

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 Recommendations engine with AI Code Generators

 

Best practice summary

 

Use embeddings + metadata filters for retrieval, generate candidate recommendations with a small/cheap model, personalize with user/item embeddings, run offline batch pipelines for indexing, and keep secrets & deployments inside Lovable (Secrets UI, Preview, Publish, GitHub sync) while doing heavy lifting (migrations, long jobs) via external CI or hosted workers.

  • Store API keys in Lovable Secrets and never hard-code them.
  • Build a reproducible offline pipeline to embed items, store vectors (Supabase/Pinecone), and version data in GitHub.
  • Use retrieval + lightweight reranker (embedding similarity → small LLM or heuristic) to be cost-effective and deterministic.
  • Monitor metrics and data drift (CTR, precision@k, MRR) and run A/B tests via feature flags or Git branches synced from Lovable to GitHub.

 

Concrete, minimal working example (Lovable-friendly)

 

The code below shows generating an OpenAI embedding and doing a semantic similarity search across a candidate subset fetched from Supabase (safe when you can’t run server-side vector SQL in Lovable). Store keys via Lovable Secrets and use Preview to test.

// // Node/Express route example
import express from 'express'
import OpenAI from 'openai'
import { createClient } from '@supabase/supabase-js'

const app = express()
app.use(express.json())

// // Secrets loaded by Lovable into env: process.env.OPENAI_KEY, SUPABASE_URL, SUPABASE_KEY
const openai = new OpenAI({ apiKey: process.env.OPENAI_KEY })
const supabase = createClient(process.env.SUPABASE_URL, process.env.SUPABASE_KEY)

// // cosine similarity helper
function cosine(a, b) {
  let dot = 0, na = 0, nb = 0
  for (let i=0;i<a.length;i++){ dot += a[i]*b[i]; na += a[i]*a[i]; nb += b[i]*b[i] }
  return dot / (Math.sqrt(na)*Math.sqrt(nb) + 1e-10)
}

app.post('/recommend', async (req, res) => {
  const { userId, textQuery, category } = req.body

  // // embed query
  const embResp = await openai.embeddings.create({
    model: 'text-embedding-3-small',
    input: textQuery
  })
  const qVec = embResp.data[0].embedding

  // // fetch candidates by metadata (limit to N to avoid fetching whole table)
  const { data: items } = await supabase
    .from('items')
    .select('id,title,description,embedding')
    .ilike('category', category ?? '%')
    .limit(200)

  // // compute similarities in JS
  const scored = items.map(it => ({ ...it, score: cosine(qVec, it.embedding) }))
    .sort((a,b)=>b.score - a.score)
    .slice(0, 10)

  return res.json({ results: scored })
})

app.listen(3000)

 

Operational tips for Lovable

 

  • Secrets UI: Put SUPABASE_URL, SERVICE_KEY, OPENAI\_KEY there. Preview/PUBLISH will read them. Never paste keys into code.
  • No terminal in Lovable: If you need DB migrations or long jobs, export to GitHub and run migrations in CI (GitHub Actions) or run Supabase migrations from the Supabase UI.
  • Preview & Patch: Use Chat Mode edits and file diffs to iterate quickly, then Preview your endpoints; when stable, Publish or sync to GitHub for production workflows.
  • Cost & latency: Use smaller embedding models for indexing, batch embeddings offline, cache recommendations (Redis/expires) and use a small reranker (cheap LLM) only for top-K items.
  • Privacy: Strip PII before sending to LLMs; use hashed user IDs for embeddings; document retention and deletion flows.

 


Recognized by the best

Trusted by 600+ businesses globally

From startups to enterprises and everything in between, see for yourself our incredible impact.

RapidDev 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.

Arkady
CPO, Praction
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!

Donald Muir
Co-Founder, Arc
RapidDev 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.

Mat Westergreen-Thorne
Co-CEO, Grantify
RapidDev is an excellent developer for custom-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.

Emmanuel Brown
Co-Founder, Church Real Estate Marketplace
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!

Samantha Fekete
Production Manager, Media Production Company
The pSEO strategy executed by RapidDev is clearly driving meaningful results.

Working with RapidDev has delivered measurable, year-over-year growth. Comparing the same period, clicks increased by 129%, impressions grew by 196%, and average position improved by 14.6%. Most importantly, qualified contact form submissions rose 350%, excluding spam.

Appreciation as well to Matt Graham for championing the collaboration!

Michael W. Hammond
Principal Owner, OCD Tech

We put the rapid in RapidDev

Need a dedicated strategic tech and growth partner? Discover what RapidDev can do for your business! Book a call with our team to schedule a free, no-obligation consultation. We’ll discuss your project and provide a custom quote at no cost.