Supabase for Everything
AssessAI's entire backend is Supabase. Auth, Postgres database, Row Level Security, real-time subscriptions, edge functions, storage. That's six infrastructure concerns handled by one service with one dashboard and one set of credentials.
Here's how each piece works in practice and where the edges are.
auth: 10 minutes to done
Supabase Auth provides email/password, magic links, OAuth (Google, GitHub, etc.), and JWT-based sessions out of the box. For AssessAI, I use email/password for admins and magic links for candidates.
The setup:
import { createClient } from "@supabase/supabase-js";
const supabase = createClient(SUPABASE_URL, SUPABASE_ANON_KEY);
// Sign up
const { data, error } = await supabase.auth.signUp({
email: "admin@company.com",
password: "securepassword",
});
// Sign in
const { data, error } = await supabase.auth.signInWithPassword({
email: "admin@company.com",
password: "securepassword",
});That's it. No password hashing. No session management. No token rotation. No "forgot password" flow implementation. Supabase handles all of it. The JWT is available on every request and integrates directly with Row Level Security.
row level security: the killer feature
RLS policies define who can see and modify which rows. These are enforced at the database level, not in your application code. Even if you write a buggy API route that forgets an auth check, the database won't return unauthorized data.
-- Users can only read their own assessments
CREATE POLICY "own_assessments_select"
ON assessments FOR SELECT
USING (auth.uid() = created_by);
-- Users can only update their own assessments
CREATE POLICY "own_assessments_update"
ON assessments FOR UPDATE
USING (auth.uid() = created_by);
-- Candidates can read assessments they're invited to
CREATE POLICY "candidate_assessment_read"
ON assessments FOR SELECT
USING (
id IN (
SELECT assessment_id FROM invitations
WHERE candidate_email = auth.jwt() ->> 'email'
)
);For a solo developer without a security team, RLS is the difference between "I hope I remembered to check auth everywhere" and "the database enforces auth regardless of my code."
I have 11 migrations on AssessAI. Every table has RLS policies. The security model is in the schema, not scattered across middleware functions.
edge functions: serverless with full access
Edge functions are Deno-based serverless functions that run close to the user. They have full access to the Supabase client, including admin privileges.
I use them for:
Webhook handlers. Payment webhooks from Razorpay hit an edge function that verifies the signature, updates the subscription, and sends a confirmation email. All in one function with direct database access.
Background processing. Assessment scoring runs as an edge function. The candidate submits, the API returns immediately, and the edge function scores asynchronously. No queue. No worker infrastructure.
Scheduled jobs. Cron-triggered edge functions for cleanup tasks, reminder emails, and data aggregation.
// supabase/functions/score-assessment/index.ts
import { serve } from "https://deno.land/std/http/server.ts";
import { createClient } from "https://esm.sh/@supabase/supabase-js";
serve(async (req) => {
const { session_id } = await req.json();
const supabase = createClient(
Deno.env.get("SUPABASE_URL")!,
Deno.env.get("SUPABASE_SERVICE_ROLE_KEY")!
);
// Score the assessment...
const scores = await evaluateResponses(session_id);
await supabase
.from("scores")
.upsert({ session_id, ...scores });
return new Response(JSON.stringify({ ok: true }));
});Deploy with supabase functions deploy score-assessment. Done.
realtime: live updates without websocket code
Supabase Realtime lets you subscribe to database changes over websockets. When a row is inserted, updated, or deleted, connected clients get notified immediately.
For AssessAI, this powers the live scoring dashboard. When a candidate's assessment is scored, the admin dashboard updates in real time without polling:
const channel = supabase
.channel("scores")
.on(
"postgres_changes",
{ event: "INSERT", schema: "public", table: "scores" },
(payload) => {
// Update the dashboard with new scores
addScore(payload.new);
}
)
.subscribe();No WebSocket server setup. No connection management. No heartbeat logic. It's a database subscription that looks like a React hook.
where it breaks down
Complex queries. Supabase's query builder is good for CRUD. For complex joins, window functions, or CTEs, I write raw SQL through supabase.rpc() or direct SQL via the MCP server. The query builder's abstraction leaks on anything beyond basic operations.
Large file processing. Supabase Storage is fine for profile pictures and documents. For video processing, large file transformations, or ML model serving, you need dedicated infrastructure.
Custom auth flows. Supabase Auth handles standard patterns well. If you need something unusual — like AssessAI's candidate magic links that also carry assessment context — you end up building custom logic around the auth system, which partially defeats the purpose.
Vendor lock-in. This is the real tradeoff. RLS policies, edge functions, realtime subscriptions — these are Supabase-specific. Moving to another provider means rewriting your security model, your serverless functions, and your real-time infrastructure.
I accept this tradeoff explicitly. At the early stage, shipping speed matters more than portability. If AssessAI reaches the scale where Supabase is a bottleneck, migration is a good problem to have.
the bottom line
Supabase replaces five services with one. For solo developers building SaaS products, that's not just convenience — it's the difference between spending your time on infrastructure and spending it on the product. I'd rather think about assessment scoring algorithms than WebSocket connection management.
One service. One dashboard. One bill. Ship the product.
More in Tools
My Claude Code Setup: 68 Commands, 15 Agents, 86 Skills
How I turned Claude Code into a complete engineering team in a terminal.
Local TTS at Zero Cost
Kokoro-82M: 337MB model, broadcast-quality speech, runs on a MacBook, costs nothing. Here's how to set it up.
Automating Everything with Hooks
Claude Code hooks: auto-format, auto-test, auto-commit, auto-sync. My full setup for turning manual chores into background processes.