Common Supabase Issues in AI-Generated Code
Supabase is a popular backend choice for vibe coding, but AI tools often generate Supabase code with subtle issues. Here's what to watch for and how to fix it.
1. Row Level Security (RLS) Problems
Issue: RLS Not Enabled
AI often creates tables without enabling RLS, leaving data exposed.
-- AI-generated (INSECURE)
CREATE TABLE projects (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
title TEXT NOT NULL,
owner_id UUID REFERENCES auth.users(id)
);
-- ALWAYS enable RLS
ALTER TABLE projects ENABLE ROW LEVEL SECURITY;
Issue: Overly Permissive Policies
-- BAD: Anyone can read everything
CREATE POLICY "Public read" ON projects FOR SELECT USING (true);
-- GOOD: Users can only read their own projects
CREATE POLICY "Users read own projects" ON projects
FOR SELECT USING (auth.uid() = owner_id);
-- GOOD: Or projects they're invited to
CREATE POLICY "Users read accessible projects" ON projects
FOR SELECT USING (
auth.uid() = owner_id OR
auth.uid() IN (
SELECT user_id FROM project_members WHERE project_id = projects.id
)
);
Complete RLS Setup
-- Enable RLS
ALTER TABLE projects ENABLE ROW LEVEL SECURITY;
-- SELECT: Users can view their own projects
CREATE POLICY "select_own_projects" ON projects
FOR SELECT USING (auth.uid() = owner_id);
-- INSERT: Users can create projects
CREATE POLICY "insert_own_projects" ON projects
FOR INSERT WITH CHECK (auth.uid() = owner_id);
-- UPDATE: Users can update their own projects
CREATE POLICY "update_own_projects" ON projects
FOR UPDATE USING (auth.uid() = owner_id);
-- DELETE: Users can delete their own projects
CREATE POLICY "delete_own_projects" ON projects
FOR DELETE USING (auth.uid() = owner_id);
2. Authentication Issues
Issue: Not Checking Auth State
// BAD: No auth check
async function createProject(data: ProjectData) {
const { error } = await supabase
.from('projects')
.insert(data);
}
// GOOD: Verify user is authenticated
async function createProject(data: ProjectData) {
const { data: { user } } = await supabase.auth.getUser();
if (!user) {
throw new Error('Must be logged in to create a project');
}
const { error } = await supabase
.from('projects')
.insert({ ...data, owner_id: user.id });
if (error) throw error;
}
Issue: Using getSession Instead of getUser
// BAD: getSession can return stale data
const { data: { session } } = await supabase.auth.getSession();
// GOOD: getUser validates with the server
const { data: { user } } = await supabase.auth.getUser();
3. Query Issues
Issue: Not Handling Errors
// BAD: Ignoring errors
const { data } = await supabase.from('projects').select('*');
// GOOD: Handle errors properly
const { data, error } = await supabase.from('projects').select('*');
if (error) {
console.error('Query failed:', error);
throw new Error('Failed to fetch projects');
}
Issue: N+1 Queries
// BAD: N+1 problem
const { data: projects } = await supabase.from('projects').select('*');
for (const project of projects) {
const { data: owner } = await supabase
.from('users')
.select('*')
.eq('id', project.owner_id)
.single();
}
// GOOD: Join in single query
const { data: projects } = await supabase
.from('projects')
.select(`
*,
owner:users(id, name, email, avatar_url)
`);
Issue: Missing .single() for Single Results
// BAD: Returns array when you want one item
const { data } = await supabase
.from('projects')
.select('*')
.eq('id', projectId);
// data is Project[] not Project
// GOOD: Use .single()
const { data, error } = await supabase
.from('projects')
.select('*')
.eq('id', projectId)
.single();
// data is Project | null
4. Real-time Subscription Issues
Issue: Memory Leaks
// BAD: No cleanup
useEffect(() => {
supabase
.channel('projects')
.on('postgres_changes', { event: '*', schema: 'public', table: 'projects' },
(payload) => console.log(payload))
.subscribe();
}, []);
// GOOD: Proper cleanup
useEffect(() => {
const channel = supabase
.channel('projects')
.on('postgres_changes',
{ event: '*', schema: 'public', table: 'projects' },
(payload) => setProjects(prev => [...prev, payload.new as Project])
)
.subscribe();
return () => {
supabase.removeChannel(channel);
};
}, []);
5. Type Safety Issues
Generate Types from Schema
# Generate TypeScript types
npx supabase gen types typescript --project-id your-project-id > src/types/database.ts
Use Generated Types
import { Database } from '@/types/database';
type Project = Database['public']['Tables']['projects']['Row'];
type NewProject = Database['public']['Tables']['projects']['Insert'];
const supabase = createClient<Database>(url, key);
// Now fully typed
const { data } = await supabase
.from('projects')
.select('*')
.returns<Project[]>();
Quick Reference: Common Fixes
| Issue | Fix | |-------|-----| | "permission denied" | Check RLS policies | | "JWT expired" | Refresh session or re-authenticate | | "relation does not exist" | Run migrations, check table name | | Empty results | Check RLS, verify data exists | | Type errors | Regenerate types from schema |
Stuck on a Supabase issue? Get expert help on CoderVibez.