Module 4
Topic 3

Database (PostgreSQL)

Working with PostgreSQL in Supabase — tables, CRUD operations, queries, and relationships.

Supabase Database Overview Querying Data Insert Data
PostgreSQL in Supabase

Supabase uses PostgreSQL as its database — the world's most advanced open-source relational database. Unlike Firestore (NoSQL), PostgreSQL is a relational database with:

  • Tables – Structured data with rows and columns
  • Relationships – Foreign keys and JOINs
  • SQL – Powerful query language
  • Transactions – ACID compliance
  • Indexes – Fast queries
  • Constraints – Data integrity

✅ PostgreSQL vs Firestore (NoSQL)

  • PostgreSQL – Relational, complex queries, transactions, strong consistency
  • Firestore – Document-based, simpler queries, eventual consistency
  • PostgreSQL – Better for complex data relationships
  • Firestore – Better for simple, hierarchical data
Database Schema

A database schema defines the structure of your data. Here's a common schema for a blog app:

users
id (UUID), email, username, created_at
posts
id, user_id (FK), title, content, created_at
comments
id, post_id (FK), user_id (FK), content, created_at
likes
id, post_id (FK), user_id (FK), created_at
CRUD Operations

Supabase provides a fluent API for CRUD operations. Here's how to perform them in Flutter.

Querying Data

Supabase supports powerful query operations including filtering, sorting, and limiting.

Relationships (Foreign Keys)

PostgreSQL supports powerful relationships between tables using foreign keys.

🔑 Foreign Key Types

  • ON DELETE CASCADE – Delete child rows when parent is deleted
  • ON DELETE SET NULL – Set foreign key to NULL when parent is deleted
  • ON DELETE RESTRICT – Prevent deletion if child rows exist
Complete Example: Blog App

Here's a complete blog app using Supabase PostgreSQL with all CRUD operations.

Step-by-Step Explanation
1.
Define your schema – Create tables in the Supabase SQL Editor with proper relationships and constraints.
2.
Create the service – Build a service class that wraps all database operations.
3.
INSERT (Create) – Use .insert() with .select() to create and return data.
4.
SELECT (Read) – Use .select() with relationships using dot notation ( '*, profiles(username)' ).
5.
UPDATE – Use .update() with .eq() and .select() .
6.
DELETE – Use .delete() with .eq() to remove rows.
Common Mistakes
❌ Mistake 1: Not using .select() after insert/update

Without .select() , you won't get the created/updated data back with default values like created_at .

✅ Correct: Use .select() after mutations

.insert(data).select().single() returns the newly created row.

❌ Mistake 2: Not handling null relationships

When using .select('*, profiles(username)') , the profiles field may be null if no related profile exists.

✅ Correct: Handle null relationships

Use profile?['username'] ?? 'Unknown' to handle null values safely.

❌ Mistake 3: Not using RLS policies

Without Row Level Security, users can read and modify data they shouldn't have access to.

✅ Correct: Always enable RLS

Enable RLS in the Supabase dashboard and create policies for each table and operation.

🎯 Key Takeaway

Supabase uses PostgreSQL for data storage, providing powerful relational database capabilities. You can perform CRUD operations with a fluent API, complex queries with filtering and sorting, and relationships with foreign keys. Always use .select() after mutations to get the updated data.