Pular para o conteúdo principal

Cursor e Supabase: Desenvolvimento Full-Stack sem Backend

Supabase development in Cursor

O Supabase é uma alternativa open-source ao Firebase que fornece PostgreSQL, subscriptions em tempo real, autenticação, storage e edge functions. Combinado com os recursos de IA do Cursor, você pode acelerar o design de schemas, geração de clientes type-safe e implementação de fluxos de autenticação complexos. Este guia aborda fluxos de trabalho práticos para construir aplicações full-stack com Supabase.

Pré-requisitos​

Antes de começar, você precisará de:

  • Cursor instalado (versão mais recente recomendada)
  • Conta Supabase (projetos gratuitos disponíveis)
  • Node.js instalado
  • Noções básicas de conceitos PostgreSQL

Configurando o Projeto Supabase​

Criando um Novo Projeto​

  1. Vá para o Supabase Dashboard
  2. Clique em "New Project"
  3. Defina nome, senha do banco de dados e região
  4. Aguarde a criação do projeto

Configurando o Ambiente no Cursor​

# Instale o CLI do Supabase
npm install -g supabase

# Inicialize o projeto
npx supabase init

# Conecte ao projeto
npx supabase link --project-ref your-project-ref

# Gere tipos
npx supabase gen types typescript --project-id your-project-ref --schema public > src/types/supabase.ts
# .env.local
NEXT_PUBLIC_SUPABASE_URL=https://your-project.supabase.co
NEXT_PUBLIC_SUPABASE_ANON_KEY=your-anon-key
SUPABASE_SERVICE_ROLE_KEY=your-service-role-key
aviso

Nunca commite a service role key no .env.local. Nunca a exponha no lado do cliente; use-a apenas para operações server-side.

Design de Schema com IA​

Gerando DDL do Supabase​

Peça ao Cursor para projetar seu schema de banco de dados:

Prompt: "Design a Supabase schema for a task management app with users, projects, tasks, and tags. Include proper foreign keys, row-level security policies, and indexes. Use the Supabase SQL format."

Resultado gerado pelo Cursor:

-- Extensão de usuários (adiciona perfil a auth.users)
create table public.profiles (
id uuid references auth.users on delete cascade primary key,
username text unique not null,
full_name text,
avatar_url text,
updated_at timestamp with time zone default timezone('utc'::text, now())
);

-- Tabela de projetos
create table public.projects (
id uuid default gen_random_uuid() primary key,
name text not null,
description text,
owner_id uuid references public.profiles(id) not null,
created_at timestamp with time zone default timezone('utc'::text, now()),
updated_at timestamp with time zone default timezone('utc'::text, now())
);

-- Tabela de tarefas
create table public.tasks (
id uuid default gen_random_uuid() primary key,
title text not null,
description text,
status text default 'todo' check (status in ('todo', 'in_progress', 'done')),
priority text default 'medium' check (priority in ('low', 'medium', 'high')),
project_id uuid references public.projects(id) on delete cascade not null,
assignee_id uuid references public.profiles(id),
due_date timestamp with time zone,
created_at timestamp with time zone default timezone('utc'::text, now()),
updated_at timestamp with time zone default timezone('utc'::text, now())
);

-- Habilitar Row Level Security
alter table public.profiles enable row level security;
alter table public.projects enable row level security;
alter table public.tasks enable row level security;

-- Políticas RLS
create policy "Users can view their own profile"
on public.profiles for select
using (auth.uid() = id);

create policy "Users can update their own profile"
on public.profiles for update
using (auth.uid() = id);

create policy "Users can view their own projects"
on public.projects for select
using (auth.uid() = owner_id);

create policy "Users can create projects"
on public.projects for insert
with check (auth.uid() = owner_id);

Aplicando Migrações​

# Execute migrações localmente
npx supabase db reset

# Push para remoto
npx supabase db push

Cliente Type-Safe​

Criando o Cliente Supabase​

Use o Cursor para gerar um cliente completamente tipado para seu app Next.js:

// lib/supabase/client.ts
import { createBrowserClient } from '@supabase/ssr';
import { Database } from '@/types/supabase';

export function createClient() {
return createBrowserClient<Database>(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!
);
}
// lib/supabase/server.ts
import { createServerClient, type CookieOptions } from '@supabase/ssr';
import { cookies } from 'next/headers';
import { Database } from '@/types/supabase';

