mgd34msu/goodvibes-gemini

supabase-db

Implements Supabase PostgreSQL database with JavaScript client, Row Level Security, real-time subscriptions, and edge functions. Use when building apps with Supabase, implementing RLS policies, or needing real-time database features.

First seen Mar 29, 2026

Installation

$ npx skills add mgd34msu/goodvibes-gemini --skill supabase-db

Similar popular skills

Related neighbors and high-traction skills in the same topics — useful to compare before installing.

Also in this package

Other skills from mgd34msu/goodvibes-gemini · top by installs.

npx skills add mgd34msu/goodvibes-gemini

Browse all from mgd34msu/goodvibes-gemini

More details

Agent compatibility

Declared targets from SKILL.md / docs. Unmarked agents are not listed — the skill may still install via the CLI.

Claude Code Not declared
Cursor Not declared
Codex Not declared
GitHub Copilot Not declared
Windsurf Not declared
Gemini CLI Not declared
Cline Not declared
OpenCode Not declared

Repository health

Stars 4
License MIT
Default branch main
Open issues 0
Status Active

Package contents

Files included with this skill beyond the listing page.

  • skill md SKILL.md 8,796 B
  • docs SUMMARY.md 252 B

History

  1. First seen on skills.sh
  2. First recorded snapshot · 1 installs

SKILL.md

Supabase Database

Supabase provides a full PostgreSQL database with automatic REST/GraphQL APIs, real-time subscriptions, Row Level Security, and edge functions.

Quick Start

npm install @supabase/supabase-js
import { createClient } from '@supabase/supabase-js'

const supabase = createClient(
  process.env.SUPABASE_URL!,
  process.env.SUPABASE_ANON_KEY!
)

CRUD Operations

Select (Read)

// Select all columns
const { data, error } = await supabase
  .from('users')
  .select('*')

// Select specific columns
const { data, error } = await supabase
  .from('users')
  .select('id, name, email')

// Select with relations
const { data, error } = await supabase
  .from('posts')
  .select(`
    id,
    title,
    author:users(name, email),
    comments(id, body)
  `)

// Filtering
const { data, error } = await supabase
  .from('users')
  .select('*')
  .eq('status', 'active')
  .gt('age', 18)
  .order('created_at', { ascending: false })
  .limit(10)

// Single record
const { data, error } = await supabase
  .from('users')
  .select('*')
  .eq('id', userId)
  .single()

Insert (Create)

// Insert single record
const { data, error } = await supabase
  .from('users')
  .insert({
    name: 'John Doe',
    email: '[email protected]'
  })
  .select()

// Insert multiple records
const { data, error } = await supabase
  .from('users')
  .insert([
    { name: 'Alice', email: '[email protected]' },
    { name: 'Bob', email: '[email protected]' }
  ])
  .select()

// Upsert (insert or update)
const { data, error } = await supabase
  .from('users')
  .upsert({
    id: 1,
    name: 'Updated Name',
    email: '[email protected]'
  })
  .select()

Update

// Update by condition
const { data, error } = await supabase
  .from('users')
  .update({ status: 'inactive' })
  .eq('id', userId)
  .select()

// Update multiple records
const { data, error } = await supabase
  .from('users')
  .update({ verified: true })
  .in('id', [1, 2, 3])
  .select()

Delete

const { error } = await supabase
  .from('users')
  .delete()
  .eq('id', userId)

// Soft delete pattern
const { error } = await supabase
  .from('users')
  .update({ deleted_at: new Date().toISOString() })
  .eq('id', userId)

Row Level Security (RLS)

RLS enforces authorization at the database level. Always enable RLS on public tables.

Enable RLS

-- Enable RLS on table
ALTER TABLE users ENABLE ROW LEVEL SECURITY;

-- Force RLS for table owner too
ALTER TABLE users FORCE ROW LEVEL SECURITY;

Policy Patterns

-- Public read access
CREATE POLICY "Public profiles visible"
ON profiles FOR SELECT
TO anon, authenticated
USING (true);

-- Users can only read their own data
CREATE POLICY "Users read own data"
ON users FOR SELECT
TO authenticated
USING ((SELECT auth.uid()) = id);

-- Users can insert their own data
CREATE POLICY "Users create own profile"
ON profiles FOR INSERT
TO authenticated
WITH CHECK ((SELECT auth.uid()) = user_id);

-- Users can update their own data
CREATE POLICY "Users update own data"
ON profiles FOR UPDATE
TO authenticated
USING ((SELECT auth.uid()) = user_id)
WITH CHECK ((SELECT auth.uid()) = user_id);

-- Users can delete their own data
CREATE POLICY "Users delete own data"
ON profiles FOR DELETE
TO authenticated
USING ((SELECT auth.uid()) = user_id);

-- Role-based access using JWT claims
CREATE POLICY "Admins have full access"
ON users FOR ALL
TO authenticated
USING (
  (SELECT auth.jwt() ->> 'role') = 'admin'
);

