O caminho mais rápido: o template oficial
O template expo já vem com tudo desta página funcionando: login e cadastro, sessão que se renova sozinha e uma lista de tarefas protegida por RLS. Ele é compilado a cada mudança contra a versão publicada do SDK.
npx degit superdb-br/templates/expo meu-app
cd meu-app
npm install
cp .env.example .env
npx expo start
Preencha o .env com os dados do painel (projeto → Conectar), rode o arquivo superdb/schema.sql do template no SQL Editor e abra o app no Expo Go ou no simulador. A tela inicial confere a conexão e diz exatamente o que falta.
Há templates para sete stacks: Next.js, React, Vue, SvelteKit, Nuxt, HTML puro e Expo. O mesmo app em todos.
Passo a passo (o que o template faz por você)
1Criar o app e instalar
O @superdb/client segue a API do supabase-js. O AsyncStorage guarda a sessão entre uma abertura do app e outra.
npx create-expo-app@latest meu-app --template blank-typescript
cd meu-app
npx expo install @react-native-async-storage/async-storage
npm install @superdb/client
2Variáveis de ambiente
No Expo, o que vai para dentro do app precisa do prefixo EXPO_PUBLIC_. Depois de mudar o .env, reinicie o npx expo start.
A chave anon é pública: vai no código do app. Quem protege os dados é o RLS. A service key nunca vai num app.
EXPO_PUBLIC_SUPERDB_URL=https://auth.superdb.com.br
EXPO_PUBLIC_SUPERDB_ANON_KEY=cole-a-chave-anon-aqui
EXPO_PUBLIC_SUPERDB_PROJECT=slug-do-projeto
3Criar a tabela com RLS
No SQL Editor do projeto. Toda tabela nova no SuperDB nasce fechada (RLS ligado, nenhuma policy): sem as últimas linhas, a lista volta sempre vazia. Elas abrem para cada pessoa só as próprias tarefas.
create table if not exists tarefas (
id uuid primary key default gen_random_uuid(),
titulo text not null check (char_length(btrim(titulo)) between 1 and 200),
feita boolean not null default false,
criada_em timestamptz not null default now(),
user_id uuid not null default auth.uid()
);
-- A lista do app sempre busca "as minhas, das mais novas para as mais antigas".
create index if not exists tarefas_por_dono on tarefas (user_id, criada_em desc);
alter table tarefas enable row level security;
-- Uma regra só, para ler, criar, alterar e apagar: a linha é sua ou não existe.
-- "using" filtra o que você vê; "with check" recusa gravar linha de outra pessoa.
drop policy if exists tarefas_do_dono on tarefas;
create policy tarefas_do_dono on tarefas
for all to authenticated
using (user_id = auth.uid())
with check (user_id = auth.uid());
revoke all on tarefas from anon;
grant select, insert, update, delete on tarefas to authenticated;
4O cliente, com a sessão que se renova
O token do login vale 1 hora. Desde a versão 0.3, o SDK renova sozinho antes de vencer e tem onAuthStateChange. O template usa o sessao.ts, que nasceu quando o SDK ainda não renovava. Ele continua funcionando e também avisa as outras abas de cada login e saída.
Copie do template os arquivos sessao.ts e erros.ts para src/lib/superdb/. O criarSuperDB renova o token antes de vencer, faz uma renovação só mesmo com várias abas abertas (o SuperDB derruba a sessão se o mesmo refresh token for usado duas vezes) e dá a você entrar, criarConta, sair, sessao e aoMudar.
import AsyncStorage from '@react-native-async-storage/async-storage'
import { criarSuperDB } from './lib/superdb/sessao'
export const app = criarSuperDB({
url: process.env.EXPO_PUBLIC_SUPERDB_URL!,
anonKey: process.env.EXPO_PUBLIC_SUPERDB_ANON_KEY!,
projeto: process.env.EXPO_PUBLIC_SUPERDB_PROJECT!, // obrigatório
armazenamento: AsyncStorage, // no celular não existe localStorage
})
export const superdb = app.superdb
Por que AsyncStorage e não SecureStore? A sessão tem três tokens e passa de 2 KB, o limite do SecureStore em algumas plataformas. Os tokens duram pouco (1 hora) e a renovação troca o refresh token a cada uso.
5Login, sessão e dados
app.sessao() devolve a sessão guardada (já renovada, se estava vencendo); app.aoMudar() avisa a cada login e saída. Nos campos de e-mail, desligue a correção automática: o iOS troca palavras do e-mail.
import { useEffect, useState } from 'react'
import { Text, TextInput, Pressable, View } from 'react-native'
import type { Session } from '@superdb/client'
import { app, superdb } from './superdb'
export default function App() {
const [sessao, setSessao] = useState<Session | null>(null)
useEffect(() => {
app.sessao().then(setSessao)
return app.aoMudar(setSessao)
}, [])
return sessao ? <Tarefas /> : <Entrar />
}
function Entrar() {
const [email, setEmail] = useState('')
const [senha, setSenha] = useState('')
return (
<View>
<TextInput value={email} onChangeText={setEmail} autoCapitalize="none"
autoCorrect={false} keyboardType="email-address" />
<TextInput value={senha} onChangeText={setSenha} secureTextEntry />
<Pressable onPress={() => app.entrar(email, senha)}><Text>Entrar</Text></Pressable>
</View>
)
}
function Tarefas() {
const [tarefas, setTarefas] = useState<{ id: string; titulo: string }[]>([])
useEffect(() => {
superdb.from('tarefas').select('id,titulo').then(({ data }) => setTarefas(data ?? []))
}, [])
return <View>{tarefas.map((t) => <Text key={t.id}>{t.titulo}</Text>)}</View>
}
6Rodar
Escaneie o QR code com o Expo Go (celular e computador na mesma rede) ou tecle i/a para abrir no simulador.
npx expo start
Próximos passos
- O template completo, com o README explicando cada arquivo
- Row Level Security: policies para cada caso
- Referência do SDK