Alternar tema

Supabase para iniciantes: backend completo com PostgreSQL, Auth e Storage

Easton editorial illustration: one application card connected to three backend modules

Depois de encarar pela 12ª vez o mesmo erro de conexão com o banco de dados, percebi uma coisa: criar um projeto full stack ainda pode ser difícil demais para quem trabalha no frontend.

Antes, sempre que eu precisava de recursos de backend, tinha de aprender Node.js e Express, configurar o banco de dados, implementar autenticação e cuidar do armazenamento de arquivos. Cada parte abria um novo buraco. Isso mudou quando conheci o Supabase.

Em resumo, ele é uma alternativa open source ao Firebase, mas usa PostgreSQL em vez de um banco NoSQL daqueles que costumam dar dor de cabeça. E reúne banco de dados, autenticação de usuários e armazenamento de arquivos em um único serviço de backend.

Neste artigo, você vai começar do zero com o Supabase e conhecer seus três recursos centrais: Database, Auth e Storage. Ao terminar, já terá base para montar rapidamente um backend completo sem sofrer com uma pilha de arquivos de configuração.


O que é Supabase

Primeiro, vale entender exatamente o que é o Supabase.

Trata-se de uma plataforma BaaS, sigla para Backend as a Service. Na prática, você não precisa montar servidores, configurar bancos de dados nem escrever todas as APIs por conta própria: a plataforma cuida disso.

Ao contrário do Firebase, o Supabase é totalmente open source. Além disso, ele usa PostgreSQL, e isso importa. Como banco de dados relacional, o PostgreSQL permite consultas SQL complexas e mantém os relacionamentos entre dados bem claros — algo muito mais confortável do que trabalhar com o modelo de documentos do Firestore em vários cenários.

O Supabase oferece seis recursos centrais:

  • Database: banco de dados PostgreSQL com consultas SQL e geração automática de REST API
  • Auth: sistema de autenticação com e-mail, login social por Google, GitHub e outros provedores, além de JWT Token
  • Storage: armazenamento de arquivos parecido com o AWS S3, mas mais simples de usar
  • Realtime: sincronização de dados em tempo real, útil para aplicações de chat
  • Edge Functions: computação de borda semelhante ao AWS Lambda
  • Vector Database: banco de dados vetorial para aplicações de IA

Neste artigo, vamos nos concentrar nos três primeiros: Database, Auth e Storage. Eles formam a base; os demais ficam para outra ocasião.

Neste ponto, talvez você esteja se perguntando qual dos dois é melhor: Supabase ou Firebase. A comparação detalhada vem mais adiante, mas a resposta curta é esta: escolha Supabase quando precisar de SQL, relacionamentos complexos ou controle sobre os dados; escolha Firebase quando estiver criando uma aplicação móvel e a sincronização em tempo real for especialmente importante.


Início rápido

Criando um projeto no Supabase

O primeiro passo é criar uma conta em supabase.com. Depois do cadastro, clique em “New Project” para abrir um novo projeto.

Na criação, você precisará preencher alguns dados:

  • Nome do projeto: escolha o que quiser, como my-first-app
  • Senha do banco de dados: guarde-a, pois ela será necessária depois
  • Região: escolha a mais próxima de você; na China, use Singapore ou Tokyo

A criação leva cerca de três minutos. Quando terminar, você verá o painel do projeto. À esquerda, há menus como Table Editor, Authentication, Storage e Edge Functions. A quantidade de opções pode assustar no início, mas vamos passar por elas uma a uma.

Instalação e inicialização

Agora instale o Supabase no seu projeto frontend. Comece pela dependência:

npm install @supabase/supabase-js

Depois, encontre dois valores no Supabase Dashboard: Project URL e Anon Public Key. Ambos ficam em Settings > API.

Inicialize então o Supabase Client:

import { createClient } from '@supabase/supabase-js';

const supabaseUrl = 'https://your-project-id.supabase.co';
const supabaseAnonKey = 'your-anon-public-key';

export const supabase = createClient(supabaseUrl, supabaseAnonKey);

Esse código é a porta de entrada para a comunicação com o Supabase. Todas as operações seguintes — consultas ao banco, login de usuários e upload de arquivos — passam pelo objeto supabase.

Testando a conexão

Depois da inicialização, teste se a conexão funciona. A forma mais simples é consultar os dados do banco:

const { data, error } = await supabase.from('users').select('*');

if (error) {
  console.error('Falha na conexão:', error.message);
} else {
  console.log('Conexão realizada!', data);
}

