Entre para a LISTA VIP da Black Friday

00

DIAS

00

HORAS

00

MIN

00

SEG

Clique para saber mais
Alura > Cursos de Programação > Cursos de Node.JS > Conteúdos de Node.JS > Primeiras aulas do curso Nest.js: lidando com migrações, relacionamentos ORM e erros em uma API

Nest.js: lidando com migrações, relacionamentos ORM e erros em uma API

Implementando migrações - Apresentação

Antônio: Boas-vindas a mais um curso de NestJS. Meu nome é Antônio Evaldo e vou ser uma das pessoas instrutoras que vai te acompanhar nesse curso.

Antônio Evaldo é um homem de pele clara, olhos escuros e cabelos escuros encaracolados. Usa óculos de grau com armação arrendondada, bigode e cavanhaque. Está com os cabelos amarrados atrás da cabeça e um moletom azul-escuro.

Camila: Olá! Meu nome é Camila Pessôa.

Camila Pessôa é uma mulher de pele clara, olhos escuros e cabelos escuros e ondulados na altura do ombro. Tem traços arredondados. Usa óculos de grau com armação arrendondada. Está com fone de ouvido sem frio e camisa preta.

Se você já desenvolve API com NestJS e TypeORM, esse curso foi pensado para você que deseja se aprofundar nessas tecnologias!

O que vamos encontrar nesse curso, Evaldo?

O que vamos aprender?

Antônio: Vamos começar a partir do projeto do curso anterior e aplicar migrações, assim, você vai entender porque e como utilizá-las.

Além disso, vamos avançar nos conceitos de relacionamentos do TypeORM. Utilizaremos relacionamentos mais avançados na prática, como, por exemplo, ManyToMany.

Mais para frente, vamos realizar um tratamento de erros mais robusto da API, utilizando boas práticas do Nest. Por fim, vamos refinar essa API com boas práticas do mercado para finalizar o projeto com êxito.

Mas, como vamos implementar todos esses conhecimentos no projeto?

Sobre o projeto

Camila: Vamos trabalhar com um projeto que você já desenvolveu nos cursos anteriores da formação NestJS que era um e-commerce. No entanto, agora o nosso e-commerce já está mais robusto e seu nome é Compree.

Além de já ter uma marca, essa aplicação também já possui várias pessoas usuárias e produtos. Por isso, nosso desafio é permitir que essas pessoas consigam realizar compras no e-commerce.

O Antônio Evaldo e eu entramos nesse time agora. Por isso, com todo esse processo, precisamos de um Trello para organizar as tarefas, como acontece em um ambiente real de desenvolvimento.

Pré-requisitos

Antônio: Para aproveitar melhor os conhecimentos desse curso, é importante que você já tenha feito os dois cursos anteriores da formação: Nest.js: criando uma API Restful e Nest.js: persistindo dados com TypeORM e PostgreSQL.

Esses cursos abordam os conhecimentos de Rest API com NestJS e TypeORM. Além disso, você precisa ter familiaridade com o TypeScript que é a linguagem que essas duas ferramentas utilizam.

Por fim, você também precisa estar familiarizado com o padrão de arquitetura MVC que utilizamos nos cursos anteriores.

Aproveite o fórum para tirar dúvidas e o Discord da Alura para interagir com outros alunos e alunas. Vamos estudar?

Implementando migrações - Retomando o projeto

Antônio: Antes de começar a desenvolver as novas funcionalidades, vamos retomar o projeto da API da loja Compree.

Vamos simular a entrada de duas novas pessoas no time: Camila e eu. Dessa forma, aprendemos o que fazer quando alguém embarca em um projeto que já tem até banco de dados.

Ainda que você não tenha o projeto na sua máquina, você pode seguir os passos que vamos te mostrar agora.

Instalar dependências do projeto

O primeiro passo é baixar o projeto do GitHub e abri-lo no VSCode.

Em seguida, abrimos o terminal integrado com "Ctrl + `" e digitar o comando para instalar as dependências do projeto:

npm install

Inclusive, se você quiser saber mais detalhes, as dependências estão especificadas no arquivo package.json.

