O princípio: um cliente, um lugar para guardar a sessão
O @superdb/client roda em qualquer lugar com JavaScript e fetch: navegador, Node, Bun, Deno, edge, React Native. Ele não tem versões por framework. O que muda de um framework para outro é só onde a sessão fica guardada, e isso é um parâmetro do cliente:
import { createClient } from '@superdb/client'
const superdb = createClient(URL_DE_AUTH, ANON_KEY, {
project: 'slug-do-projeto', // obrigatório: é o schema dos seus dados
storage: armazenamento, // opcional: { getItem, setItem, removeItem }
})
- Sem
storage, a sessão vai para olocalStorageno navegador e para a memória no servidor. - App que roda só no navegador (SPA): o padrão serve. Use o
sessao.tsdos templates para a renovação. - App com renderização no servidor: passe um armazenamento feito de cookies, e crie um cliente por requisição.
Há templates prontos para SvelteKit, Nuxt e HTML puro, além de Next.js, React, Vue e Expo. Todos em github.com/superdb-br/templates.
SPA: a sessão no navegador
O SDK guarda a sessão e, desde a versão 0.3, renova sozinho antes de o token (1 hora) vencer. Para reagir a login e saída, use db.auth.onAuthStateChange. O sessao.ts dos templates, o mesmo arquivo em todos e sem dependência de framework, também avisa as outras abas.
import { criarSuperDB } from './lib/superdb/sessao'
export const app = criarSuperDB({ url: URL_DE_AUTH, anonKey: ANON_KEY, projeto: 'slug-do-projeto' })
export const superdb = app.superdb
// app.entrar(email, senha) · app.criarConta(email, senha) · app.sair()
// app.sessao() → a sessão atual, renovada se estiver vencendo
// app.aoMudar(fn) → avisa a cada login, saída ou renovação (também de outras abas)
Com renderização no servidor: a sessão em cookies
No servidor não há localStorage, mas os cookies chegam em toda requisição. O armazenamento abaixo guarda a sessão em quatro cookies httpOnly (a sessão inteira em um cookie passaria de 4 KB). É o mesmo do template Next.js, onde está testado; aqui ele recebe três funções, para servir a qualquer framework.
type Pote = {
ler(nome: string): string | undefined
gravar(nome: string, valor: string): void
apagar(nome: string): void
}
const COOKIES = { acesso: 'sdb-acesso', renovacao: 'sdb-renovacao', dados: 'sdb-dados', vence: 'sdb-vence' }
export function armazenamentoEmCookies(pote: Pote) {
return {
getItem(chave: string) {
if (chave !== 'superdb.auth.session') return null
const acesso = pote.ler(COOKIES.acesso), renovacao = pote.ler(COOKIES.renovacao)
if (!acesso || !renovacao) return null
const vence = Number(pote.ler(COOKIES.vence)) || 0
return JSON.stringify({
access_token: acesso, refresh_token: renovacao,
data_plane_token: pote.ler(COOKIES.dados) || null,
expires_at: vence, expires_in: Math.max(0, Math.round((vence - Date.now()) / 1000)),
token_type: 'bearer', user: null,
})
},
setItem(chave: string, valor: string) {
if (chave !== 'superdb.auth.session') return
const s = JSON.parse(valor)
pote.gravar(COOKIES.acesso, s.access_token)
pote.gravar(COOKIES.renovacao, s.refresh_token)
pote.gravar(COOKIES.dados, s.data_plane_token ?? '')
pote.gravar(COOKIES.vence, String(s.expires_at))
},
removeItem(chave: string) {
if (chave === 'superdb.auth.session') Object.values(COOKIES).forEach((n) => pote.apagar(n))
},
}
}
Em cada framework, o pote é a API de cookies da requisição. Use sempre httpOnly, sameSite: 'lax', path: '/' e secure em produção:
| Framework | Onde criar o cliente | ler / gravar / apagar |
|---|---|---|
| Astro | middleware (onRequest) e páginas | context.cookies.get(n)?.value · .set(n, v, opções) · .delete(n, { path: '/' }) |
| SvelteKit | hooks.server.ts (handle), +page.server.ts | event.cookies.get(n) · .set(n, v, { path: '/', ...opções }) · .delete(n, { path: '/' }) |
| Nuxt (servidor) | server/middleware, server/api | getCookie(event, n) · setCookie(event, n, v, opções) · deleteCookie(event, n) |
| Qwik City | routeLoader$, routeAction$, onRequest | requestEv.cookie.get(n)?.value · .set(n, v, opções) · .delete(n, { path: '/' }) |
| Solid Start | funções "use server" | getCookie(n) · setCookie(n, v, opções) · deleteCookie(n) (de vinxi/http) |
Crie o cliente dentro da requisição (no hook, na action, no loader). Um cliente numa variável de módulo seria compartilhado entre visitantes no servidor.
Renovar a sessão sem derrubá-la
O token vale 1 hora. Renove no ponto por onde toda requisição passa (o middleware ou hook do framework) quando faltar menos de 1 minuto, chamando superdb.auth.refreshSession() com o cliente de cookies: o SDK grava os tokens novos pelo armazenamento.
O cuidado: o SuperDB troca o refresh token a cada renovação e trata o uso de um token já trocado como roubo, derrubando a sessão. Várias requisições simultâneas do mesmo navegador chegam com o mesmo cookie. Faça uma renovação por refresh token, e entregue a sessão já renovada a quem chegar logo depois com o token antigo. O renovar.ts do template Next faz exatamente isso em 60 linhas, e serve de modelo para qualquer middleware.
Dados: igual em todo lugar
const { data, error } = await superdb
.from('tarefas')
.select('id,titulo,feita')
.order('criada_em', { ascending: false })
Com a tabela protegida por RLS (veja o schema.sql dos templates), a consulta devolve só as linhas de quem está logado, sem filtro nenhum no código.
Próximos passos
- Todos os templates
- Quickstart Next.js: a sessão em cookies, passo a passo
- Row Level Security
- Referência do SDK