DEV Community

Cover image for Além do básico: Uso de Variáveis de Ambiente em Aplicações Node e Nest
Lucas Campos
Lucas Campos

Posted on • Updated on

Além do básico: Uso de Variáveis de Ambiente em Aplicações Node e Nest

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
Enter fullscreen mode Exit fullscreen mode

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(),
})
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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')
})
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode
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>;

Enter fullscreen mode Exit fullscreen mode
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 });
  }
}
Enter fullscreen mode Exit fullscreen mode

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 {}
Enter fullscreen mode Exit fullscreen mode

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 {}
Enter fullscreen mode Exit fullscreen mode
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();
Enter fullscreen mode Exit fullscreen mode

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 {}
Enter fullscreen mode Exit fullscreen mode
//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,
    };
  }
}
Enter fullscreen mode Exit fullscreen mode
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)