Configurar variáveis de ambiente

O próximo passo é configurar as variáveis de ambiente. Como é uma etapa importante em todo projeto, a empresa pode disponibilizar essas informações para você.

Na raiz do projeto, vamos criar um arquivo .env e colar as seguintes informações:

.env:

DB_HOST=127.0.0.1
DB_PORT=5432
DB_USERNAME=root
DB_PASSWORD=root
DB_NAME=db_lojá
DB_ADMIN_EMAIL=admin@root.com

Essas informações vão ser utilizadas, principalmente, pelo arquivo docker-compose.yaml. Nele, temos as variáveis utilizadas como DB_USERNAME e DB_PASSWORD que são dados sensíveis e, por isso, ficam nesse arquivo oculto .env.

docker-compose.yaml:

version: '3.5'
services:
  postgres:
    image: postgres:latest
    environment:
      POSTGRES_USER: ${DB_USERNAME}
      POSTGRES_PASSWORD: ${DB_PASSWORD}
      PGDATA: /data/postgres
    volumes:
      - postgres:/data/postgres
    ports:
      - "5432:5432"
    networks:
      - postgres
    restart: unless-stopped

  pgadmin:
    image: dpage/pgadmin4
    environment:
      PGADMIN_DEFAULT_EMAIL: ${DB_ADMIN_EMAIL}
      PGADMIN_DEFAULT_PASSWORD: ${DB_PASSWORD}
    ports:
      - "8081:80"
    depends_on:
      - postgres
    networks:
      - postgres

# código omitido…

Em seguida, vamos executar o docker-compose.yaml, utilizando o Docker na sua máquina.

Toda a infraestrutura necessária está na atividade "Preparando o Ambiente", onde especificamos a versão do NodeJS e Docker.

Como estamos utilizando Windows, vamos abrir o Docker Desktop. Com isso, já conseguimos nos comunicar com o Docker através do terminal integrado do VSCode. Caso queira, podemos limpar a tela com "Ctrl + L".

docker-compose up -d

Após dar "Enter", vamos criar todas as dependências listadas pelo Docker que são: o banco de dados do postgres e o Postgres pgadmin.

Configurar banco de dados no pgAdmin

Em ports na linha 24 do docker-compose.yaml, conseguimos descobrir que o Postgres Admin pode ser aberto na porta 8081 do localhost.

No navegador, vamos digitar na barra de endereço:

localhost:8081

Na primeira vez que abrimos o pgAdmin pode demorar um pouco para carregar a página. Nela, vamos colocar o e-mail e a senha para login, os quais foram configurados no arquivo .env.

Com isso, logamos no pgAdmin e podemos manipular o banco de dados. Contudo, ainda não tem nenhum servidor listado no painel de "Object Explorer" à esquerda.

O servidor que vai ser utilizado pela nossa aplicação Nest tem que ser configurado corretamente. Não apenas o servidor, como o banco de dados e as tabelas.

Será que vamos precisar criar tudo do zero?

Começamos a entender um dos problemas enfrentados por pessoas que estão entrando em um novo time e precisam estar em congruência com as pessoas que já estão no projeto e tem um banco de dados, tabelas e registros configurados.

Já vamos te mostrar uma solução para essa problemática. Mas, por enquanto, temos que fazer alguns passos obrigatórios quando configuramos o banco de dados pela primeira vez na nossa máquina.

Você já fez esses passos no curso anterior, mas quem embarca em um novo projeto também precisa seguir esses passos.

Primeiro, na parte de "Quick Links" do dashboard, clicamos em "Add New Server". Na aba "General" da janela aberta, vamos nomear o servidor como db_server. Pode ser o nome que você desejar.

Na aba "Connection", é obrigatório colocar o campo "Host name/address" como postgres. Enquanto no campo "username", vamos substituir o nome de usuário admin por root. No campo "password", definimos a senha como root.

Vamos manter o campo "port" com a posta 5432, pois também está configurada no nosso projeto.

Por fim, vamos clicar no botão "Save" no canto inferior direito.