Se aparecer um erro dizendo que a tabela users não existe, isso é normal: você ainda não a criou. A próxima seção mostra como fazer isso.

Alguns erros comuns são:

  • URL ou Key digitada incorretamente: confira se o valor foi copiado por inteiro
  • Erro de CORS: talvez o projeto frontend precise de configuração, embora o Supabase permita o acesso por padrão
  • Problema de rede: às vezes acontece; aguarde alguns minutos e tente novamente

Se estiver tudo certo, você verá os dados retornados. Pode ser apenas um array vazio, [], indicando que a tabela ainda não contém registros.


Database: operações no banco de dados

O banco de dados é o núcleo do Supabase. Quem já usou PostgreSQL vai reconhecer muita coisa. Para quem vem do frontend e ainda não tem familiaridade com SQL, o Supabase oferece o Table Editor, uma interface visual que permite trabalhar sem escrever comandos SQL.

Criando tabelas

Há duas maneiras de criar uma tabela: pelo Table Editor, de forma visual, ou escrevendo SQL.

Comecemos pelo Table Editor. Clique em “Table Editor” no menu à esquerda do Dashboard e depois em “Create a new table”. Você terá de informar o nome da tabela, os campos e os tipos.

Por exemplo, crie uma tabela users:

CampoTipoRestrição
idint8Primary Key, Auto Increment
emailtextUnique, Not Null
nametext-
created_attimestamptzDefault: now()

Depois de preencher, clique em “Save” e a tabela será criada.

Ainda assim, considero o SQL mais rápido, principalmente quando as operações ficam mais complexas. O Supabase oferece um SQL Editor no menu à esquerda do Dashboard.

Use este SQL para criar as tabelas users e projects:

-- Tabela de usuários
CREATE TABLE users (
  id SERIAL PRIMARY KEY,
  email TEXT UNIQUE NOT NULL,
  name TEXT,
  created_at TIMESTAMPTZ DEFAULT NOW()
);

-- Tabela de projetos
CREATE TABLE projects (
  id SERIAL PRIMARY KEY,
  user_id INTEGER REFERENCES users(id),
  title TEXT NOT NULL,
  description TEXT,
  status TEXT DEFAULT 'active',
  created_at TIMESTAMPTZ DEFAULT NOW()
);

Há um detalhe importante: o campo user_id da tabela projects é uma chave estrangeira ligada ao id da tabela users. Assim, cada projeto pertence a uma pessoa. Essa clareza nos relacionamentos é uma das vantagens de um banco relacional.

Operações CRUD

Com as tabelas criadas, podemos testar as operações de criação, leitura, atualização e exclusão.

Inserindo dados:

// Inserir um usuário
const { data, error } = await supabase
  .from('users')
  .insert([
    { email: '[email protected]', name: 'John Doe' }
  ]);

if (error) {
  console.error('Falha ao inserir:', error.message);
} else {
  console.log('Registro inserido:', data);
}

Consultando dados:

// Consultar todos os projetos e incluir os dados do usuário
const { data, error } = await supabase
  .from('projects')
  .select(`
    *,
    users (
      name,
      email
    )
  `)
  .eq('status', 'active')
  .order('created_at', { ascending: false });

console.log(data);

Essa consulta tem uma característica interessante: além de buscar a tabela projects, ela usa a chave estrangeira para trazer as informações relacionadas de cada usuário. Uma só consulta retorna os dados das duas tabelas. Para fazer o mesmo no Firestore, seria preciso executar várias consultas e combinar os resultados manualmente.

Atualizando dados:

const { data, error } = await supabase
  .from('projects')
  .update({ status: 'completed' })
  .eq('id', 1);

Excluindo dados:

const { data, error } = await supabase
  .from('projects')
  .delete()
  .eq('id', 1);

As operações são bem diretas. Vale observar que .eq() é um filtro de igualdade. O Supabase também oferece métodos como .gt() para “maior que”, .lt() para “menor que” e .like() para correspondência de padrões. Os filtros SQL mais usados estão disponíveis.

Row Level Security (RLS)

Esta parte é especialmente importante quando sua aplicação trabalha com usuários.

RLS significa Row Level Security, ou segurança em nível de linha. Com esse recurso, você controla quais registros cada pessoa pode ver, impedindo o acesso aos dados de outras pessoas.

Imagine que a tabela projects contenha muitos projetos, mas cada pessoa autenticada só possa ver os projetos que criou.

Primeiro, habilite RLS:

ALTER TABLE projects ENABLE ROW LEVEL SECURITY;

