Spraxium logoSpraxium

Variáveis de Ambiente

O pacote @spraxium/env valida e interpreta variáveis de ambiente no boot. Aprenda a definir schemas, associar propriedades a chaves de ambiente e entender o log de inicialização.

Que problema @spraxium/env resolve

Todo bot do Discord precisa de configurações que mudam entre ambientes: o token do bot, URIs de banco de dados, números de porta e feature flags. Ler esses valores diretamente de process.env é frágil porque variáveis de ambiente sempre são strings, podem estar ausentes e erros de digitação só aparecem em runtime, quando algum recurso quebra. O pacote @spraxium/env substitui leituras manuais de process.env por um sistema baseado em schema que valida, transforma e tipa cada variável antes de o bot iniciar.

Você define uma classe em que cada propriedade é vinculada a uma variável de ambiente pelo decorator @Env. Decorators de validação como IsString, IsPort ou IsDiscordToken declaram o formato esperado. No boot, o framework executa todos os validadores, imprime uma tabela de inicialização com todos os valores resolvidos e interrompe o processo se algo for inválido. Isso detecta erros de configuração imediatamente, em vez de deixá-los se propagar até virarem falhas em runtime.

O pacote também inclui uma classe base chamada SpraxiumBaseEnv, que já declara os bindings de DISCORD_TOKEN e NODE_ENV. Você pode estendê-la para que seu schema herde esses campos comuns e adicione apenas variáveis específicas do projeto. A classe de schema é injetável, então qualquer provider pode solicitá-la no construtor e acessar valores tipados e validados.

Definindo um schema

Um schema é uma classe comum decorada com @EnvSchema. Cada propriedade usa @Env para indicar qual variável de ambiente ela lê, além de um ou mais decorators de validação para declarar o tipo esperado. A classe funciona como fonte única da verdade para toda a configuração de que o bot precisa em runtime.

src/app.env.ts
import {
  EnvSchema,
  Env,
  IsString,
  IsPort,
  IsBoolean,
  IsOptional,
} from '@spraxium/env';

@EnvSchema()
export class AppEnv {
  @Env('DATABASE_URL')
  @IsString()
  DATABASE_URL!: string;

  @Env('PORT')
  @IsPort()
  PORT!: number;

  @Env('ENABLE_CACHE')
  @IsBoolean()
  @IsOptional()
  ENABLE_CACHE?: boolean;
}

O decorator @EnvSchema marca a classe para o pipeline de validação. Sem ele, o framework ainda valida, mas imprime um aviso. Mesmo assim, a recomendação é sempre incluí-lo para deixar a intenção explícita. O decorator @Env recebe o nome exato da variável de ambiente como primeiro argumento e, opcionalmente, um objeto de opções como segundo.

Cada propriedade precisa ter pelo menos um validador de tipo. Os validadores rodam na ordem em que foram declarados, de cima para baixo: primeiro, uma fase de transform converte a string bruta para o tipo de destino; depois, uma fase de validate verifica o valor convertido. Se algum validador falhar, o framework reúne todos os erros e os imprime antes de encerrar.

Estendendo a classe base

A classe SpraxiumBaseEnv inclui três campos pré-configurados de que quase todo bot precisa: DISCORD_TOKEN com IsDiscordToken, DISCORD_CLIENT_ID com IsDiscordId e IsOptional, e NODE_ENV com IsEnum restrito a cinco valores. Estender essa classe evita duplicar essas declarações em todo projeto.

src/app.env.ts
import { EnvSchema, Env, IsMongoUri, IsPort } from '@spraxium/env';
import { SpraxiumBaseEnv } from '@spraxium/env';

@EnvSchema()
export class AppEnv extends SpraxiumBaseEnv {
  @Env('MONGO_URI')
  @IsMongoUri()
  MONGO_URI!: string;

  @Env('API_PORT')
  @IsPort()
  API_PORT!: number;
}

