Garanta a integridade da sua aplicação: aprenda validar variáveis de ambiente em APIs Node.JS
Tags: typescript, api, node, fastify
Published: 2023-05-15 00:14:25

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.

---

#### :zap: 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`.

---

#### :zap: 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`.

---

#### :zap: Iniciando o projeto

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

Abra o terminal e execute o comando:

```bash
mkdir env-validate && \
cd env-validate
```

Agora vamos iniciar um projeto node com o comando:

```bash
yarn init -y
```

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:

```bash
touch .gitignore && \
echo '.env' >> .gitignore && \
echo 'node_modules' >> .gitignore
```

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

```bash
git add . && \
git commit -m "initial commit" 
```

---

#### :zap: 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`:

```bash
yarn add fastify
```

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_).

```bash
yarn add -D typescript @types/node tsx tsup
```

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

```bash
yarn tsc --init 
```

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:

```bash
mkdir -p src/infra && \
touch src/infra/app.ts
```

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

```ts
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 }

```

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

```bash
touch src/infra/server.ts
```

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

```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()

```

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`:

```json
"scripts": {
  "start:dev": "tsx watch src/infra/server.ts"
}
```

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

```bash
yarn start:dev
```

O que esperamos ver no terminal é a seguinte mensagem:

![terminal message with fastify server started](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qfmb0l9qxeu4bamnrcwj.png)


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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/p921nox5mdc52ndzkceq.png)

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

Novo commit para finalizar esse bloco

```bash
git add . && \
git commit -m "fastify server started"
```

---

#### :zap: 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.

```bash
touch .env
```

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

```env
# API_PORT=3333
```

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:

```bash
yarn add zod dotenv 
```

Para criar o arquivo `env.ts`, execute:

```bash
touch src/infra/env.ts
```

Cole o conteúdo abaixo no arquivo gerado:

```ts
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

```

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

```ts
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()

```

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:

```bash
yarn start:dev
```

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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/m3u2auhxwuv01rgirhce.png)

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:

```env
API_PORT=3333
```

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

```bash
yarn start:dev
```

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

![server started with env environment](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/rinq8u3do3buqg8345ch.png)

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`

```bash
cp .env .env.example
```

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

```bash
git add . && \                 
git commit -m "environment variables validated"
```

---

#### :zap: 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:

```json
"scripts": {
  ...
  "prebuild": "tsc --noEmit",
  "build": "tsup src --out-dir build",
  "start": "node build/server.js"
}
```

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:

```bash
yarn build
```

Você deverá ver algo como:

![transpile image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/66hbdnt8o60cypb3al3g.png)

Agora basta executar:

```bash
yarn start
```

E verá no seu terminal...

![node execute transpiled code](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/drsk1ghxp3i6v9q62fr1.png)

Novo commit e fim do tutorial

```bash
git add . && \                   
git commit -m "app build added"
```

---

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](https://github.com/lipex360x/env-validate)

Até o próximo ;)