Depois, crie uma Policy:

-- Cada pessoa só pode ver os próprios projetos
CREATE POLICY "Users can view their own projects"
ON projects FOR SELECT
USING (user_id = auth.uid());

auth.uid() é uma função fornecida pelo Supabase que retorna o ID da pessoa autenticada. Essa Policy faz com que uma consulta à tabela projects retorne somente os registros cujo user_id corresponde a esse ID.

Você também pode criar Policies para inserção, atualização e exclusão:

-- Cada pessoa só pode inserir os próprios projetos
CREATE POLICY "Users can insert their own projects"
ON projects FOR INSERT
WITH CHECK (user_id = auth.uid());

-- Cada pessoa só pode atualizar os próprios projetos
CREATE POLICY "Users can update their own projects"
ON projects FOR UPDATE
USING (user_id = auth.uid())
WITH CHECK (user_id = auth.uid());

-- Cada pessoa só pode excluir os próprios projetos
CREATE POLICY "Users can delete their own projects"
ON projects FOR DELETE
USING (user_id = auth.uid());

Desse modo, mesmo uma tentativa mal-intencionada de consultar os dados de outra pessoa é bloqueada no nível do banco de dados. Isso é muito mais seguro do que depender de uma verificação no código frontend.


Auth: sistema de autenticação de usuários

O Supabase oferece um sistema de autenticação bastante completo. Ele aceita login com e-mail e senha, Magic Link sem senha, senha de uso único (OTP), login social com Google, GitHub, Apple e mais de 20 provedores, login por telefone e SSO empresarial.

Visão geral da autenticação

Primeiro, vamos entender o mecanismo central do Supabase Auth.

Ele usa JWT Token, ou JSON Web Token. Quando o login é concluído, o Supabase gera um Token. O frontend envia esse Token nas solicitações à API de backend, que o valida para confirmar a identidade da pessoa.

O Supabase Auth também salva automaticamente os dados de usuários na tabela auth.users do PostgreSQL. Essa tabela é criada pela plataforma e não exige manutenção manual. Ela armazena ID, e-mail, data de criação, horário do último login e outras informações.

Outro recurso é o Session Persistence, ou persistência de sessão. Depois do login, o navegador mantém a sessão e não exige uma nova autenticação quando a página é aberta novamente. O SDK frontend do Supabase cuida disso automaticamente, sem código adicional.

Autenticação com Email/Password

Essa é a forma de login mais comum. Comecemos pelo cadastro:

// Cadastro de usuário
const { data, error } = await supabase.auth.signUp({
  email: '[email protected]',
  password: 'securepassword123',
});

if (error) {
  console.error('Falha no cadastro:', error.message);
} else {
  console.log('Cadastro concluído:', data);
}

Depois do cadastro, o Supabase envia uma mensagem de confirmação ao endereço informado. A conta é ativada quando a pessoa acessa o link do e-mail. Esse é o comportamento padrão e pode ser desativado no Dashboard, embora isso não seja recomendado: a confirmação de e-mail ajuda a evitar cadastros mal-intencionados.

Em seguida, faça o login:

// Login de usuário
const { data, error } = await supabase.auth.signInWithPassword({
  email: '[email protected]',
  password: 'securepassword123',
});

if (error) {
  console.error('Falha no login:', error.message);
} else {
  console.log('Login realizado:', data);
}

Depois do login, você pode obter os dados da pessoa autenticada:

// Obter o usuário atual
const { data: { user } } = await supabase.auth.getUser();

console.log('Usuário atual:', user);
// Saída semelhante a: { id: 'abc123', email: '[email protected]', ... }

Por fim, encerre a sessão:

await supabase.auth.signOut();

Depois de sair, a Session é apagada e será necessário entrar novamente.

Outra necessidade comum é redefinir a senha. O Supabase já oferece essa função:

// Enviar e-mail para redefinição de senha
const { data, error } = await supabase.auth.resetPasswordForEmail(
  '[email protected]'
);

A pessoa recebe um e-mail e usa o link para redefinir a senha. Você não precisa implementar lógica adicional para o processo.

Social Auth: login social

Esse recurso é bastante popular: em vez de preencher um formulário, a pessoa entra diretamente com a conta do Google ou GitHub, o que melhora muito a experiência.

Comece configurando o login social. No Dashboard, acesse Authentication > Providers e habilite a plataforma desejada, como Google ou GitHub.