export function createClient() {
const cookieStore = cookies();

return createServerClient<Database>(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
{
cookies: {
get(name: string) {
return cookieStore.get(name)?.value;
},
set(name: string, value: string, options: CookieOptions) {
cookieStore.set({ name, value, ...options });
},
remove(name: string, options: CookieOptions) {
cookieStore.set({ name, value: '', ...options });
},
},
}
);
}
dica

Sempre regenere os tipos quando alterar o schema do banco de dados. Faça disso parte do seu processo de build para garantir type safety conforme o schema muda.

Autenticação Supabase​

Auth com Email/Senha no Next.js​

// app/auth/sign-up/page.tsx
'use client';

import { useState } from 'react';
import { createClient } from '@/lib/supabase/client';
import { useRouter } from 'next/navigation';

export default function SignUpPage() {
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const [error, setError] = useState('');
const router = useRouter();
const supabase = createClient();

const handleSignUp = async (e: React.FormEvent) => {
e.preventDefault();
setError('');

const { error } = await supabase.auth.signUp({
email,
password,
options: {
emailRedirectTo: `${location.origin}/auth/callback`,
},
});

if (error) {
setError(error.message);
return;
}

router.push('/auth/verify-email');
};

return (
<form onSubmit={handleSignUp} className="max-w-md mx-auto p-8">
<h1 className="text-2xl font-bold mb-4">Sign Up</h1>
{error && <p className="text-red-500 mb-4">{error}</p>}
<input
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
placeholder="Email"
className="w-full border rounded px-3 py-2 mb-4"
required
/>
<input
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
placeholder="Password"
className="w-full border rounded px-3 py-2 mb-4"
required
/>
<button
type="submit"
className="w-full bg-blue-500 text-white py-2 rounded"
>
Sign Up
</button>
</form>
);
}

Configurando Provedores OAuth​

// app/auth/sign-in/page.tsx
const handleGoogleSignIn = async () => {
const supabase = createClient();
await supabase.auth.signInWithOAuth({
provider: 'google',
options: {
redirectTo: `${location.origin}/auth/callback`,
},
});
};

Ative provedores OAuth no Supabase Dashboard:

  1. Vá para Authentication > Providers
  2. Ative Google
  3. Adicione Client ID e Secret
  4. Copie o Authorized redirect URI

Consultando Dados​

Consultas Type-Safe​

// app/projects/page.tsx
import { createClient } from '@/lib/supabase/server';

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

// Inferência completa de tipos
const { data: projects, error } = await supabase
.from('projects')
.select(`
*,
tasks:tasks(
id,
title,
status,
priority
)
`)
.order('created_at', { ascending: false });

if (error) {
throw new Error(error.message);
}

return (
<div className="p-8">
<h1 className="text-2xl font-bold mb-4">Projects</h1>
{projects?.map((project) => (
<div key={project.id} className="border rounded p-4 mb-4">
<h2 className="text-xl font-semibold">{project.name}</h2>
<p className="text-gray-600">{project.description}</p>
<div className="mt-4">
<h3 className="font-medium">Tasks ({project.tasks.length})</h3>
{project.tasks.map((task) => (
<div key={task.id} className="text-sm">
{task.title} - {task.status}
</div>
))}
</div>
</div>
))}
</div>
);
}
informação

Com os tipos do Supabase gerados, você obtém autocomplete e type checking em project.tasks e project.name.

Subscriptions em Tempo Real​

// hooks/useRealtimeTasks.ts
'use client';

import { useEffect, useState } from 'react';
import { createClient } from '@/lib/supabase/client';
import { Database } from '@/types/supabase';

type Task = Database['public']['Tables']['tasks']['Row'];

export function useRealtimeTasks(projectId: string) {
const [tasks, setTasks] = useState<Task[]>([]);
const supabase = createClient();

useEffect(() => {
// Fetch inicial
const fetchTasks = async () => {
const { data } = await supabase
.from('tasks')
.select('*')
.eq('project_id', projectId)
.order('created_at', { ascending: false });

if (data) setTasks(data);
};

fetchTasks();

// Subscription em tempo real
const channel = supabase
.channel(`project-${projectId}`)
.on(
'postgres_changes',
{
event: '*',
schema: 'public',
table: 'tasks',
filter: `project_id=eq.${projectId}`,
},
(payload) => {
if (payload.eventType === 'INSERT') {
setTasks((prev) => [payload.new as Task, ...prev]);
} else if (payload.eventType === 'UPDATE') {
setTasks((prev) =>
prev.map((t) => (t.id === payload.new.id ? (payload.new as Task) : t))
);
} else if (payload.eventType === 'DELETE') {
setTasks((prev) => prev.filter((t) => t.id !== payload.old.id));
}
}
)
.subscribe();

return () => {
supabase.removeChannel(channel);
};
}, [projectId, supabase]);

return tasks;
}