Com isso, o servidor db_server foi criado. No painel de "Object Explorer", vamos expandir "db_server > Databases" ao clicar na seta à esquerda de seus nomes.

Por padrão, temos dois banco de dados criados: postgres e root. Agora, precisamos criar um banco de dados exatamente com o nome especificado no arquivo .env: o db_loja.

Para isso, clicamos com o botão direito em Databases, selecionamos a opção "Create > Database". Na janela que se abre, vamos definir o nome do banco de dados como db_loja no campo "Database". Novamente clicamos no botão "Save".

Agora, temos listado o banco de dados db_loja no "Object Explorer". Expandimos "db_loja > Schemas > public > Tables" para perceber que não tem nenhuma tabela criada.

Próximos passos

Novamente nos deparamos com um contratempo por não ter nenhuma tabela ou registro criado. Como resolver esse problema para embarcar no projeto e ficar alinhado com o time?

Existe uma forma automática e que já está configurada no projeto para resolver essa questão. A seguir, vamos mostrar as vantagens e desvantagens dessa forma, bem como qual será a solução que se aplica melhor para o nosso caso. Te esperamos no próximo vídeo!

Implementando migrações - Usando a CLI do TypeORM

Antônio: Vamos continuar a retomada do nosso projeto. Já configuramos as variáveis de ambiente, Docker e Postgres Admin. Mas, nos deparamos com uma situação problemática onde as tabelas que ainda não foram criadas.

Como fazemos para nos alinhar com o restante do time que já está essas configurações de banco de dados implementadas em suas máquinas?

Para mostrar uma solução mais robusta para esse problema, vamos ir até o cartão "Criar Migrations" na coluna "Backlog" onde temos os cartões prontos para iniciar no Trello organizado pelo time.

Nesse cartão, temos a seguinte informação sobre o projeto:

O Banco de Dados da Compree ainda não possui migrations e está apenas utilizando a opção synchronize: true no Data Source do TypeORM. Essa opção é viável para uso apenas em ambiente de desenvolvimento, pois pode gerar perda de dados em produção.

Vamos implementar as migrations na nossa API para que haja um versionamento de BD mais robusto.

Opção sincronize: true

Vamos mostrar a opção synchronize: true no código para que você possa entender melhor. No VSCode, vamos até "src > config > postgres.config.service.ts". Esse arquivo que criamos no curso anterior faz a integração do NestJS com TypeORM.

postgres.config.service.ts:

@Injectable()
export class PostgresConfigService implements TypeOrmOptionsFactory {
  constructor(private configService: ConfigService) {}

  createTypeOrmOptions(): TypeOrmModuleOptions {
    return {
      type: 'postgres',
      host: this.configService.get<string>('DB_HOST'),
      port: this.configService.get<number>('DB_PORT'),
      username: this.configService.get<string>('DB_USERNAME'),
      password: this.configService.get<string>('DB_PASSWORD'),
      database: this.configService.get<string>('DB_NAME'),
      entities: [__dirname + '/../**/*.entity.{js,ts}'],
      syncronize: true,
    };
  }
}

Nele, temos uma classe PostgresConfigService com o decorator @Injectable que implementa a TypeOrmOptionsFactory. Também tem um constructor que pega um configService, esse código já se relaciona com as variáveis de ambiente e como podem ser utilizadas no NestJS.

Por fim, temos o método createTypeOrmOptions() que retorna um objeto que se relaciona ao TypeORM. Esse objeto é o que chamamos de Data Source (fonte de dados) que contém as informações mais importantes para que o TypeORM consiga trabalhar.

Por isso, é nessa fonte de dados que informamos o banco de dados que estamos utilizando, o postgres. Também informamos o host, a porta, o nome de usuário, senha, o banco de dados e até as entidades que serão utilizadas pelo TypeORM.

Note que a última opção se chama syncronize e tem valor true. Essa opção faz o trabalho de criar automaticamente as tabelas e, por vezes, alguns registros a depender do volume guardado no Docker quando rodamos a API.

Se rodamos a API, as tabelas vão ser criadas automaticamente de acordo o esquema de entidades do TypeORM.