A configuração varia entre provedores, mas os passos gerais são:

  1. Criar uma OAuth App no Google ou GitHub
  2. Copiar Client ID e Client Secret para o Supabase
  3. Configurar a URL de retorno, ou Redirect URL

Depois disso, o código frontend é simples:

// Login com Google
const { data, error } = await supabase.auth.signInWithOAuth({
  provider: 'google',
});

// Login com GitHub
const { data, error } = await supabase.auth.signInWithOAuth({
  provider: 'github',
});

Ao executar o método, a página redireciona para a autorização do Google ou GitHub. Depois que a pessoa aceita, ela retorna à sua aplicação já autenticada.

Também é possível personalizar a URL de retorno e os parâmetros adicionais:

const { data, error } = await supabase.auth.signInWithOAuth({
  provider: 'google',
  options: {
    redirectTo: 'https://your-app.com/dashboard',
    queryParams: {
      access_type: 'offline',
      prompt: 'consent',
    }
  }
});

redirectTo define a página aberta após o login. queryParams reúne parâmetros adicionais do OAuth, como a solicitação de acesso offline e a exibição da confirmação de autorização a cada tentativa.

Integrando RLS e Auth

Na seção sobre RLS, mencionamos auth.uid(). Agora as duas partes se conectam.

Depois que alguém entra, auth.uid() retorna seu ID. Use esse ID em uma Policy do banco para garantir que cada pessoa acesse apenas os próprios dados.

Por exemplo, ao criar um projeto, você pode associá-lo automaticamente à pessoa autenticada:

// Criar projeto e associá-lo automaticamente ao usuário atual
const { data: { user } } = await supabase.auth.getUser();

const { data, error } = await supabase
  .from('projects')
  .insert([
    {
      title: 'New Project',
      user_id: user.id  // Obter o ID pelo Auth
    }
  ]);

As RLS Policies garantem que:

  • Nas consultas, só apareçam projetos com user_id = auth.uid()
  • Nas inserções, user_id seja igual a auth.uid()
  • A mesma regra seja aplicada a atualizações e exclusões

Assim, autenticação e autorização formam um fluxo completo: login, associação de dados e restrição de acesso.


Storage: armazenamento de arquivos

O Supabase Storage é um serviço de armazenamento de objetos semelhante ao AWS S3, mas muito mais simples de usar. Ele serve para avatares, imagens, documentos e outros arquivos.

Visão geral do Storage

Há dois conceitos centrais: Bucket e Object.

  • Bucket: contêiner de armazenamento semelhante a uma pasta. Você pode criar vários Buckets, como avatars para avatares e documents para documentos
  • Object: o arquivo em si, como avatar.jpg ou report.pdf

Um Bucket pode ter dois modos de acesso:

  • Public: bucket público; qualquer pessoa pode acessar os arquivos, por isso é adequado para imagens públicas
  • Private: bucket privado; somente pessoas autorizadas podem acessar os arquivos, por isso é adequado para documentos confidenciais

Criando um Bucket

No Dashboard, clique em Storage e depois em “New Bucket”.

Por exemplo, crie um bucket avatars para armazenar avatares:

  • Nome: avatars
  • Public bucket: marque a opção, pois avatares normalmente são públicos
  • File size limit: defina o tamanho máximo do arquivo, como 2 MB

Depois da criação, você já poderá enviar arquivos para o bucket.

Upload e download de arquivos

Faça o upload:

// Fazer upload do avatar do usuário
const file = document.getElementById('avatar-input').files[0];

const { data, error } = await supabase.storage
  .from('avatars')
  .upload('user-id/avatar.jpg', file, {
    cacheControl: '3600',
    upsert: false
  });

if (error) {
  console.error('Falha no upload:', error.message);
} else {
  console.log('Upload concluído:', data);
}

Observe alguns detalhes:

  • O primeiro argumento de upload() é o caminho do arquivo: 'user-id/avatar.jpg'
  • cacheControl define o tempo de cache, aqui em 3.600 segundos
  • upsert informa se um arquivo existente deve ser substituído. Com false, a operação gera erro; com true, substitui o arquivo

Faça o download:

// Fazer download do arquivo
const { data, error } = await supabase.storage
  .from('avatars')
  .download('user-id/avatar.jpg');

if (error) {
  console.error('Falha no download:', error.message);
} else {
  // data é um objeto Blob que pode ser convertido em URL para exibição
  const url = URL.createObjectURL(data);
  document.getElementById('avatar-img').src = url;
}

Se o bucket for público, você também pode obter diretamente uma URL pública:

