Escolher um banco de dados

Nesta página

Escolha um adaptador de banco de dados por implantação. O banco de dados guarda o modelo de conteúdo, entradas, usuários, configurações e dados de plugins. Binários de mídia pertencem a um backend de armazenamento separado.

Visão geral

DatabaseUse it whenRuntime
SQLiteUm processo Node.js tem um disco persistenteNode.js ou desenvolvimento local
D1O site roda no Cloudflare Workers e deve usar Cloudflare SQLCloudflare Workers
HyperdriveO site roda no Workers e deve usar uma origem PostgreSQL existenteCloudflare Workers
PostgreSQLVários processos Node.js precisam de um banco compartilhadoNode.js
libSQLUma implantação Node.js precisa de um banco remoto compatível com SQLiteNode.js

D1 é o padrão dos templates Cloudflare. SQLite é a opção Node.js mais simples, mas exige um volume persistente gravável e backups operacionais do banco.

SQLite

SQLite usa o driver de banco integrado do Node.js e é a opção mais simples para implantações Node.js.

import { sqlite } from "emdash/db";

export default defineConfig({
	integrations: [
		emdash({
			database: sqlite({ url: "file:./data.db" }),
		}),
	],
});

Configuração

OptionTypeDescription
urlstringCaminho de arquivo com prefixo file:

Caminho de arquivo

A url deve começar com file::

// Relative path
database: sqlite({ url: "file:./data/emdash.db" });

// Absolute path
database: sqlite({ url: "file:/var/data/emdash.db" });

// From environment variable
database: sqlite({ url: `file:${process.env.DATABASE_PATH}` });

Write-ahead logging

O EmDash abre bancos SQLite no modo write-ahead logging (WAL). Enquanto o site roda, o SQLite mantém dois arquivos extras ao lado do banco, como emdash.db-wal e emdash.db-shm. O processo precisa de acesso de escrita ao diretório do banco para criá-los.

O arquivo -wal pode conter alterações confirmadas que ainda não estão no arquivo principal do banco. Faça backup com o comando de backup do SQLite em vez de copiar só o arquivo .db. Veja Backup e recuperação SQLite.

WAL exige memória compartilhada, então mantenha o banco em um disco local ou volume de bloco em vez de um sistema de arquivos de rede como NFS ou SMB.

Cloudflare D1

D1 é o banco SQLite serverless da Cloudflare. Use-o ao implantar no Cloudflare Workers.

import { d1 } from "@emdash-cms/cloudflare";

export default defineConfig({
	integrations: [
		emdash({
			database: d1({ binding: "DB" }),
		}),
	],
});

Configuração

OptionTypeDefaultDescription
bindingstring—Nome do binding D1 de wrangler.jsonc
sessionstring"disabled"Modo de replicação de leitura (veja abaixo)
bookmarkCookiestring"__em_d1_bookmark"Nome do cookie para marcadores de sessão

Binding Wrangler

wrangler.jsonc

{
  "d1_databases": [
    {
      "binding": "DB",
      "database_name": "emdash-db"
    }
  ]
}

wrangler.toml

[[d1_databases]]
binding = "DB"
database_name = "emdash-db"

O Wrangler pode provisionar um banco D1 ausente a partir deste binding durante a implantação. As migrações do EmDash são um passo separado. Siga Implantar na Cloudflare para o conjunto completo de bindings e Gerenciar migrações do banco principal para o runbook de migração.

Réplicas de leitura

O D1 oferece replicação de leitura para reduzir a latência de leitura em sites distribuídos globalmente. Quando habilitada, consultas de leitura são roteadas para réplicas próximas em vez de sempre atingir o banco primário.

O EmDash usa a API Sessions do D1 para gerenciar isso de forma transparente. Habilite com a opção session:

import { d1 } from "@emdash-cms/cloudflare";

export default defineConfig({
	integrations: [
		emdash({
			database: d1({
				binding: "DB",
				session: "auto",
			}),
		}),
	],
});

