DEV Community

Cover image for Garanta a integridade da sua aplicação: aprenda validar variáveis de ambiente em APIs Node.JS
Felipe Borges
Felipe Borges

Posted on

Garanta a integridade da sua aplicação: aprenda validar variáveis de ambiente em APIs Node.JS

Finalmente consegui finalizar meu primeiro post para o dev.to

Nas primeiras tentativas de entregar algo eu falhei miseravelmente pois, simplesmente, a postagem ficava gigantesca demais.

Dessa vez consegui deixar o escopo fechadinho e de uma maneira que, acredito eu, seja bem fácil de reproduzir.

Então, chega de falatório (ou escritório...) e vamos começar.


⚡ Pré requisitos

Não vamos criar nada revolucionário ou complexo demais por aqui, já que o intuito é demonstrar como validar as variáveis necessárias para a aplicação subir.

Para este tutorial, vamos precisar do Node.js instalado na máquina. Também vou usar o git para versionar os códigos. Vou partir do princípio de que estes pré-requisitos já estejam atendidos por você, caro leitor.

Estou utilizando o macOS como sistema operacional e utilizarei o terminal para executar todos os comandos. Se estiver usando Windows, sugiro fortemente que utilize o WSL.

Também estou usando o yarn como gerenciador de pacote ao invés do npm.


⚡ Tudo que vamos usar nesse projeto

  • typescript
  • fastify
  • dotenv
  • zod
  • tsx
  • tsup

Um detalhe: estou usando o fastify pois vou postar mais alguns materiais por aqui usando esse micro-framework. Mas o que vou aplicar aqui é totalmente compatível com o express ou com frameworks mais parrudos, como o NestJS.


⚡ Iniciando o projeto

Vamos iniciar criando um novo diretório env-validate.

Abra o terminal e execute o comando:

mkdir env-validate && \
cd env-validate
Enter fullscreen mode Exit fullscreen mode

Agora vamos iniciar um projeto node com o comando:

yarn init -y
Enter fullscreen mode Exit fullscreen mode

Já preparando para que os commits no git não suba o diretório node_modules e o .env, vamos criar um arquivo .gitignore na raiz do projeto:

touch .gitignore && \
echo '.env' >> .gitignore && \
echo 'node_modules' >> .gitignore
Enter fullscreen mode Exit fullscreen mode

Vamos fazer um primeiro commit para facilitar a navegação no histórico de versões no futuro.

git add . && \
git commit -m "initial commit" 
Enter fullscreen mode Exit fullscreen mode

⚡ Iniciando o Fastify e o Typescript

Vamos iniciar um servidor usando o fastify com uma rota GET /hello para termos um mínimo de código por aqui.

Para isso vamos instalar o fastify:

yarn add fastify
Enter fullscreen mode Exit fullscreen mode

Como vamos usar Typescript, vamos precisar do próprio typescript instalado.

Também vamos precisar instalar a tipagem do node (@types/node), um interpretador para os arquivos .ts (tsx) e um compilador de typescript para javascript (tsup).

yarn add -D typescript @types/node tsx tsup
Enter fullscreen mode Exit fullscreen mode

Com o typescript instalado, vamos iniciar o tsconfig.json com o comando:

yarn tsc --init 
Enter fullscreen mode Exit fullscreen mode

Para este tutorial não vamos precisar modificar nada neste arquivo


Agora vamos gerar o arquivo app.ts, onde as configurações do fastify estarão:

mkdir -p src/infra && \
touch src/infra/app.ts
Enter fullscreen mode Exit fullscreen mode

Cole o conteúdo abaixo no arquivo app.ts gerado:

import fastify, { FastifyReply, FastifyRequest } from "fastify";

const app = fastify()

app.get('/hello', (request: FastifyRequest, reply: FastifyReply) => {
  return reply.status(200).send({ message: 'hello world' })
})

export { app }

Enter fullscreen mode Exit fullscreen mode

O código deste arquivo criamos uma constante app que é uma instância da função fastify.

Com essa variável app, criamos uma rota GET /hello que retornará uma mensagem 'hello world' com status 200.