// Obter URL pública
const { data } = supabase.storage
  .from('avatars')
  .getPublicUrl('user-id/avatar.jpg');

console.log('URL pública:', data.publicUrl);
// Saída semelhante a: https://your-project.supabase.co/storage/v1/object/public/avatars/user-id/avatar.jpg

Arquivos de buckets privados exigem uma URL temporária de acesso:

// Gerar uma URL temporária válida por uma hora
const { data, error } = await supabase.storage
  .from('documents')
  .createSignedUrl('private-file.pdf', 3600);

console.log('URL temporária:', data.signedUrl);

Para excluir um arquivo:

const { data, error } = await supabase.storage
  .from('avatars')
  .remove(['user-id/avatar.jpg']);

Controle de acesso

O Storage também aceita RLS Policies para controlar quais arquivos cada pessoa pode acessar.

Por exemplo, você pode permitir que cada pessoa envie e acesse somente os arquivos dentro da própria pasta.

-- Cada pessoa só pode fazer upload para a própria pasta
CREATE POLICY 'Users can upload to their own folder'
ON storage.objects FOR INSERT
WITH CHECK (
  bucket_id = 'avatars' AND
  (storage.foldername(name))[1] = auth.uid()::text
);

-- Cada pessoa só pode acessar arquivos da própria pasta
CREATE POLICY 'Users can access their own files'
ON storage.objects FOR SELECT
USING (
  bucket_id = 'avatars' AND
  (storage.foldername(name))[1] = auth.uid()::text
);

Aqui, storage.foldername(name) extrai a parte de pasta do caminho. [1] seleciona a primeira pasta, que corresponde ao ID do usuário.

Assim, o caminho de upload precisa seguir o formato user-id/filename. A RLS verifica se user-id é igual ao ID da pessoa autenticada e rejeita o upload quando os valores não correspondem.


Caso prático: aplicação de gerenciamento de tarefas

Depois de conhecer as partes separadamente, vamos reuni-las em um caso completo.

Imagine uma aplicação simples de gerenciamento de tarefas com estes recursos:

  • Cadastro e login de usuários
  • Criação de tarefas e visualização da lista
  • Upload de anexos para cada tarefa

Modelagem do banco de dados

Comece pelas tabelas:

-- A tabela de usuários é criada automaticamente pelo Auth
-- Tabela tasks
CREATE TABLE tasks (
  id SERIAL PRIMARY KEY,
  user_id UUID REFERENCES auth.users(id) NOT NULL,
  title TEXT NOT NULL,
  description TEXT,
  status TEXT DEFAULT 'pending' CHECK (status IN ('pending', 'completed')),
  created_at TIMESTAMPTZ DEFAULT NOW(),
  updated_at TIMESTAMPTZ DEFAULT NOW()
);

-- Habilitar RLS
ALTER TABLE tasks ENABLE ROW LEVEL SECURITY;

-- Policy: cada pessoa só pode acessar as próprias tarefas
CREATE POLICY 'Users can manage their own tasks'
ON tasks FOR ALL
USING (user_id = auth.uid())
WITH CHECK (user_id = auth.uid());

-- Tabela attachments, relacionada às tarefas
CREATE TABLE task_attachments (
  id SERIAL PRIMARY KEY,
  task_id INTEGER REFERENCES tasks(id) ON DELETE CASCADE,
  file_name TEXT NOT NULL,
  file_path TEXT NOT NULL,
  file_size INTEGER,
  created_at TIMESTAMPTZ DEFAULT NOW()
);

-- Habilitar RLS
ALTER TABLE task_attachments ENABLE ROW LEVEL SECURITY;

-- Policy: verificar o acesso pelo relacionamento com a tarefa
CREATE POLICY 'Users can manage attachments of their tasks'
ON task_attachments FOR ALL
USING (
  EXISTS (
    SELECT 1 FROM tasks
    WHERE tasks.id = task_attachments.task_id
    AND tasks.user_id = auth.uid()
  )
);

Há um detalhe nessa estrutura: a Policy da tabela task_attachments não verifica diretamente um user_id. Ela usa task_id para encontrar a linha correspondente na tabela tasks e então valida tasks.user_id. Desse modo, somente quem é dono da tarefa pode gerenciar seus anexos.

Criando o Storage Bucket

Crie um bucket privado chamado task-files:

-- Criar pelo Dashboard ou com SQL
INSERT INTO storage.buckets (name, public)
VALUES ('task-files', false);