-- Team-based access
CREATE POLICY "Team members access"
ON projects FOR SELECT
TO authenticated
USING (
  team_id IN (
    SELECT team_id FROM team_members
    WHERE user_id = (SELECT auth.uid())
  )
);

RLS Performance Tips

-- GOOD: Wrap auth functions in SELECT for caching
USING ((SELECT auth.uid()) = user_id)

-- BAD: Direct function call
USING (auth.uid() = user_id)

-- Add indexes on columns used in policies
CREATE INDEX idx_profiles_user_id ON profiles(user_id);

Real-time Subscriptions

// Subscribe to all changes
const channel = supabase
  .channel('table-changes')
  .on(
    'postgres_changes',
    {
      event: '*',
      schema: 'public',
      table: 'messages'
    },
    (payload) => {
      console.log('Change:', payload)
    }
  )
  .subscribe()

// Subscribe to specific events
const channel = supabase
  .channel('new-messages')
  .on(
    'postgres_changes',
    {
      event: 'INSERT',
      schema: 'public',
      table: 'messages',
      filter: 'room_id=eq.123'
    },
    (payload) => {
      console.log('New message:', payload.new)
    }
  )
  .subscribe()

// Broadcast messages (no database)
const channel = supabase.channel('room-1')
channel.on('broadcast', { event: 'cursor' }, (payload) => {
  console.log('Cursor:', payload)
})
await channel.subscribe()

// Send broadcast
channel.send({
  type: 'broadcast',
  event: 'cursor',
  payload: { x: 100, y: 200 }
})

// Cleanup
supabase.removeChannel(channel)

TypeScript Types

// Generate types from database
// npx supabase gen types typescript --project-id your-project > types/supabase.ts

import { Database } from './types/supabase'

const supabase = createClient<Database>(
  process.env.SUPABASE_URL!,
  process.env.SUPABASE_ANON_KEY!
)

// Typed queries
type User = Database['public']['Tables']['users']['Row']
type InsertUser = Database['public']['Tables']['users']['Insert']
type UpdateUser = Database['public']['Tables']['users']['Update']

const { data } = await supabase
  .from('users')
  .select('*')
  .returns<User[]>()

Database Functions (RPC)

-- Create a function
CREATE OR REPLACE FUNCTION get_user_stats(user_id UUID)
RETURNS JSON AS $$
  SELECT json_build_object(
    'post_count', (SELECT COUNT(*) FROM posts WHERE author_id = user_id),
    'follower_count', (SELECT COUNT(*) FROM follows WHERE following_id = user_id)
  );
$$ LANGUAGE SQL SECURITY DEFINER;
// Call the function
const { data, error } = await supabase
  .rpc('get_user_stats', { user_id: '123' })

Advanced Queries

// Full-text search
const { data } = await supabase
  .from('posts')
  .select('*')
  .textSearch('title', 'serverless database')

// Range queries
const { data } = await supabase
  .from('events')
  .select('*')
  .gte('start_date', '2024-01-01')
  .lte('end_date', '2024-12-31')

// Pattern matching
const { data } = await supabase
  .from('users')
  .select('*')
  .ilike('name', '%john%')

// JSON queries
const { data } = await supabase
  .from('users')
  .select('*')
  .contains('metadata', { role: 'admin' })

// Count
const { count } = await supabase
  .from('users')
  .select('*', { count: 'exact', head: true })

Server-Side Usage

// Use service role key for admin operations (bypasses RLS)
import { createClient } from '@supabase/supabase-js'

const supabaseAdmin = createClient(
  process.env.SUPABASE_URL!,
  process.env.SUPABASE_SERVICE_ROLE_KEY! // Keep secret!
)

// Admin operations bypass RLS
const { data } = await supabaseAdmin
  .from('users')
  .select('*')

Error Handling

const { data, error } = await supabase
  .from('users')
  .select('*')

if (error) {
  if (error.code === 'PGRST116') {
    // No rows found
  } else if (error.code === '42501') {
    // RLS policy violation
  } else {
    console.error('Database error:', error.message)
  }
  return
}

// data is available here

Common Patterns

Pagination

const PAGE_SIZE = 10

async function getPage(page: number) {
  const from = page * PAGE_SIZE
  const to = from + PAGE_SIZE - 1

  const { data, error, count } = await supabase
    .from('posts')
    .select('*', { count: 'exact' })
    .order('created_at', { ascending: false })
    .range(from, to)

  return {
    data,
    totalPages: Math.ceil((count || 0) / PAGE_SIZE)
  }
}

Cursor-Based Pagination

async function getNextPage(cursor?: string) {
  let query = supabase
    .from('posts')
    .select('*')
    .order('created_at', { ascending: false })
    .limit(10)

  if (cursor) {
    query = query.lt('created_at', cursor)
  }

  const { data } = await query
  const nextCursor = data?.[data.length - 1]?.created_at

  return { data, nextCursor }
}

References

  • [Row Level Security Patterns](references/rls-patterns.md)
  • [Real-time Best Practices](references/realtime.md)