Backend & Data

PostgreSQL (Legacy)

PostgreSQL (often called "Postgres") is an advanced, enterprise-class database system with over 30 years of active development. Key features include:

PostgreSQL (Legacy)

PostgreSQL is a powerful, open-source relational database management system that forms the foundation of Supabase. VibeReference's new default backend uses Convex instead of Postgres. Keep this as reference if you maintain legacy projects.

Introduction to PostgreSQL

PostgreSQL (often called "Postgres") is an advanced, enterprise-class database system with over 30 years of active development. Key features include:

  • ACID Compliance: Ensuring data validity despite errors or failures
  • Advanced Data Types: Including JSON, arrays, hstore, and geometric types
  • Extensibility: Custom functions, operators, data types, and more
  • Concurrent Support: Multi-version concurrency control (MVCC)
  • Full-Text Search: Built-in indexing and searching capabilities
  • Advanced Indexing: B-tree, Hash, GiST, SP-GiST, GIN, and BRIN

Connection Through Supabase

In VibeReference, you'll typically interact with PostgreSQL through Supabase, which provides a user-friendly interface and API for database operations:

// lib/supabase/client.ts
import { createClient } from '@supabase/supabase-js';

export const supabase = createClient(
  process.env.NEXT_PUBLIC_SUPABASE_URL!,
  process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!
);

// Query example
const { data, error } = await supabase
  .from('your_table')
  .select('*')
  .eq('status', 'active');

Schema Design

Basic Schema Example

Here's a simple schema example for a VibeReference application:

-- Users table (extends Supabase auth.users)
CREATE TABLE public.profiles (
  id UUID REFERENCES auth.users(id) PRIMARY KEY,
  display_name TEXT,
  avatar_url TEXT,
  created_at TIMESTAMP WITH TIME ZONE DEFAULT now(),
  updated_at TIMESTAMP WITH TIME ZONE DEFAULT now()
);

-- Projects table
CREATE TABLE public.projects (
  id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
  name TEXT NOT NULL,
  description TEXT,
  owner_id UUID REFERENCES public.profiles(id) NOT NULL,
  is_public BOOLEAN DEFAULT false,
  created_at TIMESTAMP WITH TIME ZONE DEFAULT now(),
  updated_at TIMESTAMP WITH TIME ZONE DEFAULT now()
);

-- Project members junction table
CREATE TABLE public.project_members (
  project_id UUID REFERENCES public.projects(id) ON DELETE CASCADE,
  user_id UUID REFERENCES public.profiles(id) ON DELETE CASCADE,
  role TEXT NOT NULL CHECK (role IN ('admin', 'editor', 'viewer')),
  created_at TIMESTAMP WITH TIME ZONE DEFAULT now(),
  PRIMARY KEY (project_id, user_id)
);

Data Types

PostgreSQL supports a rich set of data types:

Data Type Description Example
TEXT Variable length text 'Hello, world!'
VARCHAR(n) Variable length with limit VARCHAR(100)
INTEGER 4-byte integer 42
BIGINT 8-byte integer 9223372036854775807
NUMERIC Exact decimal number NUMERIC(10,2) for currency
BOOLEAN True/false value TRUE, FALSE
UUID Universal unique identifier uuid_generate_v4()
TIMESTAMP Date and time TIMESTAMP WITH TIME ZONE
JSONB Binary JSON data '{"key": "value"}'
ARRAY Array of values INTEGER[], TEXT[]

Row Level Security (RLS)

PostgreSQL's Row Level Security feature is a cornerstone of securing your VibeReference application. RLS allows you to define policies that restrict which rows a user can access.

Example RLS Policy

-- Enable RLS on the projects table
ALTER TABLE public.projects ENABLE ROW LEVEL SECURITY;

-- Create policy for viewing projects
CREATE POLICY view_projects ON public.projects
  FOR SELECT
  USING (
    is_public OR                                  -- Public projects are visible to all
    owner_id = auth.uid() OR                      -- Owner can see their projects
    EXISTS (                                       -- Members can see their projects
      SELECT 1 FROM public.project_members
      WHERE project_id = projects.id AND user_id = auth.uid()
    )
  );

-- Create policy for updating projects
CREATE POLICY update_projects ON public.projects
  FOR UPDATE
  USING (
    owner_id = auth.uid() OR                      -- Owner can update
    EXISTS (                                       -- Admins and editors can update
      SELECT 1 FROM public.project_members
      WHERE project_id = projects.id 
      AND user_id = auth.uid() 
      AND role IN ('admin', 'editor')
    )
  );

Common PostgreSQL Operations

SELECT Queries

-- Basic select
SELECT * FROM projects WHERE is_public = true;

-- Joins
SELECT p.name, pr.display_name as owner
FROM projects p
JOIN profiles pr ON p.owner_id = pr.id;

-- Aggregation
SELECT COUNT(*), owner_id 
FROM projects 
GROUP BY owner_id
HAVING COUNT(*) > 5;

-- Window functions
SELECT 
  p.name, 
  p.created_at,
  ROW_NUMBER() OVER(PARTITION BY p.owner_id ORDER BY p.created_at DESC) as row_num
FROM projects p;

Data Manipulation

-- Insert
INSERT INTO projects (name, description, owner_id, is_public)
VALUES ('New Project', 'Description here', 'user-uuid', true);

-- Update
UPDATE projects 
SET name = 'Updated Name', updated_at = now()
WHERE id = 'project-uuid';

-- Delete
DELETE FROM projects WHERE id = 'project-uuid';

Using JSON

-- Query JSON data
SELECT data->>'name' as name
FROM resources
WHERE data->>'type' = 'document';

-- Update JSON data
UPDATE resources 
SET data = jsonb_set(data, '{status}', '"archived"')
WHERE id = 'resource-uuid';

Real-time Features with Supabase

PostgreSQL's LISTEN/NOTIFY feature powers Supabase's real-time capabilities:

// Subscribe to changes on the projects table
const channel = supabase
  .channel('schema-db-changes')
  .on(
    'postgres_changes',
    {
      event: '*', // Listen to all changes
      schema: 'public',
      table: 'projects',
    },
    (payload) => {
      console.log('Change received!', payload);
      // Update UI or state based on the change
    }
  )
  .subscribe();

Migrations and Schema Management

For VibeReference projects, it's recommended to manage your PostgreSQL schema using migration files:

-- migrations/001_initial_schema.sql
CREATE TABLE public.profiles (
  id UUID REFERENCES auth.users(id) PRIMARY KEY,
  display_name TEXT,
  avatar_url TEXT,
  created_at TIMESTAMP WITH TIME ZONE DEFAULT now(),
  updated_at TIMESTAMP WITH TIME ZONE DEFAULT now()
);

-- More table definitions...

-- Apply migrations from your application or using Supabase migrations

Performance Optimization

Indexing

-- B-tree index (default, good for equality and range queries)
CREATE INDEX idx_projects_owner ON projects(owner_id);

-- Unique index
CREATE UNIQUE INDEX idx_projects_name_owner ON projects(name, owner_id);

-- Partial index
CREATE INDEX idx_active_projects ON projects(created_at) 
WHERE is_archived = false;

-- Text search index
CREATE INDEX idx_projects_description_gin ON projects 
USING GIN (to_tsvector('english', description));

Query Optimization

  • Use EXPLAIN ANALYZE to understand query execution plans
  • Consider denormalization for frequently accessed data
  • Use appropriate indexes for your query patterns
  • Keep statistics up to date with regular ANALYZE

Resources

Ready to build?

Go from idea to launched product in a week with AI-assisted development.