-- Policy: cada pessoa só pode fazer upload para a própria pasta
CREATE POLICY 'Users can upload task files'
ON storage.objects FOR INSERT
WITH CHECK (
  bucket_id = 'task-files' AND
  (storage.foldername(name))[1] = auth.uid()::text
);

CREATE POLICY 'Users can access task files'
ON storage.objects FOR SELECT
USING (
  bucket_id = 'task-files' AND
  (storage.foldername(name))[1] = auth.uid()::text
);

Implementação do código principal

Agora reúna todos os recursos:

import { createClient } from '@supabase/supabase-js';

const supabase = createClient(
  'https://your-project.supabase.co',
  'your-anon-key'
);

// 1. Cadastrar usuário
async function register(email: string, password: string) {
  const { data, error } = await supabase.auth.signUp({
    email,
    password,
  });

  if (error) throw error;
  return data;
}

// 2. Fazer login
async function login(email: string, password: string) {
  const { data, error } = await supabase.auth.signInWithPassword({
    email,
    password,
  });

  if (error) throw error;
  return data;
}

// 3. Criar tarefa
async function createTask(title: string, description?: string) {
  const { data: { user } } = await supabase.auth.getUser();

  if (!user) throw new Error('Usuário não autenticado');

  const { data, error } = await supabase
    .from('tasks')
    .insert([
      {
        title,
        description,
        user_id: user.id,
      }
    ])
    .select();

  if (error) throw error;
  return data[0];
}

// 4. Obter lista de tarefas
async function getTasks() {
  const { data, error } = await supabase
    .from('tasks')
    .select('*')
    .order('created_at', { ascending: false });

  if (error) throw error;
  return data;
}

// 5. Fazer upload de anexo
async function uploadAttachment(taskId: number, file: File) {
  const { data: { user } } = await supabase.auth.getUser();

  if (!user) throw new Error('Usuário não autenticado');

  const filePath = user.id + '/' + taskId + '/' + file.name;

  const { data, error } = await supabase.storage
    .from('task-files')
    .upload(filePath, file);

  if (error) throw error;

  // Registrar na tabela task_attachments
  const { data: attachment, error: dbError } = await supabase
    .from('task_attachments')
    .insert([
      {
        task_id: taskId,
        file_name: file.name,
        file_path: filePath,
        file_size: file.size,
      }
    ])
    .select();

  if (dbError) throw dbError;

  return attachment[0];
}

// 6. Obter anexos da tarefa
async function getTaskAttachments(taskId: number) {
  const { data, error } = await supabase
    .from('task_attachments')
    .select('*')
    .eq('task_id', taskId);

  if (error) throw error;

  // Gerar uma URL temporária de acesso
  const attachmentsWithURLs = data.map(async (attachment) => {
    const { data: urlData } = await supabase.storage
      .from('task-files')
      .createSignedUrl(attachment.file_path, 3600);

    return {
      ...attachment,
      url: urlData.signedUrl,
    };
  });

  return Promise.all(attachmentsWithURLs);
}

// 7. Concluir tarefa
async function completeTask(taskId: number) {
  const { data, error } = await supabase
    .from('tasks')
    .update({ status: 'completed', updated_at: new Date() })
    .eq('id', taskId)
    .select();

  if (error) throw error;
  return data[0];
}

// 8. Encerrar sessão
async function logout() {
  await supabase.auth.signOut();
}

Esse conjunto cobre cadastro, login, CRUD de tarefas e upload de arquivos: praticamente toda a estrutura de uma aplicação completa. A partir daqui, você pode acrescentar categorias, tags, comentários, notificações e outros recursos.


Comparação entre Supabase e Firebase

Depois de tudo isso, talvez ainda reste a pergunta: qual escolher, Supabase ou Firebase? Vamos comparar os dois com mais detalhes.

Principais diferenças

CritérioSupabaseFirebase
Tipo de banco de dadosPostgreSQL, relacionalFirestore, NoSQL orientado a documentos
Código abertoTotalmente open sourceProduto proprietário do Google
Modelo de preçoValor fixo de US$ 25/mês no Pro PlanCobrança por uso, com leituras, gravações e armazenamento separados
Propriedade dos dadosControle total e exportação a qualquer momentoDados no Google e exportação mais trabalhosa
Capacidade de consultaSQL avançado e relacionamentos complexosConsultas limitadas; relacionamentos complexos exigem várias consultas
Recursos em tempo realWebSocket com assinatura manualTempo real nativo do Firestore com sincronização automática
Suporte offlineExige implementação própria de cacheSuporte offline nativo e sincronização automática
Dificuldade de migraçãoPostgreSQL padrão, fácil de migrarFormato proprietário e migração mais cara