Modos de sessão

ModeBehavior
"disabled"Sem sessões. Todas as consultas vão ao primário. Padrão.
"auto"Solicitações anônimas leem da réplica mais próxima. Usuários autenticados obtêm consistência read-your-writes via cookies de marcador.
"primary-first"Como "auto", mas a primeira consulta sempre vai ao primário. Use em sites com escritas muito frequentes.

Como funciona

  • Visitantes anônimos recebem first-unconstrained — leituras vão à réplica mais próxima para a menor latência. Como usuários anônimos nunca escrevem, não precisam de garantias de consistência.
  • Usuários autenticados (editores, autores) recebem sessões baseadas em marcadores. Após uma escrita, um cookie de marcador garante que a próxima solicitação veja pelo menos esse estado.
  • Solicitações de escrita (POST, PUT, DELETE) sempre começam no banco primário.
  • Consultas em tempo de build (coleções de conteúdo Astro) ignoram sessões por completo e usam o primário diretamente.

libSQL

libSQL é um fork do SQLite que oferece conexões remotas. Use-o quando precisar de um banco remoto sem Cloudflare D1.

import { libsql } from "emdash/db";

export default defineConfig({
	integrations: [
		emdash({
			database: libsql({
				url: process.env.LIBSQL_DATABASE_URL,
				authToken: process.env.LIBSQL_AUTH_TOKEN,
			}),
		}),
	],
});

Configuração