Agora vamos gerar o arquivo server.ts, que é o nosso entrypoint da aplicação

touch src/infra/server.ts
Enter fullscreen mode Exit fullscreen mode

Cole o conteúdo abaixo no arquivo server.ts:

import { app } from "./app";

async function bootstrap() {
  await app.listen({ host: '0.0.0.0', port: 3333 })

  console.log('🚀 server started at port 3333')
}

bootstrap()

Enter fullscreen mode Exit fullscreen mode

Explicando...

Aqui temos uma função assíncrona bootstrap que cria um servidor fastify que veio da variável 'app' importada na primeira linha.

Este servidor está subindo no endereço http://localhost:3333

Mas para este servidor funcionar, precisamos inserir um script no arquivo package.json.

Abra este arquivo e insira uma nova chave scripts:

"scripts": {
  "start:dev": "tsx watch src/infra/server.ts"
}
Enter fullscreen mode Exit fullscreen mode

Para subir o servidor, basta no terminal executar o comando:

yarn start:dev
Enter fullscreen mode Exit fullscreen mode

O que esperamos ver no terminal é a seguinte mensagem:

terminal message with fastify server started

Uma vez visto a bela mensagem de start do servidor, basta acessar via browser o endereço: http://localhost:3333/hello

Se tudo estiver certo, esta é a mensagem que deverá ver no seu browser

browser hello world message

Para ver a mensagem formatada assim, estou usando a extensão JSON Viewer com tema dark no Chrome

Novo commit para finalizar esse bloco

git add . && \
git commit -m "fastify server started"
Enter fullscreen mode Exit fullscreen mode

⚡ Validando as variáveis de ambiente

Se ainda estiver com o servidor executando no terminal, encerre o processo com CTRL + C ou abra uma nova aba/janela.

Agora vamos gerar um arquivo .env onde listaremos quais variáveis de ambiente são necessárias para a aplicação iniciar.

touch .env
Enter fullscreen mode Exit fullscreen mode

Cole o conteúdo abaixo no arquivo .env gerado:

# API_PORT=3333
Enter fullscreen mode Exit fullscreen mode

Para validar as variáveis de ambiente, vamos utilizar a biblioteca de validação zod e também vamos precisar instalar a biblioteca dotenv. Esta última nos possibilita ler as variáveis de ambiente ao importá-la no início do arquivo env.ts.

Execute no terminal:

yarn add zod dotenv 
Enter fullscreen mode Exit fullscreen mode

Para criar o arquivo env.ts, execute:

touch src/infra/env.ts
Enter fullscreen mode Exit fullscreen mode

Cole o conteúdo abaixo no arquivo gerado:

import 'dotenv/config'
import { z } from 'zod'

const envSchema = z.object({
  API_PORT: z.coerce.number()
})

const getEnv = envSchema.safeParse(process.env)

if (!getEnv.success) {
  const errorMessage = 'load environment failed'
  console.error(errorMessage, getEnv.error.format())
  throw new Error(errorMessage)
}

export const env = getEnv.data

Enter fullscreen mode Exit fullscreen mode

Vamos repassar o que foi colado:

Primeiro, importamos o dotenv/config para realizar a leitura do arquivo .env. Em seguida importamos de dentro do zod o z.

O zod trabalha com uma série de parâmetros e aqui estamos usando o object para gerar um schema com as variáveis necessárias para a aplicação iniciar.

Neste caso declaramos que a propriedade API_PORT é do tipo z.coerce.number().

Tudo que é importado das variáveis de ambiente do arquivo .env é interpretado como texto. O termo coerce utilizando antes do .number() é uma forma de dizer para o zod realizar uma conversão do que está chegando (string) para o formato number.

Em seguida criamos uma variável getEnv onde passamos o envSchema.safeParse(process.env)

O comando safeParse valida se o que está sendo repassado pelo process.env atende ao que foi especificado no schema.

Como resultado, a variável getEnv terá uma propriedade .success booleana.

Logo abaixo estamos checando se este .success falhou e, caso tenha falhado, interrompemos o script com um erro load environment failed.