Recomendações por cenário

Escolha Supabase quando:

  • Os dados tiverem relacionamentos complexos e exigirem consultas SQL e joins
  • O projeto for de longo prazo e você quiser controle total dos dados
  • A equipe conhecer SQL e preferir PostgreSQL
  • O orçamento for limitado e um custo fixo for mais previsível que a cobrança por uso
  • A equipe priorizar open source e quiser controlar a stack

Escolha Firebase quando:

  • Você precisar criar um protótipo rapidamente
  • A prioridade for uma aplicação móvel e os recursos offline forem importantes
  • A sincronização em tempo real for central, como em uma aplicação de chat
  • O projeto já fizer parte do ecossistema Google Cloud
  • A estrutura de dados for simples e não exigir consultas complexas

Para ser sincero, já usei Firebase em alguns projetos e levei um susto com a conta: ela passou de US$ 800 em um mês. Depois da migração para o Supabase, os mesmos recursos ficaram em torno de US$ 25. É uma diferença considerável.

A exportação de dados do Firebase também pode ser trabalhosa. O Firestore usa um formato proprietário que precisa ser convertido depois da exportação. No Supabase, basta exportar um arquivo SQL ou usar ferramentas do PostgreSQL para gerar CSV, mantendo aberta a possibilidade de migrar para outro banco.

Por outro lado, o Firebase realmente leva vantagem em recursos de tempo real e aplicações móveis. A sincronização em tempo real e o suporte offline do Firestore funcionam muito bem. Para um chat, documento colaborativo ou outro produto fortemente dependente de tempo real, ele pode ser mais conveniente.


Conclusão e próximos passos

Depois deste percurso, podemos resumir o valor central do Supabase em alguns pontos:

  • Banco de dados PostgreSQL, com consultas poderosas e relacionamentos claros
  • Autenticação de nível empresarial, com várias formas de login, JWT Token e permissões por RLS
  • Armazenamento de objetos simples de usar e com controle de acesso
  • Sincronização em tempo real e computação de borda para explorar mais adiante

Além disso, o Supabase é open source, oferece controle total dos dados e mantém os custos previsíveis, características importantes para projetos de longo prazo.

Se você trabalha no frontend, quer criar rapidamente uma aplicação full stack e não deseja se perder em configurações de backend, o Supabase é uma boa escolha. Em poucas horas, dá para preparar banco de dados, autenticação e armazenamento, bem mais rápido do que em um backend tradicional.

Uma sequência de aprendizado possível é:

  • Entenda primeiro Database, Auth e Storage, que são os fundamentos
  • Depois, experimente RLS Policies para compreender como autenticação e autorização se conectam
  • Pratique com um projeto real: escolha uma necessidade e construa a aplicação do zero
  • Em seguida, explore recursos avançados como Realtime, Edge Functions e Vector Database

Com isso, chegamos ao fim. A documentação oficial do Supabase é clara e pode ajudar quando surgir alguma dúvida: supabase.com/docs. A comunidade também oferece muitos tutoriais e exemplos; no GitHub, por exemplo, a lista awesome-supabase reúne vários recursos.

Em uma frase: se você quer usar PostgreSQL no backend sem perder tempo com configuração, o Supabase já deixa o caminho pronto — basta começar.


Como começar rapidamente com os três recursos centrais do Supabase

Crie um projeto Supabase do zero e aprenda os fundamentos de Database, Auth e Storage.