Porém, existe um grande problema nessa opção: ela não pode ser utilizada em produção porque pode acarretar em perda de dados. Por isso, é útil apenas para ambientes de desenvolvimento.

Por isso, não vamos utilizar essa opção para produção. Afinal, queremos te mostar quais são as práticas utilizadas no mercado.

Diante disso, vamos apagar a opção sincronize: true de método createTypeOrmOptions() e salvar o arquivo postgres.config.service.ts.

Agora, vamos substituir essa opção pela solução mais robusta utilizada no mercado: as migrations (migrações).

CLI do TypeORM

Para conseguir as migrations que são um recurso específico do TypeORM, precisamos instalar a CLI do TypeORM que é a linha de comando do TypeORM.

Para isso, vamos abrir o terminal integrado do VSCode e digitar npm i -g para instalar globalmente a versão typeORM@0.3.16.

Contudo, ela não vai funcionar necessariamente em conjunto com o projeto Nest, mas vai ser uma parte independente. Inclusive, a API nem precisa estar rodando para utilizar os comandos da CLI.

npm i -g typeORM@0.3.16

Vamos dar um "Enter" e aguardar o comando ser executado. Após instalar a CLI do TypeORM, vamos conseguir utilizar qualquer comando precisamos criar um Data Source específico.

Já temos o Data Source no Nest, mas lembra que a CLI não faz parte do projeto? Na verdade, vamos fazer um arquivo que vai configurar um objeto muito parecido com o utilizado no postgres.config.service.ts.

Dentro da pasta "src", vamos criar uma nova pasta chamada "db" que se refere a database. Nessa pasta, criamos um arquivo chamado data-source-cli.ts.

Nesse arquivo, vamos colocar uma configuração específica para o TypeORM. Por isso, digitamos const chamada dataSourceOptions que vai ser do tipo DataSourceOptions. Com isso, fizemos o auto import do tipo DataSourceOptions desde typeorm.

Vamos atribuir a essa constante o objeto que vai ser a fonte de dados. Vamos usar como base o objeto que está sendo retornado pelo método createTypeOrmOptions() em postgres.config.service.ts. Copiamos e colamos esse trecho na atribuição do dataSourceOptions.

Contudo, não vamos utilizar o this.configService porque é específico no NestJS para pegar informações do .env, como as variáveis DB_HOST e DB_PORT.

Na propriedade host, vamos substituir this.configService.get<String>() por process.env. para pegar diretamente da variável de ambiente. Vamos manter o DB_HOST, porém sem aspas, pois vamos remover a string e colocar como propriedade do process.env.

Vamos fazer o mesmo nas outras linhas. Na porta, vamos colocar process.env.DB_PORT. Porém, para não acusar erro de atribuição do tipo string para tipo number, vamos envolver process.env com Number(). Isso porque as variáveis de ambiente vêm automaticamente como string.

Para fazer essa substituição mais rapidamente, você pode posicionar o cursor no início do this.configService na linha de username. Em seguida, aperte "Ctrl + Alt" e a seta para baixo duas vezes para também selecionar as linhas de password e database.

Logo, aperte "Ctrl + Shift" e seta para direita até selecionar o início da string em this.configService.get<String>(). Agora, vamos apagar esse trecho selecionado e escrever process.env. Por fim, basta apagar as aspas e fecha parênteses no final de cada linha.

data-source-cli.ts:

import { DataSourceOptions } from 'typeorm';

const dataSourceOptions: DataSourceOptions = {
  type: 'postgres',
  host: process.env.DB_HOST,
  port: Number(process.env.DB_PORT),
  username: process.env.DB_USERNAME,
  password: process.env.DB_PASSWORD,
  database: process.env.DB_NAME,
  entities: [__dirname + '/../**/*.entity.{js,ts}'],
};

Para conseguir utilizar as variáveis de ambiente desse modo, também precisamos instalar o pacote dotenv que ainda não está nesse projeto.

Para isso, abrimos o terminal integrado e escrever o comando para instalar a versão dotenv@16.0.3