Se a validação passar, ou seja, se o .success for verdadeiro, exportamos a variável env com os dados obtidos do getEnv.data


Chegou a hora de testar:

Volte ao arquivo server.ts e mude a propriedade port para que este leia o env.API_PORT exportado nos passos anteriores.

o arquivo server.ts ficará assim

import { app } from "./app";
import { env } from "./env";

async function bootstrap() {
  await app.listen({ host: '0.0.0.0', port: env.API_PORT })

  console.log(`🚀 server started at port ${env.API_PORT}`)
}

bootstrap()

Enter fullscreen mode Exit fullscreen mode

Aproveitei e também modifiquei a mensagem do console para que a porta passada nas variáveis seja exibida no terminal.

Agora vamos rodar a aplicação para ver o que acontece:

yarn start:dev
Enter fullscreen mode Exit fullscreen mode

E... eitaaaa, erro!

Calma, era o esperado :)

Como pode observar no terminal, temos a seguinte mensagem de erro no topo da execução:

terminal env message error

Essa mensagem está aqui porque criamos o nosso arquivo .env com a variável API_PORT comentada de propósito.

Para resolver isso, pare a execução do servidor com CTRL + C, volte no arquivo .env e remova o # da primeira linha.

Seu arquivo .env. deve ficar assim:

API_PORT=3333
Enter fullscreen mode Exit fullscreen mode

Execute a aplicação novamente e tudo deve dar certo agora

yarn start:dev
Enter fullscreen mode Exit fullscreen mode

O esperado é você ver o seu terminal com a mensagem:

server started with env environment

Tenho certeza (será?) que tudo deu certo.

Então vamos criar um novo arquivo .env.example para que, ao clonar este repositório, seja possível iniciar o projeto apenas renomeando para .env

cp .env .env.example
Enter fullscreen mode Exit fullscreen mode

E, por último nessa sessão, vamos commitar a coisa toda e partir para a última parte.

git add . && \                 
git commit -m "environment variables validated"
Enter fullscreen mode Exit fullscreen mode

⚡ Gerando build do projeto

Para finalizar este tutorial, vamos configurar o script que executará o build da aplicação e o start da versão final em javascript.

Para isso, vá até o arquivo package.json e adicione os scripts abaixo:

"scripts": {
  ...
  "prebuild": "tsc --noEmit",
  "build": "tsup src --out-dir build",
  "start": "node build/server.js"
}
Enter fullscreen mode Exit fullscreen mode

Explicando mais uma vez...

O script build utiliza a biblioteca tsup para transpilar os códigos typescript para javascript usando como diretório build como saída.

Porém, antes de executar o transpile, o prebuild é chamado automaticamente para que o typescript faça o transpile por conta própria apenas em memória. Caso algo falhe, o build é interrompido nessa fase.

Se nada falhar, o diretório build é populado com os arquivos javascript nos quais o Node.JS será capaz de executar nativamente.

O script start executa o servidor já com o arquivo compilado.

Este é o comando que será utilizado caso esta aplicação esteja publicada.

Para testar tudo, basta executar no terminal:

yarn build
Enter fullscreen mode Exit fullscreen mode

Você deverá ver algo como:

transpile image

Agora basta executar:

yarn start
Enter fullscreen mode Exit fullscreen mode

E verá no seu terminal...

node execute transpiled code

Novo commit e fim do tutorial

git add . && \                   
git commit -m "app build added"
Enter fullscreen mode Exit fullscreen mode

Ufaaa...

Assim encerro meu primeiro tutorial postado por aqui.

Toda e qualquer variável de ambiente necessária para a aplicação poderá ser listada no schema gerado no arquivo env.ts.

Dessa forma, caso alguma delas não seja declarada no ambiente onde a aplicação será publicada.

Por exemplo: variáveis do banco de dados, da AWS ou de qualquer outro provider, variáveis de tokens JWT e por aí vai.

Basta adicionar o que precisa no schema e está garantido que a aplicação se quer irá iniciar na falta de alguma delas.

Espero que tenham gostado.

Para acessar o repositório do projeto no github, clique aqui

Até o próximo ;)

Top comments (0)