⏱️ Estimated time: 2 hr

  1. 1

    Step 1: Criar um projeto no Supabase

    Acesse supabase.com, crie uma conta e abra um novo projeto:

    • Nome do projeto: my-first-app
    • Senha do banco de dados: guarde-a com cuidado
    • Região: escolha a mais próxima de você (na China, use Singapore ou Tokyo)

    Quando a criação terminar, acesse Settings > API para obter o Project URL e a Anon Public Key.
  2. 2

    Step 2: Instalar e inicializar o cliente

    Instale a dependência no projeto frontend:

    npm install @supabase/supabase-js

    Inicialize o Supabase Client:

    import { createClient } from '@supabase/supabase-js';

    const supabase = createClient(
    'https://your-project.supabase.co',
    'your-anon-key'
    );
  3. 3

    Step 3: Criar a tabela e configurar RLS

    Crie a tabela com o SQL Editor:

    CREATE TABLE tasks (
    id SERIAL PRIMARY KEY,
    user_id UUID REFERENCES auth.users(id),
    title TEXT NOT NULL,
    status TEXT DEFAULT 'pending'
    );

    ALTER TABLE tasks ENABLE ROW LEVEL SECURITY;

    CREATE POLICY 'Users can manage their own tasks'
    ON tasks FOR ALL
    USING (user_id = auth.uid());

    A RLS Policy garante que cada pessoa só acesse os próprios dados.
  4. 4

    Step 4: Implementar a autenticação de usuários

    Exemplo de autenticação com Email/Password:

    // Cadastro
    await supabase.auth.signUp({
    email: '[email protected]',
    password: 'password123'
    });

    // Login
    await supabase.auth.signInWithPassword({
    email: '[email protected]',
    password: 'password123'
    });

    // Obter usuário atual
    const { data: { user } } = await supabase.auth.getUser();

    Também há login social com Google, GitHub e mais de 20 provedores.
  5. 5

    Step 5: Configurar o armazenamento de arquivos

    Crie um Storage Bucket público ou privado:

    // Fazer upload de um arquivo
    await supabase.storage
    .from('avatars')
    .upload('user-id/avatar.jpg', file);

    // Obter a URL pública
    const { data } = supabase.storage
    .from('avatars')
    .getPublicUrl('user-id/avatar.jpg');

    Para arquivos privados, use createSignedUrl para gerar um link temporário de acesso.

FAQ

Qual é a principal diferença entre Supabase e Firebase?
O Supabase usa o banco de dados relacional PostgreSQL, com consultas SQL complexas e relacionamentos entre dados. O Firebase usa o Firestore, um banco NoSQL orientado a documentos com consultas mais limitadas. O Supabase é totalmente open source, cobra um valor fixo de US$ 25 por mês e oferece controle total dos dados; o Firebase é proprietário, cobra por uso — podendo passar de US$ 800 por mês — e dificulta a exportação de dados.
Para quais tipos de projeto o Supabase é indicado?
Ele atende bem aos seguintes cenários:

• Dados com relacionamentos complexos, que exigem consultas SQL e joins
• Projetos de longo prazo nos quais você deseja controlar totalmente os dados
• Projetos sensíveis a custos, com orçamento limitado e preferência por despesas previsíveis
• Aplicações full stack criadas rapidamente por quem trabalha no frontend
• Equipes que priorizam open source e querem controlar a própria stack
O que é Row Level Security (RLS) e para que serve?
RLS é o recurso de segurança em nível de linha do PostgreSQL. Ele permite controlar, diretamente no banco de dados, que cada pessoa acesse apenas os próprios dados. Com a função auth.uid() do Supabase Auth, você garante que uma pessoa autenticada só veja ou altere os registros que criou, o que é mais seguro que fazer essa verificação no código frontend.
Quais formas de login o Supabase Auth aceita?
Há várias opções de login:

• Email/Password, com e-mail e senha
• Magic Link, sem senha
• OTP, com senha de uso único
• Social Auth, com Google, GitHub, Apple e mais de 20 provedores
• Phone Auth, com Twilio ou MessageBird
• SSO, para login único empresarial
Qual é a diferença entre buckets públicos e privados no Supabase Storage?
Os arquivos de um Public Bucket podem ser acessados por qualquer pessoa, por isso ele é adequado para imagens e avatares públicos. Os arquivos de um Private Bucket exigem autorização. Nesse caso, use createSignedUrl para gerar um link temporário, com validade configurável, apropriado para documentos privados e arquivos enviados por usuários.
Quanto tempo leva para migrar do Firebase para o Supabase?
Uma aplicação simples pode ser migrada em dois ou três dias: converta os dados do Firestore em tabelas PostgreSQL, exporte os usuários do Firebase Auth para o Supabase Auth e migre os arquivos do Cloud Storage para o Supabase Storage. Uma aplicação complexa pode exigir uma ou duas semanas, principalmente por causa da conversão do modelo de dados e da reescrita das consultas.
O que está incluído no plano gratuito do Supabase?
O plano gratuito inclui solicitações de API ilimitadas, 50 mil usuários ativos por mês, 500 MB de armazenamento no banco de dados, 1 GB para arquivos e 5 GB de largura de banda. Ele é suficiente para desenvolver um MVP pessoal; ao ultrapassar os limites, é preciso migrar para o Pro Plan, que custa US$ 25 por mês.

21 min de leitura · Publicado em: 3 abr 2026 · Atualizado em: 4 set 2026

Comentários

Entre com GitHub para comentar

Easton BlogEaston Blog