Supabase database in Next.js — from zero to CRUD

Set up Supabase as your database in Next.js. Create a table, insert data, read rows, update, and delete — with full code for each step. No SQL experience needed.

Why Supabase for your database?

Supabase gives you a PostgreSQL database with a simple JavaScript API. No SQL needed for basic operations — you use supabase.from("table").select() and it just works. It also comes with real-time subscriptions and Row Level Security so your data is safe.


Step 1: Create a table

In the Supabase Dashboard, go to Table EditorNew Table. Let's create a simple "todos" table:

SQL Editor in Supabase
-- You can run this in Supabase SQL Editor
-- or just use the Table Editor UI

create table todos (
  id uuid default gen_random_uuid() primary key,
  user_id uuid references auth.users(id),
  title text not null,
  done boolean default false,
  created_at timestamptz default now()
);

-- Enable Row Level Security
alter table todos enable row level security;

-- Users can only see their own todos
create policy "Users see own todos"
  on todos for select
  using (auth.uid() = user_id);

-- Users can only insert their own todos
create policy "Users insert own todos"
  on todos for insert
  with check (auth.uid() = user_id);

Step 2: Read data (SELECT)

Read data
// app/dashboard/page.tsx — Server Component
import { createClient } from "@/libs/supabase/server";

export default async function Dashboard() {
  const supabase = await createClient();

  const { data: todos, error } = await supabase
    .from("todos")
    .select("*")
    .order("created_at", { ascending: false });

  if (error) {
    return <p>Error loading todos</p>;
  }

  return (
    <ul>
      {todos?.map((todo) => (
        <li key={todo.id}>{todo.title}</li>
      ))}
    </ul>
  );
}

Step 3: Insert data (INSERT)

Insert data
// app/api/todos/route.ts
import { createClient } from "@/libs/supabase/server";
import { NextRequest, NextResponse } from "next/server";

export async function POST(req: NextRequest) {
  const supabase = await createClient();
  const { data: { user } } = await supabase.auth.getUser();

  if (!user) {
    return NextResponse.json({ error: "Not logged in" }, { status: 401 });
  }

  const { title } = await req.json();

  const { data, error } = await supabase
    .from("todos")
    .insert({ title, user_id: user.id })
    .select()
    .single();

  if (error) {
    return NextResponse.json({ error: error.message }, { status: 500 });
  }

  return NextResponse.json(data);
}

Step 4: Update data (UPDATE)

Update data
// Mark a todo as done
const { error } = await supabase
  .from("todos")
  .update({ done: true })
  .eq("id", todoId);

Step 5: Delete data (DELETE)

Delete data
// Delete a todo
const { error } = await supabase
  .from("todos")
  .delete()
  .eq("id", todoId);

That's CRUD

Create (insert), Read (select), Update, Delete — the four operations every app needs. Supabase makes each one a single line. Add authentication and Row Level Security, and your data is safe without writing any extra backend code.


Database already configured

Delfy comes with Supabase set up — profiles table, RLS policies, server/client helpers, and examples for every CRUD operation.

Get the boilerplate
Supabase database in Next.js — from zero to CRUD | Delfy.dev Blog