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 • Edited on

4 2 2 2 2

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!

Sentry blog image

How to reduce TTFB

In the past few years in the web dev world, we’ve seen a significant push towards rendering our websites on the server. Doing so is better for SEO and performs better on low-powered devices, but one thing we had to sacrifice is TTFB.

In this article, we’ll see how we can identify what makes our TTFB high so we can fix it.

Read more

Top comments (0)

A Workflow Copilot. Tailored to You.

Pieces.app image

Our desktop app, with its intelligent copilot, streamlines coding by generating snippets, extracting code from screenshots, and accelerating problem-solving.

Read the docs