OptionTypeDescription
urlstringURL do banco (libsql://... ou file:...)
authTokenstringToken de autenticação em runtime para bancos remotos (opcional no local)
migrationAuthTokenEnvstringNome da variável do token de migração (padrão TURSO_AUTH_TOKEN)

Desenvolvimento local

Use um arquivo libSQL local durante o desenvolvimento:

database: libsql({ url: "file:./data.db" });

PostgreSQL

PostgreSQL é suportado em implantações Node.js que precisam de um banco relacional completo.

import { postgres } from "emdash/db";

export default defineConfig({
	integrations: [
		emdash({
			database: postgres({
				connectionString: process.env.DATABASE_URL,
			}),
		}),
	],
});

Configuração

Você pode conectar com uma connection string ou parâmetros individuais:

// Connection string
database: postgres({
	connectionString: "postgres://user:password@localhost:5432/emdash",
});

// Individual parameters
database: postgres({
	host: "localhost",
	port: 5432,
	database: "emdash",
	user: "emdash",
	password: process.env.DB_PASSWORD,
	ssl: true,
});
OptionTypeDescription
connectionStringstringURL de conexão PostgreSQL
hoststringHost do banco
portnumberPorta do banco
databasestringNome do banco
userstringUsuário do banco
passwordstringSenha do banco
sslbooleanHabilitar SSL
pool.minnumberConexões mínimas do pool (padrão 0)
pool.maxnumberConexões máximas do pool (padrão 10)
pool.connectionTimeoutMillisnumberEspera máxima de conexão (padrão pg: 0, sem tempo limite)
pool.idleTimeoutMillisnumberVida útil do cliente ocioso (padrão pg: 10.000 ms)
migrationConnectionStringEnvstringNome da variável da connection string de migração (padrão DATABASE_URL)

Defina pool.connectionTimeoutMillis com um valor diferente de zero para limitar quanto uma solicitação espera quando o PostgreSQL está inacessível ou nenhuma conexão do pool fica disponível. Defina pool.idleTimeoutMillis como 0 para manter clientes ociosos abertos até o pool fechar. Omitir qualquer opção preserva o padrão do pg.

Requisitos da função do banco

O EmDash cria e atualiza suas próprias tabelas PostgreSQL. Migrações principais criam e alteram tabelas de sistema e de coleções, tipos de conteúdo criam tabelas ec_*, e adicionar ou remover um campo altera sua tabela de coleção. A função PostgreSQL configurada precisa, portanto, de autoridade de esquema durante a vida do site, não só na configuração inicial.

Use uma função canônica para o EmDash. Ela precisa de:

  • CONNECT no banco;
  • USAGE e CREATE no esquema ativo;
  • propriedade de cada tabela e função EmDash, diretamente ou por associação com INHERIT na função proprietária; e
  • SELECT, INSERT, UPDATE e DELETE nessas tabelas.

Não precisa ser superusuário, ter CREATEDB ou CREATEROLE, nem criar extensões. O PostgreSQL não fornece concessão de tabela ALTER ou DROP: essas operações pertencem ao proprietário do objeto e a funções que herdam seus privilégios. Conceder ALL em uma tabela a outra função não a torna proprietária. O EmDash não executa SET ROLE, então associação configurada sem herança não basta.

A maioria das instalações pode usar o esquema existente do banco, comumente public. É a opção mais simples quando o banco é dedicado ao EmDash. Nos exemplos abaixo, emdash_app é a função de login na connection string do EmDash; use uma função de provedor existente ou crie um login dedicado. Conceda acesso com uma conexão administrativa, substituindo os nomes de banco, esquema e função:

GRANT CONNECT ON DATABASE app TO emdash_app;
GRANT USAGE, CREATE ON SCHEMA public TO emdash_app;

Essas concessões permitem que a função crie novos objetos. Não alteram o proprietário de tabelas existentes; use o runbook de reparo de propriedade PostgreSQL quando um site existente tiver proprietários mistos.

O EmDash usa o current_schema() ativo do PostgreSQL. Não cria um esquema nem define search_path, então verifique a conexão antes da implantação:

SELECT
  current_database(),
  session_user,
  current_user,
  current_schema(),
  current_setting('search_path');

Opcional: usar um esquema dedicado

Use um esquema dedicado quando o EmDash compartilha um banco com outro aplicativo ou quando quer isolar seus objetos de public. É opcional e é mais fácil configurar antes da primeira configuração do EmDash. Um banco dedicado ao EmDash não precisa de um esquema separado.

Assumindo que a função canônica emdash_app já existe, crie e selecione seu esquema com uma conexão administrativa:

GRANT CONNECT ON DATABASE app TO emdash_app;
CREATE SCHEMA emdash AUTHORIZATION emdash_app;
ALTER ROLE emdash_app IN DATABASE app SET search_path = emdash;

Isso não move uma instalação existente de public nem repara propriedade mista. Sites existentes devem manter o esquema atual e usar o runbook de reparo de propriedade PostgreSQL em vez disso.

Pool de conexões

O adaptador usa pg.Pool. Ajuste o tamanho do pool conforme sua implantação:

database: postgres({
	connectionString: process.env.DATABASE_URL,
	pool: { min: 2, max: 20 },
});

Hyperdrive

Use o adaptador hyperdrive() para rodar o EmDash no Cloudflare Workers com um banco PostgreSQL existente — ou compatível com Postgres (p. ex. PlanetScale Postgres). O Hyperdrive agrupa e acelera a conexão na rede da Cloudflare; o dialeto PostgreSQL do EmDash executa as consultas.

import { hyperdrive, r2 } from "@emdash-cms/cloudflare";

export default defineConfig({
	integrations: [
		emdash({
			database: hyperdrive({ binding: "HYPERDRIVE" }),
			storage: r2({ binding: "MEDIA" }),
		}),
	],
});

Requisitos

  • pg >= 8.16.3 instalado no seu site (pnpm add pg)
  • compatibility_flags: ["nodejs_compat"]
  • compatibility_date >= "2024-09-23"

Configuração

Primeiro prepare a função PostgreSQL. Depois crie a configuração Hyperdrive com a connection string dessa função e adicione o binding à configuração Wrangler:

wrangler hyperdrive create emdash-db \
  --connection-string "postgres://user:password@host/db?sslmode=verify-full" \
  --caching-disabled

wrangler.jsonc

{
  "hyperdrive": [
    {
      "binding": "HYPERDRIVE",
      "id": "<your-hyperdrive-id>"
    }
  ]
}

wrangler.toml

[[hyperdrive]]
binding = "HYPERDRIVE"
id = "<your-hyperdrive-id>"

Configuração

OptionTypeDefaultDescription
bindingstring"HYPERDRIVE"Nome do binding Hyperdrive primário (cache desabilitado)
cachedBindingstring—Binding opcional com cache habilitado para leituras anônimas (veja abaixo)
preferUncachedAfterWriteMsnumber60000*Após publicar conteúdo, preferir binding por estes ms em leituras públicas anônimas (alinhar ao max_age do Hyperdrive)
migrationConnectionStringEnvstringCLOUDFLARE_HYPERDRIVE_LOCAL_CONNECTION_STRING_<BINDING>Variável de ambiente com a URL direta da origem PostgreSQL para emdash migrate
maxnumber5Tamanho máximo do pool de conexões in-Worker para o Hyperdrive

*O padrão 60000 se aplica apenas quando cachedBinding está definido; caso contrário é ignorado.

Servir leituras anônimas do cache

Por padrão você desabilita o cache do Hyperdrive por completo, porque a administração e as escritas precisam de consistência read-after-write. Mas solicitações públicas anônimas com GET ou HEAD podem tolerar uma janela curta de desatualização. Se esse compromisso for aceitável, execute duas configurações Hyperdrive no mesmo banco: uma com cache desligado (o binding primário) e uma com cache ligado (cachedBinding). O EmDash roteia essas solicitações públicas anônimas pelo binding com cache e todas as outras pelo primário sem cache.

# Primary — caching OFF (used by admin, auth'd requests, writes, migrations)
wrangler hyperdrive create emdash-db \
  --connection-string "postgres://user:password@host/db?sslmode=verify-full" \
  --caching-disabled

# Cached — SAME database role and connection string, caching ON
wrangler hyperdrive create emdash-db-cached \
  --connection-string "postgres://user:password@host/db?sslmode=verify-full"
{
	"hyperdrive": [
		{ "binding": "HYPERDRIVE", "id": "<caching-disabled-id>" },
		{ "binding": "HYPERDRIVE_CACHED", "id": "<caching-enabled-id>" }
	]
}
database: hyperdrive({ binding: "HYPERDRIVE", cachedBinding: "HYPERDRIVE_CACHED" });

Este é o padrão de duas configurações que a Cloudflare documenta para cache. O EmDash decide qual binding usar por solicitação:

  • Leituras anônimas de caminhos do site público (GET/HEAD, sem sessão, não sob /_emdash) → cachedBinding com cache, exceto por uma janela curta após publicar conteúdo (padrão 60 s; defina preferUncachedAfterWriteMs para o max_age do Hyperdrive) quando o EmDash prefere o binding sem cache para que um rebuild não possa reabastecer caches de edge/objeto a partir de resultados ainda desatualizados do Hyperdrive.
  • Solicitações autenticadas (editores, autores) → binding sem cache.
  • Solicitações de mutação (POST, PUT, PATCH, DELETE, incluindo anônimas) → binding sem cache.
  • Qualquer solicitação sob /_emdash (admin, configuração, auth, APIs internas), mesmo um GET anônimo → binding sem cache.
  • Migrações em runtime e cold-start → sempre o binding primário.
  • Migrações gerenciadas pela implantação → conectam diretamente à origem PostgreSQL com migrationConnectionStringEnv; nunca usam nenhum binding Hyperdrive.

Opcional: usar uma função em cache separada

Migrações, configuração, solicitações autenticadas e escritas explícitas sempre usam o binding primário. Uma função separada para cachedBinding não precisa de propriedade do esquema nem de CREATE, mas precisa de CONNECT, USAGE no esquema e SELECT em cada tabela usada pelo site público.

Solicitações públicas anônimas GET e HEAD também podem registrar acertos de redirecionamento e 404s. Para preservar esses recursos, a função em cache precisa ainda de UPDATE em _emdash_redirects e SELECT, INSERT, UPDATE e DELETE em _emdash_404_log. Plugins ou código de aplicativo que escrevem durante um GET ou HEAD público podem exigir mais. Use a mesma função para ambos os bindings a menos que tenha testado o site com uma função em cache restrita.

Adicione a função em cache depois que o EmDash concluir suas migrações iniciais. Os exemplos abaixo usam o esquema opcional emdash; substitua seu esquema ativo, como public. Crie o login e as configurações do banco com a função administrativa do seu provedor:

CREATE ROLE emdash_cached LOGIN PASSWORD 'replace-with-a-secret';
GRANT CONNECT ON DATABASE app TO emdash_cached;
ALTER ROLE emdash_cached IN DATABASE app SET search_path = emdash;

Depois conecte-se como emdash_app, o proprietário do esquema e das tabelas, para conceder acesso a tabelas existentes e futuras:

GRANT USAGE ON SCHEMA emdash TO emdash_cached;
GRANT SELECT ON ALL TABLES IN SCHEMA emdash TO emdash_cached;
GRANT UPDATE ON emdash._emdash_redirects TO emdash_cached;
GRANT SELECT, INSERT, UPDATE, DELETE ON emdash._emdash_404_log TO emdash_cached;

ALTER DEFAULT PRIVILEGES IN SCHEMA emdash
  GRANT SELECT ON TABLES TO emdash_cached;

Conecte-se com ambas as funções e verifique se reportam o mesmo current_database() e current_schema() antes de habilitar cachedBinding. Em um esquema compartilhado, GRANT SELECT ON ALL TABLES também expõe tabelas não relacionadas. Conceda acesso a tabelas EmDash individuais em vez disso e atualize essas concessões quando coleções ou outros objetos de esquema forem adicionados.

Migrações principais

O EmDash executa migrações principais automaticamente por padrão para cada dialeto suportado. O build e o sync do Astro também emitem um .emdash/migrations.json validado e sem segredos, que emdash migrate pode aplicar antes da implantação. SQLite, libSQL, PostgreSQL, D1 e a origem PostgreSQL direta atrás do Hyperdrive têm executores de implantação.

Veja Gerenciar migrações do banco principal para credenciais de destino, serialização de CI, política de runtime auto/check/manual e recuperação de registros desconhecidos ou escritas D1 ambíguas.

Para PostgreSQL, migrações em runtime passam pela conexão configurada; migrações em runtime do Hyperdrive sempre usam seu binding primário. Migrações Hyperdrive gerenciadas pela implantação conectam diretamente à origem PostgreSQL. Migrações principais podem criar tabelas, índices e funções, alterar ou remover colunas e restrições, e atualizar linhas existentes. Uma função que pode conectar e modificar linhas mas não possui os objetos EmDash existentes não basta. O assistente de configuração não pode reparar privilégios de banco ausentes porque as migrações em runtime rodam antes da configuração.

Se o banco estiver vazio (sem coleções) e o assistente de configuração não tiver sido concluído, o EmDash também aplica um arquivo seed no primeiro boot. O seed é lido de .emdash/seed.json, o caminho em package.json#emdash.seed ou seed/seed.json — o que for encontrado primeiro — e embutido no build em tempo de compilação. Se nenhum estiver presente, um seed padrão integrado é usado. Boots seguintes contra um banco existente deixam seu conteúdo intacto.

Usar bancos separados para ambientes separados

Dê a desenvolvimento, preview, staging e produção cada um o seu próprio banco. Uma implantação de preview apontada para produção pode executar migrações principais ou comandos destrutivos do modelo de conteúdo contra dados ao vivo.

Para Cloudflare, defina cada binding D1 ou Hyperdrive sob o ambiente Wrangler correspondente e passe --env aos comandos Wrangler. Para Node.js, injete uma URL de banco diferente em cada ambiente de runtime. Mantenha credenciais em segredos de runtime, não em astro.config.mjs.