JSTAcademy
0 XP
Dashboard
Crash Courses
Environment, Setup & Your First Database Query
12 min
Basic+150 XP
Crash Courses · Basic

Environment, Setup & Your First Database Query

Understand what Supabase is, set up your project and VS Code environment, and write your first typed query.
12 min read+150 XP on completionCert: Supabase Crash Course
Tap any word in the text below to start reading from there.

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 server

The 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.

  1. Open VS Code → Extensions (Ctrl+Shift+X / Cmd+Shift+X)
  2. Search "Supabase" and install the official extension
  3. Connect it to your project from the extension panel

Creating a Supabase Project

  1. Go to [supabase.com/dashboard](https://supabase.com/dashboard) and sign in
  2. Click New Project — choose organization, name, database password, region
  3. Wait ~2 minutes for provisioning
  4. 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/ssr

Environment 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.ts

Now supabase.from('courses') is fully typed — TypeScript enforces column names, filters, and return shapes at compile time.

0%