A classe filha herda automaticamente os campos de token, client ID e ambiente do Node. O pipeline de validação coleta campos de toda a cadeia de protótipos, então os validadores definidos em SpraxiumBaseEnv rodam junto com os seus. O método get também é herdado, permitindo acessar qualquer campo pelo nome da propriedade com tipagem completa.

Você acessa o schema validado injetando-o em qualquer provider. Antes de o framework conseguir resolver essa injeção, é preciso registrar a instância validada no container raiz de DI a partir de main.ts. O framework não consegue fazer isso automaticamente porque chamar new AppEnv() diretamente pularia o pipeline de validação e deixaria todos os campos indefinidos. O método app.provide fecha essa lacuna: ele aceita qualquer token e uma instância já criada, armazenando-a no container raiz para que todo provider que declarar AppEnv no construtor receba o mesmo objeto validado.

src/main.ts
import { SpraxiumFactory } from '@spraxium/core';
import { EnvValidator } from '@spraxium/env';
import { AppEnv } from './app.env';
import { AppModule } from './app.module';

async function main() {
  const env = EnvValidator.validate(AppEnv);
  const app = await SpraxiumFactory.create({ token: env.token });

  app.useModule(AppModule);
  app.provide(AppEnv, env);

  await app.listen();
}

main();

Cada valor já chega interpretado e tipado, então você nunca precisa chamar parseInt nem comparar strings manualmente. Depois do registro, qualquer provider em qualquer módulo pode injetar AppEnv pelo construtor e acessar o mesmo singleton validado. Como todos compartilham exatamente os mesmos valores, não existe risco de ler configurações antigas ou inconsistentes entre módulos diferentes.

src/services/database.service.ts
import { Injectable } from '@spraxium/core';
import { AppEnv } from '../app.env';

@Injectable()
export class DatabaseService {
  constructor(private readonly env: AppEnv) {}

  connect() {
    return this.createConnection(this.env.MONGO_URI, this.env.API_PORT);
  }
}

O objeto de opções do @Env

O segundo argumento de @Env aceita duas opções que controlam valores padrão e a ocultação no log de inicialização. A opção default fornece um fallback quando a variável de ambiente está ausente, o que também torna o campo implicitamente opcional. A opção secret controla se o valor aparece na tabela de inicialização ou se é mascarado com pontos.

src/app.env.ts
@Env('API_PORT', { default: '3000', secret: false })
@IsPort()
API_PORT!: number;

Tabela de inicialização e hot reload

Quando o schema é validado com sucesso, o framework imprime uma tabela no console mostrando o nome de cada variável, seu valor resolvido ou a máscara, e a origem dele: env, default ou ausente para campos opcionais. Isso dá visibilidade imediata sobre a configuração que o bot carregou e facilita identificar quando um arquivo .env está desatualizado ou quando uma variável foi resolvida a partir do valor padrão em vez do valor real.

No modo de desenvolvimento, se a validação falhar o framework não encerra. Em vez disso, ele observa o arquivo .env em busca de mudanças e revalida automaticamente. Quando você corrige a variável inválida e salva o arquivo, o validador roda de novo, imprime um diff mostrando o que mudou e continua a sequência de boot. Esse ciclo de hot reload elimina a necessidade de reiniciar o processo toda vez que você corrige um erro de digitação no arquivo .env.

O framework oferece suporte a arquivos .env.local e .env nativamente, carregados nessa ordem de prioridade. Variáveis que já existem em process.env nunca são sobrescritas pelo carregador de arquivos, então variáveis de ambiente definidas no sistema ou na CI sempre têm precedência sobre valores vindos de arquivo.

Estrutura do projeto

Um bot típico mantém a classe de schema na raiz de src, para que o contrato de ambiente seja fácil de encontrar durante o bootstrap. Os serviços importam AppEnv de src/app.env.ts, o que mantém os caminhos relativos curtos e previsíveis entre módulos. Os arquivos .env ficam na raiz do projeto, e a validação roda antes de qualquer provider iniciar.

project structure
app.env.ts
app.module.ts
main.ts
database.service.ts
.env
.env.local