Ambiente
Variáveis de ambiente obrigatórias e opcionais. O schema em apps/server/src/bootstrap/env.ts é a fonte de verdade.
Última atualização 27 de out. de 2026
Todas as env vars são validadas por um schema Zod em apps/server/src/bootstrap/env.ts. O servidor falha na inicialização com um erro claro se uma var obrigatória estiver faltando ou malformada. Adapters opcionais sobem como no-op quando sua configuração está vazia — você nunca precisa preencher todas as chaves.
Mínimo para subir
Estas quatro são o piso. Sem elas o servidor sai cedo:
BETTER_AUTH_SECRET=<openssl rand -base64 32>
BETTER_AUTH_URL=http://localhost:3005
DATABASE_URL=postgresql://postgres:postgres@localhost:5432/boilerplate?schema=public
CORS_ORIGINS=http://localhost:3004Gere o auth secret com openssl rand -base64 32. CORS_ORIGINS é uma lista separada por vírgula de origins permitidos para o browser.
Auth providers (opcional)
GOOGLE_CLIENT_ID=
GOOGLE_CLIENT_SECRET=Deixe ambos vazios para desabilitar o Google OAuth. O magic link é configurado via seu provider de email — veja o módulo notifications.
Storage (opcional)
STORAGE_PROVIDER=null # null | local | s3 | uploadthing
# S3 / R2 / MinIO
STORAGE_S3_BUCKET=
STORAGE_S3_REGION=
STORAGE_S3_ACCESS_KEY_ID=
STORAGE_S3_SECRET_ACCESS_KEY=
STORAGE_S3_ENDPOINT= # defina para R2/MinIO; deixe vazio para AWS
STORAGE_S3_PUBLIC_BASE_URL= # base do CDN
# Disco local (dev)
STORAGE_LOCAL_ROOT=
STORAGE_LOCAL_PUBLIC_BASE_URL=
# UploadThing
UPLOADTHING_TOKEN=
UPLOADTHING_APP_ID=STORAGE_PROVIDER=null (o default) pula o registro de um adapter, então endpoints que precisam de uploads retornam 503. O provider local constrói URLs públicas a partir de BETTER_AUTH_URL (a URL do server), não APP_URL — uploads são servidos pela API, não pelo Next.js.
Background jobs (opcional)
REDIS_URL=redis://localhost:6379
JOBS_REDIS_URL= # padrão: REDIS_URL
JOBS_PREFIX=bull
BULL_BOARD_ENABLED=true # monta a UI em /admin/queuesSem Redis, o BullMQ é registrado mas nenhum worker conecta. BULL_BOARD_ENABLED=true monta uma UI Bull Board protegida em /admin/queues.
Rate limiting
RATE_LIMIT_REDIS_ENABLED=true # cai para memória quando REDIS_URL está vazio
RATE_LIMIT_READ_PER_MIN=300
RATE_LIMIT_WRITE_PER_MIN=60
RATE_LIMIT_AUTH_IP_PER_MIN=30
RATE_LIMIT_AUTH_EMAIL_PER_15MIN=5Os quatro tiers (read, write, auth-ip, auth-email) têm cada um seu próprio prefixo Redis (rl:read:, rl:write:, etc.) para que os counters nunca colidam. Os handlers do limiter são memoizados sobre a referência RateLimitDeps, fazendo com que o modo memória se comporte de forma idêntica ao modo Redis (um store compartilhado por tier).
AI chat (opcional)
ANTHROPIC_API_KEY= # vazio = stream stub, sem chamadas upstream
AI_MODEL=claude-haiku-4-5 # modelo padrão, sempre permitido
AI_ALLOWED_MODELS= # modelos extras que o cliente pode pedir (separados por vírgula)
AI_MAX_OUTPUT_TOKENS=1024 # limite rígido de tokens gerados por completion
AI_STREAM_TIMEOUT_MS=120000 # teto de tempo (ms) por stream de chat; aborta o upstreamPOST /api/v1/ai/chat rejeita com 422 qualquer model fora de AI_MODEL + AI_ALLOWED_MODELS, então um caller não consegue escolher um modelo mais caro.
Observabilidade (opcional)
SENTRY_DSN= # deixe vazio para desabilitar
SENTRY_TRACES_SAMPLE_RATE=0.1
APP_VERSION=
OTEL_EXPORTER_OTLP_ENDPOINT= # deixe vazio para desabilitar trace export
OTEL_SERVICE_NAME=mern-saas-server
METRICS_ENABLED=true # endpoint /metrics do PrometheusO OTel deve subir antes de Express/Prisma carregarem. O primeiro import em apps/server/src/index.ts é ./otel-init.js por causa disso. Não reordene.
O Sentry server SDK é @sentry/bun (não @sentry/node). logger.error(...) não auto-captura; use o helper captureError(err, ctx) para erros que devem paginar alguém.
Billing (depende do provider)
Defina PAYMENT_PROVIDER como um de stripe, mercado-pago, polar, depois preencha as chaves desse provider:
PAYMENT_PROVIDER=stripe
STRIPE_SECRET_KEY=
STRIPE_WEBHOOK_SECRET=
# ou
PAYMENT_PROVIDER=mercado-pago
MERCADO_PAGO_ACCESS_TOKEN=
MERCADO_PAGO_WEBHOOK_SECRET=
# ou
PAYMENT_PROVIDER=polar
POLAR_ACCESS_TOKEN=
POLAR_WEBHOOK_SECRET=
POLAR_SERVER=sandbox # ou production[!WARNING]
POLAR_SERVERésandboxpor default fora de produção. ComNODE_ENV=productionePAYMENT_PROVIDER=polarele deve ser definido explicitamente (sandboxouproduction): o server se recusa a subir se faltar, assim uma variável esquecida não manda tráfego real ao sandbox da Polar.
Veja Billing para a estratégia e setup de webhooks.
Usage metering (opcional)
Desabilitado por default. Ative a flag depois que o downstream registrar seus meters e um mapa plano → cap.
USAGE_METERING_ENABLED=false # defina "true" para enforçar cotas
USAGE_FREE_PRICE_ID=price_free_synthetic # chave sintética do tier Free; não é um price real do PolarVeja Usage metering para a referência completa.
Pagamentos cripto (opcional)
O módulo crypto-payments (Aliquot) sobe sem nenhuma destas. Enquanto CRYPTO_MASTER_MNEMONIC não estiver definida, todo endpoint /api/v1/crypto/* e /api/v1/public/* responde 503 CRYPTO_NOT_CONFIGURED; o resto do server não é afetado.
CRYPTO_MASTER_MNEMONIC="<BIP-39 mnemonic>" # custody; unset → crypto endpoints answer 503
CRYPTO_RPC_POLYGON_AMOY=https://polygon-amoy-bor-rpc.publicnode.com,https://polygon-amoy.drpc.org
CRYPTO_RPC_BSC_TESTNET=https://bsc-testnet-rpc.publicnode.com
CRYPTO_RPC_BASE_SEPOLIA=https://sepolia.base.org,https://base-sepolia-rpc.publicnode.com
# CRYPTO_RPC_POLYGON_MAINNET= / CRYPTO_RPC_BSC_MAINNET= / CRYPTO_RPC_BASE_MAINNET=
# CRYPTO_CONFIRMATIONS_POLYGON_AMOY=5 # dev-only override (defaults: Polygon 64, BSC 15, Base 30)
CRYPTO_PLATFORM_FEE_BPS=50 # default processing fee (0.50 %) for orgs without their own config
CRYPTO_PAYOUT_FEE_POLYGON=0.10 # flat token fee per payout, per network family
CRYPTO_PAYOUT_FEE_BSC=0.15
CRYPTO_PAYOUT_FEE_BASE=0.10
# CRYPTO_LOGS_MAX_RANGE_BSC_TESTNET=200 # max blocks per eth_getLogs call (default 200, all 6 networks)
# CRYPTO_NATIVE_PRICE_USD_POLYGON=0.50 # fixed native price for refund fee quotes (BSC 600, BASE 3000)
# CRYPTO_CHECKOUT_BASE_URL=https://pay.example.com # hosted checkout origin; defaults to APP_URL
# TELEGRAM_BOT_TOKEN= # Telegram Mini-App initData verificationCRYPTO_MASTER_MNEMONICé a wallet de custódia (HD pathm/44'/60'/0'/0/i: o índice 0 é a hot wallet, os endereços de depósito das invoices começam em 1000). O processo da API deriva endereços só a partir da chave pública estendida; a assinatura acontece no worker. Precisa ser um mnemonic BIP-39 válido em inglês.CRYPTO_RPC_<NETWORK>aceita URLs separadas por vírgula (fallback do viem, a primeira é a principal). Uma rede sem URLs aparece desabilitada emGET /api/v1/crypto/networkse não pode receber invoices. Para chains locais rodebun run chain:deve aponte estas variáveis para as URLs impressas.NEXT_PUBLIC_CRYPTO_RPC_<NETWORK>(cliente,apps/client/.env.local, opcional, uma URL) é o RPC que o botão "Pagar com wallet" do checkout entrega à wallet do comprador quando a wallet ainda não conhece a rede (wallet_addEthereumChain). Sem definir, o checkout usa o RPC público da rede. Defina apenas para chains locais, por exemploNEXT_PUBLIC_CRYPTO_RPC_POLYGON_AMOY=http://127.0.0.1:18002, para que uma wallet do navegador envie para a mesma chain anvil que o servidor escaneia. É embutida no build: reconstrua (ou reinicie onext dev) depois de mudá-la.NEXT_PUBLIC_SITE_URL(cliente, opcional) é a origem canônica do site público: metadata, Open Graph, sitemap, robots, JSON-LD ellms.txt. Sem definir, éhttps://aliquot.cc, o domínio de produção. Defina para um deploy de staging. É embutida no build.CRYPTO_CHECKOUT_BASE_URLmontapay_url(<base>/pay/<invoiceId>), os links de resgate de refunds (<base>/pay/<invoiceId>/refund/<token>) e as URLs de payment links (<base>/l/<slug>).CRYPTO_LOGS_MAX_RANGE_<NETWORK>limita o intervalo de blocos de cada chamadaeth_getLogs(default 200:sepolia.base.orgnão aceita mais). O scanner também divide pela metade qualquer intervalo que o provider recuse por tamanho e alcança um backlog em passos limitados, então raramente é preciso mexer.CRYPTO_NATIVE_PRICE_USD_<FAMILY>(POLYGON0.50,BSC600,BASE3000) converte o gas de um refund em unidades de stablecoin: o comprador recebegross − max(gas × max fee × preço, 0.01), fixado no resgate.TELEGRAM_BOT_TOKENé o token do bot que abre o checkout como Telegram Mini-App (/pay/<invoiceId>?tg=1).POST /api/v1/public/telegram/verifyverifica com ele a assinatura doinitDatada Mini-App (HMAC-SHA256, secret = HMAC("WebAppData", token)) e rejeita umauth_datecom mais de 24 h. Sem o token esse endpoint responde 503TELEGRAM_NOT_CONFIGURED; o checkout continua funcionando.- O worker (
bun run worker) roda o caminho do dinheiro: scanner de chain (2 s), expiração (10 s), sweeps (15 s), payouts (3 s), refunds (5 s). Precisa de Redis; sem Redis nada é escaneado nem enviado. O gas nativo que chega à hot wallet (índice 0) é registrado automaticamente como funding do operador (EQUITY:OPERATOR_FUNDING).
[!WARNING] Com
NODE_ENV=productiono server se recusa a subir com o mnemonic público de desenvolvimento (o commitado emapps/server/.enve.env.example), e rejeita overridesCRYPTO_CONFIRMATIONS_*para mainnets.
Adicionando uma nova variável
- Edite
apps/server/src/bootstrap/env.tse estenda o schema Zod. Vars obrigatórias precisam de.min(1); opcionais usam.optional()ou.default(...). - Adicione uma entrada documentada em
apps/server/.env.example. - Leia de
env.YOUR_VAR— nunca deprocess.envdiretamente.