Environment, Setup & Your First Database Query
Environment, Setup & Your First Database Query
What is Supabase? First Principles
Supabase is often called an "open-source Firebase alternative." That's accurate but undersells it. At its core, Supabase is a real PostgreSQL database with a complete developer platform built on top of it.
The architecture from your app to the database looks like this:
Your App (Next.js / React / Mobile)
│
▼
@supabase/supabase-js (JS client)
│
├── REST API (PostgREST) ──────► Postgres database
├── Auth (GoTrue) ──────────────► auth.users table
├── Storage ────────────────────► S3-compatible buckets
└── Realtime ───────────────────► WebSocket serverThe JS client translates .from('courses').select('*').eq('track', 'crash') into a REST API call, which PostgREST converts into SQL and executes against Postgres. You get back JSON. You never write raw SQL from the frontend.
Every surface — REST API, Realtime, Storage — is protected by the same Row Level Security policies you write once in SQL. This is Supabase's core security primitive, and it's the reason you can query the database directly from the browser using the anon key.
VS Code Setup
Install the Supabase extension for VS Code (publisher: Supabase). It provides SQL autocomplete in migration files, a database table explorer in the editor sidebar, and edge function tooling.
- Open VS Code → Extensions (Ctrl+Shift+X / Cmd+Shift+X)
- Search "Supabase" and install the official extension
- Connect it to your project from the extension panel
Creating a Supabase Project
- Go to [supabase.com/dashboard](https://supabase.com/dashboard) and sign in
- Click New Project — choose organization, name, database password, region
- Wait ~2 minutes for provisioning
- Go to Settings → API to find your Project URL and API keys
Installing the SDK
# Core SDK — works anywhere
npm install @supabase/supabase-js
# For Next.js App Router (handles cookie-based sessions)
npm install @supabase/ssrEnvironment Variables
# .env.local — add to .gitignore, never commit
NEXT_PUBLIC_SUPABASE_URL=https://your-project-id.supabase.co
NEXT_PUBLIC_SUPABASE_ANON_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
SUPABASE_SERVICE_ROLE_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...NEXT_PUBLIC_ prefix exposes the variable to browser code. The service role key has no NEXT_PUBLIC_ prefix — it must never reach the browser. It bypasses all RLS policies.
The Multi-File Pattern
Real projects use three connected files that separate concerns cleanly:
// lib/supabase.ts — creates a typed client singleton
import { createClient } from '@supabase/supabase-js'
import type { Database } from '@/types/database'
export const supabase = createClient<Database>(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!
)# types/database.ts — generate TypeScript types from your live schema
npx supabase gen types typescript --project-id <your-id> > src/types/database.ts// app/page.tsx — uses the typed client
import { supabase } from '@/lib/supabase'
export default async function Page() {
const { data: courses, error } = await supabase
.from('courses')
.select('*')
.eq('track', 'crash')
.order('module', { ascending: true })
if (error) throw error
return <CourseList courses={courses} />
}The Database generic makes every query type-safe. TypeScript knows the shape of every table and column — wrong column names and type mismatches become compile errors.
The {data, error} Pattern
Every Supabase operation returns a Promise resolving to { data, error }. Always check error before using data. When error is not null, data is null.
const { data, error } = await supabase
.from('courses')
.select('id, title, xp')
.eq('track', 'crash')
.order('module', { ascending: true })
if (error) {
console.error('Query failed:', error.message)
return
}
// data is guaranteed to be non-null here
console.log(`Found ${data.length} courses`)
data.forEach(course => console.log(` ${course.title} — ${course.xp} XP`))This pattern is consistent across every Supabase operation — queries, inserts, updates, deletes, auth, storage. Once you know it for one, you know it for all.
Generating TypeScript Types
Your Supabase schema is the source of truth. Regenerate types whenever you change your database schema:
npx supabase gen types typescript \
--project-id abcdefghijklmnop \
> src/types/database.tsNow supabase.from('courses') is fully typed — TypeScript enforces column names, filters, and return shapes at compile time.