Supabase
01 / 03

Supabase Essentials

Supabase Essentials

Client Setup & Auth

import { createClient } from '@supabase/supabase-js';
import type { Database } from './database.types'; // generated types

const supabase = createClient<Database>(
  process.env.NEXT_PUBLIC_SUPABASE_URL!,
  process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!
);

// Auth
const { data, error } = await supabase.auth.signUp({
  email: 'user@example.com',
  password: 'password123',
  options: { data: { full_name: 'Alice' } },
});

const { error } = await supabase.auth.signInWithPassword({
  email: 'user@example.com',
  password: 'password123',
});

await supabase.auth.signInWithOAuth({ provider: 'github' });
await supabase.auth.signOut();

// Get current user
const { data: { user } } = await supabase.auth.getUser();
const { data: { session } } = await supabase.auth.getSession();

// Listen to auth state changes
supabase.auth.onAuthStateChange((event, session) => {
  if (event === 'SIGNED_IN') console.log('User signed in', session?.user);
  if (event === 'SIGNED_OUT') console.log('User signed out');
});

Database Queries

// SELECT
const { data, error } = await supabase
  .from('posts')
  .select('id, title, body, created_at, author:users(name, email)')
  .eq('status', 'published')
  .order('created_at', { ascending: false })
  .range(0, 19);   // pagination: rows 0-19

// Filters
.eq('id', userId)
.neq('status', 'deleted')
.gt('views', 100)
.gte('age', 18)
.in('role', ['admin', 'moderator'])
.like('name', '%alice%')
.ilike('name', '%alice%')          // case-insensitive
.is('deleted_at', null)
.not('status', 'eq', 'hidden')
.or('role.eq.admin,role.eq.moderator')

// INSERT
const { data, error } = await supabase
  .from('posts')
  .insert({ title: 'Hello', body: 'World', author_id: user.id })
  .select()
  .single();

// UPSERT
await supabase
  .from('profiles')
  .upsert({ id: user.id, username: 'alice' }, { onConflict: 'id' });

// UPDATE
await supabase
  .from('posts')
  .update({ status: 'published' })
  .eq('id', postId)
  .eq('author_id', userId);  // RLS-style safety

// DELETE
await supabase
  .from('posts')
  .delete()
  .eq('id', postId);

// Count
const { count } = await supabase
  .from('posts')
  .select('*', { count: 'exact', head: true })
  .eq('status', 'published');

Row Level Security & Realtime

-- Row Level Security (RLS) — SQL policies
-- Enable RLS on table
ALTER TABLE posts ENABLE ROW LEVEL SECURITY;

-- Policy: users can only read their own posts
CREATE POLICY "users read own posts"
  ON posts FOR SELECT
  USING (auth.uid() = author_id);

-- Policy: users can insert posts as themselves
CREATE POLICY "users insert own posts"
  ON posts FOR INSERT
  WITH CHECK (auth.uid() = author_id);

-- Policy: everyone can read published posts
CREATE POLICY "public read published"
  ON posts FOR SELECT
  USING (status = 'published');

-- Policy: admins can do anything
CREATE POLICY "admins full access"
  ON posts
  USING (auth.jwt() ->> 'role' = 'admin');
// Realtime subscriptions
const channel = supabase
  .channel('posts-changes')
  .on(
    'postgres_changes',
    { event: '*', schema: 'public', table: 'posts', filter: `author_id=eq.${userId}` },
    (payload) => {
      if (payload.eventType === 'INSERT') addPost(payload.new);
      if (payload.eventType === 'UPDATE') updatePost(payload.new);
      if (payload.eventType === 'DELETE') removePost(payload.old.id);
    }
  )
  .subscribe();

// Cleanup
supabase.removeChannel(channel);

// Storage
const { data, error } = await supabase.storage
  .from('avatars')
  .upload(`${userId}/avatar.png`, file, { upsert: true });

const { data: { publicUrl } } = supabase.storage
  .from('avatars')
  .getPublicUrl(`${userId}/avatar.png`);

Keep your own version of these notes — editable, searchable, and organised by your stack.

Start free