Como Tipar Variáveis de Ambiente em TypeScript
Você já escreveu process.env.API_KEY pela centésima vez, e o TypeScript ainda a tipa como string | undefined. Sua IDE não oferece autocomplete. Pior ainda, você fez deploy para produção apenas para descobrir que uma variável ausente travou sua aplicação. As variáveis de ambiente em TypeScript merecem um tratamento melhor do que isso.
Este guia mostra como adicionar type safety às variáveis de ambiente em projetos frontend modernos e full-stack—cobrindo tanto import.meta.env para configurações baseadas em Vite quanto process.env para contextos Node.
Pontos-Chave
- Aplicações browser usam injeção em tempo de build (variáveis incorporadas nos bundles), enquanto Node.js lê variáveis de ambiente em runtime—essa distinção afeta tanto as estratégias de segurança quanto de tipagem.
- Use declarações
ImportMetaEnvpara projetos Vite e augmentation deNodeJS.ProcessEnvpara contextos Node para obter autocomplete da IDE e verificação de tipos. - Tipos TypeScript sozinhos não podem prevenir crashes em runtime—sempre valide variáveis de ambiente obrigatórias na inicialização usando verificações simples ou bibliotecas de schema como Zod.
- Nunca coloque secrets em variáveis com prefixo (
VITE_,NEXT_PUBLIC_) pois estas se tornam visíveis nos bundles client-side.
Entendendo Variáveis de Ambiente em Build-Time vs. Runtime
Antes de tipar qualquer coisa, entenda uma distinção crítica: aplicações browser e servidores lidam com variáveis de ambiente de forma diferente.
Injeção em build-time (aplicações browser): Ferramentas como Vite substituem referências a variáveis de ambiente com valores reais durante o build. As variáveis não existem em runtime—elas são incorporadas ao seu bundle JavaScript.
Variáveis de ambiente em runtime (server-side): Node.js lê process.env do ambiente do sistema real quando seu código é executado. Os valores podem mudar entre deploys sem necessidade de rebuild.
Essa distinção é importante para segurança. Qualquer variável injetada em build-time se torna visível no seu código client-side. É por isso que frameworks usam regras de exposição baseadas em prefixos—Vite só expõe variáveis começando com VITE_, e Next.js expõe variáveis client-side com o prefixo NEXT_PUBLIC_. Chaves privadas sem esses prefixos permanecem apenas server-side.
Tipando import.meta.env em Projetos Vite
Vite usa import.meta.env ao invés de process.env. Para adicionar variáveis de ambiente type-safe em TypeScript, crie um arquivo de declaração:
// env.d.ts
/// <reference types="vite/client" />
interface ImportMetaEnv {
readonly VITE_API_URL: string
readonly VITE_APP_TITLE: string
// adicione mais variáveis aqui
}
interface ImportMeta {
readonly env: ImportMetaEnv
}
Agora o TypeScript fornece autocomplete e trata essas variáveis como string ao invés de string | undefined. Coloque este arquivo no seu diretório src e certifique-se de que seu tsconfig.json o inclui.
Tipando ProcessEnv para Contextos Node.js
Para código server-side ou ferramentas usando process.env, faça augmentation da interface NodeJS.ProcessEnv:
// globals.d.ts
declare namespace NodeJS {
interface ProcessEnv {
DATABASE_URL: string
API_SECRET: string
NODE_ENV: 'development' | 'production' | 'test'
}
}
Essa abordagem fornece autocomplete em toda sua codebase. O exemplo de NODE_ENV mostra que você pode usar union types para variáveis com valores possíveis conhecidos.
Discover how at OpenReplay.com.
Por Que Tipos Sozinhos Não São Suficientes
Aqui está o problema: declaration merging diz ao TypeScript o que deveria existir, não o que existe de fato. Você tipou DATABASE_URL como string, mas se alguém esquecer de configurá-la, sua aplicação trava em runtime.
Tipos TypeScript são apagados em tempo de compilação. Eles não podem validar que variáveis de ambiente realmente existem quando seu código executa.
Validando Variáveis de Ambiente na Inicialização
Para validar variáveis de ambiente em TypeScript, verifique-as cedo—antes que sua aplicação faça qualquer coisa importante. Uma abordagem simples:
// config.ts
function getEnvVar(key: string): string {
const value = process.env[key]
if (!value) {
throw new Error(`Missing required environment variable: ${key}`)
}
return value
}
export const config = {
databaseUrl: getEnvVar('DATABASE_URL'),
apiSecret: getEnvVar('API_SECRET'),
} as const
Importe este módulo config no ponto de entrada da sua aplicação. Se qualquer variável estiver ausente, você saberá imediatamente ao invés de descobrir no meio de uma requisição.
Para validação mais robusta, bibliotecas como Zod permitem definir schemas que validam tipos, formatos e restrições:
import { z } from 'zod'
const envSchema = z.object({
DATABASE_URL: z.string().url(),
PORT: z.string().transform(Number).pipe(z.number().min(1)),
})
export const env = envSchema.parse(process.env)
Isso falha rapidamente com mensagens de erro claras se DATABASE_URL não for uma URL válida ou PORT não for um número.
Mantendo Suas Variáveis Seguras
Lembre-se das regras de prefixo. Em projetos Vite, apenas variáveis com prefixo VITE_ chegam ao browser. Todo o resto está disponível apenas para o processo de build (server-side) e não é enviado ao browser. Nunca coloque secrets em variáveis com prefixo—elas ficarão visíveis no seu bundle de produção.
Para secrets server-side, confie na configuração de ambiente da sua plataforma de hospedagem ao invés de arquivos .env em produção. Adicione .env ao seu .gitignore imediatamente.
Conclusão
Variáveis de ambiente type-safe em TypeScript requerem duas camadas: declaration merging para autocomplete em tempo de compilação e validação em runtime para segurança em produção. Use ImportMetaEnv para projetos Vite, NodeJS.ProcessEnv para contextos Node, e sempre valide variáveis obrigatórias na inicialização. Seu eu futuro—debugando um incidente de produção às 3 da manhã—agradecerá.
Perguntas Frequentes
Sim, mas mantenha as declarações separadas. Crie um arquivo env.d.ts com ImportMetaEnv para seus pacotes frontend Vite e um globals.d.ts com augmentation de NodeJS.ProcessEnv para pacotes backend. O tsconfig.json de cada pacote deve incluir apenas seu arquivo de declaração relevante para evitar conflitos de tipos.
Seu tsconfig.json provavelmente não está incluindo o arquivo de declaração. Verifique se seu array include cobre a localização do arquivo, ou adicione o caminho do arquivo explicitamente. Verifique também se você não está acidentalmente sobrescrevendo a interface em outro arquivo de declaração. Reinicie seu servidor TypeScript após fazer mudanças.
Para aplicações frontend, validação em build-time é mais útil já que as variáveis são incorporadas durante o processo de build. Adicione um script prebuild que verifica se as variáveis VITE_ obrigatórias existem antes do Vite executar. Validação em runtime no browser não pode se recuperar de variáveis ausentes de qualquer forma.
Marque variáveis opcionais com um ponto de interrogação na sua declaração de interface, como OPTIONAL_VAR?: string. Para validação, use o método optional() do Zod ou forneça valores padrão com default(). Seu objeto config deve refletir quais variáveis são realmente obrigatórias versus desejáveis.
Complete picture for complete understanding
Capture every clue your frontend is leaving so you can instantly get to the root cause of any issue with OpenReplay — the open-source session replay tool for developers. Self-host it in minutes, and have complete control over your customer data.
Check our GitHub repo and join the thousands of developers in our community.