Storage​

Upload de Arquivos​

// app/upload/page.tsx
'use client';

import { useState } from 'react';
import { createClient } from '@/lib/supabase/client';

export default function UploadPage() {
const [uploading, setUploading] = useState(false);
const supabase = createClient();

const handleUpload = async (e: React.ChangeEvent<HTMLInputElement>) => {
const file = e.target.files?.[0];
if (!file) return;

setUploading(true);
const fileExt = file.name.split('.').pop();
const fileName = `${Math.random()}.${fileExt}`;

const { error } = await supabase.storage
.from('avatars')
.upload(fileName, file);

setUploading(false);

if (error) {
alert('Upload failed: ' + error.message);
return;
}

alert('Upload successful!');
};

return (
<div className="p-8">
<h1 className="text-2xl font-bold mb-4">Upload Avatar</h1>
<input
type="file"
accept="image/*"
onChange={handleUpload}
disabled={uploading}
/>
</div>
);
}

Configurando Bucket de Storage​

Configure políticas RLS para buckets:

-- Criar bucket de storage
insert into storage.buckets (id, name, public)
values ('avatars', 'avatars', true);

-- Políticas RLS de storage
create policy "Anyone can view avatars"
on storage.objects for select
using (bucket_id = 'avatars');

create policy "Authenticated users can upload avatars"
on storage.objects for insert
with check (
bucket_id = 'avatars' and
auth.role() = 'authenticated'
);

Edge Functions​

Criando Edge Functions em TypeScript​

Use o Cursor para gerar Edge Functions do Supabase:

// supabase/functions/send-notification/index.ts
import { serve } from 'https://deno.land/[email protected]/http/server.ts';
import { createClient } from 'https://esm.sh/@supabase/supabase-js@2';

const corsHeaders = {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Headers': 'authorization, x-client-info, apikey, content-type',
};

serve(async (req) => {
if (req.method === 'OPTIONS') {
return new Response('ok', { headers: corsHeaders });
}

try {
const { userId, message } = await req.json();

const supabase = createClient(
Deno.env.get('SUPABASE_URL')!,
Deno.env.get('SUPABASE_SERVICE_ROLE_KEY')!
);

// Inserir notificação
const { data, error } = await supabase
.from('notifications')
.insert({ user_id: userId, message })
.select()
.single();

if (error) throw error;

return new Response(JSON.stringify(data), {
headers: { ...corsHeaders, 'Content-Type': 'application/json' },
status: 200,
});
} catch (error) {
return new Response(
JSON.stringify({ error: error.message }),
{
headers: { ...corsHeaders, 'Content-Type': 'application/json' },
status: 400,
}
);
}
});

Deploy de Functions​

# Deploy edge function
npx supabase functions deploy send-notification

# Executar localmente
npx supabase functions serve

Checklist Supabase + Next.js​

TarefaComando/Ação
Gerar tiposnpx supabase gen types typescript ...
Executar migraçõesnpx supabase db reset
Push de migraçõesnpx supabase db push
Deploy edge functionnpx supabase functions deploy fn-name
Revisar políticas RLSSupabase Dashboard > Authentication > Policies
Verificar connection stringSettings > Database > Connection string

Referência Rápida​

TarefaPrompt do Cursor
Design de schema"Design a Supabase schema for [feature] with RLS"
Implementar auth"Add Supabase auth to my Next.js app with middleware"
Subscription em tempo real"Create a real-time hook for [table] changes"
Gerar tipos"Generate Supabase types for my schema"
Edge function"Write a Supabase Edge Function for [task]"
Configurar storage"Configure Supabase Storage with RLS policies"

Recursos Relacionados​

Última atualização: Julho de 2025. Os recursos do Supabase continuam evoluindo; consulte a documentação oficial para a API mais recente.