E aí 🚀
Quando estamos construindo aplicações backend, é crucial lidar com informações sensíveis, como URLs para conexão com o banco de dados, chaves secretas e outras variáveis críticas.
E como não queremos deixar essas informações expostas, recorremos ao uso variáveis de ambiente para proteger nossa aplicação e evitar exposições indesejadas.
Nesse artigo, apresentarei uma forma que tenho usado em minhas aplicações Node e Nest.
Passo 1: Instalação dos pacotes - Node
Para projetos node, vamos precisar dos seguintes pacotes, zod e dotenv:
npm install zod dotenv
Dependendo da sua entrutura de pastas, costumo criar uma pasta para config e, dentro dela, um arquivo chamado env.ts
.
Passo 2: Criação do schema
O que vamos fazer agora é utilizar o zod para validação de nossas variáveis de ambiente.
import 'dotenv/config'
import { z } from 'zod'
const environmentSchema = z.object({
NODE_ENV: z.enum(['dev', 'test', 'prod']).default('dev'),
PORT: z.coerce.number().default(3333),
DB_URL: z.string(),
})
Anteriormente, importamos o zod e dotenv e, em seguida, criamos um esquema (schema) com base nas variaveis que serão utilizadas em nossa aplicação. Nesse caso,estamos considerando apenas as variáveis PORT/ DB_URL e NODE_ENV.
Mas como validamos esse esquema para nossas variaveis de ambiente?
// Código acima
const _env = environmentSchema.safeParse(process.env) //
if (!_env.success) {
console.error('Invalid environment variables', _env.error.format())
throw new Error('Invalid environment variables')
}
export const env = _env.data
Como o process.env
é retornado como um objeto, o que permite validar esse objeto em conformidade com o enviromnentSchema
e, caso não cumpra o "contrato" será lançado um erro.
Passo 3: Utilização
A utilizaração é bem simples:
import express from 'express'
import { env } from '../config/env' // caminho para a pasta em seu projeto
const app = express()
app.listen(env.PORT, () => {
console.log('server running')
})
Agora, vamos para o queridinho Nest.
Passo 1: Instalação dos pacotes - Nest
Nesse exemplo, criaremos um modulo, um serviço e um esquema para isso mas antes será necessário instalar o pacote abaixo:
npm i --save @nestjs/config
Passo 2: Criação do schema
Para o arquivo env, teremos algumas mudanças:
//env.ts
import { z } from 'zod';
export const envSchema = z.object({
DATABASE_URL: z.string().url(),
PORT: z.coerce.number().optional().default(3000),
});
export const validateEnv = (env: Record<string, any>) => {
const _env = envSchema.safeParse(process.env); //
if (!_env.success) {
console.error('Invalid environment variables');
throw new Error('Invalid environment variables');
}
return _env.data;
};
export type Env = z.infer<typeof envSchema>;
Passo 3: Trabalhando com módulos
Dentro do Nest, trabalhar com modulos e services é essencial. O modulo de env precisa ser visivel por toda aplicação, e o serviço precisa ser injetado em qualquer modulo em que for chamado.
Como assim injetar? serviço? Se está meio confuso, recomendo consultar a documentação do Nest antes de prosseguir com o artigo.
// env.service
import { Injectable } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { Env } from './env';
@Injectable()
export class EnvService {
constructor(private configService: ConfigService<Env, true>) {}
get<T extends keyof Env>(key: T) {
return this.configService.get(key, { infer: true });
}
}
No exemplo acima, é um serviço no nest, fazendo o uso do padrão de decorators
conseguimos tornar esse serviço acessível e utilizável em diferentes partes da aplicação.
// env.module.ts
import { Module } from '@nestjs/common';
import { EnvService } from './env.service';
@Module({
providers: [EnvService],
exports: [EnvService],
})
export class EnvModule {}
Conforme mencionado anteriormente, é crucial informar à nossa aplicação que esse módulo deve ser global. Para realizar essa configuração, vamos até o módulo principal da aplicação:
// app.module.ts
import { ConfigModule } from '@nestjs/config';
import { envSchema } from './infra/env';
@Module({
imports: [
ConfigModule.forRoot({
validate: (env) => validateEnv(env),
isGlobal: true,
}),
],
})
export class AppModule {}
Passo 4: Utilizando o serviço de variáveis
Mas e o arquivo main.ts
? como teria acesso as variáveis? como consigo iniciar minha aplicação na porta que eu desejo?
Ao utilizar o app
, podemos configurar e inicializar a aplicação conforme suas necessidades incluindo a porta que desejamos utilizar:
//main.ts
async function bootstrap() {
const app = await NestFactory.create(AppModule);
const configService = app.get(EnvService); //Acessando o service
const port = configService.get('PORT'); //Pegando a variável {PORT}
await app.listen(port);
}
bootstrap();
Por fim, caso seja necessário acessar alguma variável, podemos realizar a injeção desse serviço no módulo em que é necessário o acesso. Assim conseguimos garantir o uso variáveis.
// database.module.ts
import { EnvModule, EnvService } from '../env';
@Module({
imports: [
TypeOrmModule.forRootAsync({
imports: [EnvModule],
inject: [EnvService],
useClass: TypeOrmConfigService,
}),
],
})
export class DatabaseModule {}
//typeorm.service.ts
@Injectable()
export class TypeOrmConfigService implements TypeOrmOptionsFactory {
constructor(private config: EnvService) {}
createTypeOrmOptions(): TypeOrmModuleOptions {
return {
type: 'postgres',
host: this.config.get('DB_HOST'),
port: this.config.get('DB_PORT'),
username: this.config.get('DB_USERNAME'),
password: this.config.get('DB_PASSWORD'),
database: this.config.get('DB_NAME'),
entities,
synchronize: true,
dropSchema: true,
logging: true,
};
}
}
Conclusão
Para concluir, o objetivo deste artigo é apresentar uma abordagem para lidar com variáveis de ambiente, tanto no Nest quanto no Node.
Espero que alguma informaçõe fornecida seja útil e que agora você possa utilizar ou aprimorar esse modo em suas aplicações. Se houver dúvidas ou sugestões, sinta-se à vontade para discutir!
Top comments (0)