npm i dotenv@16.0.3

Após instalar, temos que importar o dotenv nesse arquivo data-source-cli.ts. Na primeira linha, importamos dotenv/config entre aspas.

Para finalizar as configurações do arquivo, após dataSourceOptions, vamos escreverconst dataSource que recebe new DataSource(), passando o dataSourceOptions como parâmetro. Com isso, importamos automaticamente o DataSource também desde typeorm.

Por fim, vamos exportar o dataSouce como export default. Essa é a linha que vai servir para a CLI do TypeORM, pois vai conter todas as informações do Data Source.

Essa é a maneira de criar um Data Source, passando as opções como parte do construtor. Só falta mais um detalhe que falta colocar nas opções da fonte de dados.

Após entities, vamos dar "Enter" para escrever migrations na próxima linha. É onde vamos indicar quais são as pastas do projeto que vão conter as migrations, seguindo um padrão similar ao usado da propriedade entities.

Entre colchetes, escrevemos __dirname + e, entre aspas, /migrations/*.{js,ts}. Isso significa que vamos configurar as migrações do projeto dentro da pasta "db" onde está esse arquivo data-source-cli.ts, mas dentro de uma pasta "migrations".

Dentro da pasta "migrations" que ainda vamos criar, vamos capturar qualquer arquivo que termine em .js ou .ts.

import 'dotenv/config';
import { DataSource, DataSourceOptions } from 'typeorm';

const dataSourceOptions: DataSourceOptions = {
  type: 'postgres',
  host: process.env.DB_HOST,
  port: Number(process.env.DB_PORT),
  username: process.env.DB_USERNAME,
  password: process.env.DB_PASSWORD,
  database: process.env.DB_NAME,
  entities: [__dirname + '/../**/*.entity.{js,ts}'],
  migrations: [__dirname + '/migrations/*.{js,ts}'],
};

const dataSource = new DataSource(dataSourceOptions);

export default dataSource;

Após salvar esse arquivo, vamos rodar um comando específico da CLI no terminal integrado para testar se o Data Source funciona.

Normalmente, precisaríamos colocar somente typeorm para utilizar os comandos da CLI. Porém, precisamos adicionar também -ts-node-esm porque trabalhamos com tipos TypeScript, como o data-source-cli.ts, e o ECMAScript module. Se não fosse esm, escreveríamos -ts-node-commonjs.

Em seguida, vamos adicionar a opção --dataSource com a letra "S" maiúscula (ou somente o atalho -d) para indicar o caminho do arquivo que acabamos de criar, começando pela raiz do projeto: src/db/data-source-cli.ts.

Agora, podemos dar o comando que queremos executar pela CLI do TypeORM. Nesse caso, o migration:show.

typeorm-ts-node-esm -d src/db/data-source-cli.ts migration:show

Após apertar "Enter", não acontece nada. O que é um bom sinal, já que não houve nenhum erro. Isso significa que conseguiu capturar corretamente as informações do arquivo.

Se tivesse dado algum problema, apareceria o seguinte erro no terminal:

Error during migration show

Error: Given data source file must contain export of a DataSource

O VSCode avisa que houve um erro em mostrar as migrações, pois o arquivo tinha que conter uma instância de Data Source.

Mas, se deu certo, por que não houve nenhum retorno?

Isso porque o comando migration:show mostra todas as migrações existentes no nosso código, porém, ainda não criamos nenhuma. Contudo, nosso arquivo já está preparado para criar essas migrações - o que vamos fazer no próximo vídeo.

Sobre o curso Nest.js: lidando com migrações, relacionamentos ORM e erros em uma API

O curso Nest.js: lidando com migrações, relacionamentos ORM e erros em uma API possui 181 minutos de vídeos, em um total de 53 atividades. Gostou? Conheça nossos outros cursos de Node.JS em Programação, ou leia nossos artigos de Programação.

Matricule-se e comece a estudar com a gente hoje! Conheça outros tópicos abordados durante o curso:

Aprenda Node.JS acessando integralmente esse e outros cursos, comece hoje!

Conheça os Planos para Empresas