Supabase Setup for Authentication and Database
Wire up Supabase as your backend in under an hour — authentication, Postgres, row-level security, and a typed client. Real-world patterns for a Next.js app.
Supabase is the open-source Firebase alternative — managed Postgres, built-in auth, generated REST and GraphQL APIs, real-time subscriptions, and edge functions. For a small SaaS or side project it removes most of the backend boilerplate. This tutorial sets it up cleanly with a Next.js front end.
Create a project
- Sign in to
app.supabase.com. - Click "New project", pick a region close to your users, set a strong database password, and wait two minutes for provisioning.
- Copy the project URL and anon key from Project Settings → API.
Wire up Next.js
pnpm add @supabase/supabase-js @supabase/ssr
# .env.local
NEXT_PUBLIC_SUPABASE_URL=https://xxx.supabase.co
NEXT_PUBLIC_SUPABASE_ANON_KEY=ey...
// src/lib/supabase/server.ts
import { createServerClient } from "@supabase/ssr";
import { cookies } from "next/headers";
export async function supabaseServer() {
const cookieStore = await cookies();
return createServerClient(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
{
cookies: {
getAll: () => cookieStore.getAll(),
setAll: (cs) => cs.forEach((c) => cookieStore.set(c.name, c.value, c.options)),
},
},
);
}
Authentication in three lines
import { supabaseServer } from "@/lib/supabase/server";
export default async function Page() {
const supabase = await supabaseServer();
const { data } = await supabase.auth.getUser();
return <p>Hello, {data.user?.email ?? "guest"}</p>;
}
Tables with row-level security
Open the Supabase dashboard → SQL editor. Create a table and turn on RLS so users can only access their own rows.
CREATE TABLE notes (
id BIGSERIAL PRIMARY KEY,
user_id UUID NOT NULL REFERENCES auth.users(id) ON DELETE CASCADE,
body TEXT NOT NULL,
created_at TIMESTAMPTZ DEFAULT NOW()
);
ALTER TABLE notes ENABLE ROW LEVEL SECURITY;
CREATE POLICY "Users see only their notes"
ON notes FOR SELECT
USING (auth.uid() = user_id);
CREATE POLICY "Users insert their own notes"
ON notes FOR INSERT
WITH CHECK (auth.uid() = user_id);
Generated TypeScript types
pnpm dlx supabase gen types typescript --project-id <ref> > src/lib/supabase/types.ts
Now supabase.from("notes").select() is fully typed — no manual interfaces to maintain.
Real-time subscriptions
supabase
.channel("notes")
.on("postgres_changes", { event: "INSERT", schema: "public", table: "notes" }, (payload) => {
console.log("new note:", payload.new);
})
.subscribe();
When Supabase fits and when it doesn't
- Great for: SaaS MVPs, side projects, anything CRUD-heavy with auth.
- Less great for: heavy custom backend logic, intensive write workloads at scale, multi-region needs that Supabase doesn't yet cover natively.
Continue reading
Related essays
Setting Up Next.js 16 with TypeScript: A Modern Project Walkthrough
Spin up a fresh Next.js 16 project with TypeScript, Tailwind, and a sane folder layout — and understand the App Router conventions before you write your first feature.
Tailwind CSS v4 Project Setup: The Modern Configuration
Tailwind v4 changed how configuration works — no more <code>tailwind.config.js</code> by default. Here's the modern setup for any Vite or Next.js project, explained.
PostgreSQL Local Setup on macOS with Homebrew
Install PostgreSQL locally without Docker, create a development database, and configure your shell — the cleanest path for daily Mac development.