Implementando TypeORM no NestJS

Implementando TypeORM no NestJS

O NestJS é um framework que facilita (e muito) o desenvolvimento com o Node.js, pois concede funcionalidades que, utilizando a arquitetura MSC, nos entregam trechos de código que requereriam um considerável tempo que pode assim ser reaproveitado em questões mais específicas do projeto.

Nas minhas últimas publicações (você pode consultar os artigos anteriores clicando aqui), pudemos aprender muito sobre o NestJS, no que tange a sua estrutura e como utilizá-lo para realizar consultas por meio de métodos HTTP (GET, POST, PATCH e DELETE). Hoje, daremos um passo a mais com o framework, utilizando o mesmo com uma ORM: o TypeORM.

Vamos lá?

O que é o NestJS?

Falando um pouco mais sobre o NestJS, trata-se de um framework de backend para Node.js que oferece uma coleção de classes, funções, tipos e padrões para facilitar o desenvolvimento de aplicações. Ele é escrito em TypeScript, uma linguagem derivada do JavaScript que adiciona tipagem estática, proporcionando um código mais seguro e menos suscetível a erros. Com o NestJS, é possível criar aplicações eficientes, escaláveis e confiáveis.

Sem mais delongas, vamos criar o nosso projeto NestJS. No diretório onde você deseja que fique o seu projeto, inicialize um terminal e execute os comandos a seguir:

nest new zoo-functions
cd zoo-functions
npm i class-validator class-transformer

Ao executar a linha de código acima, será questionado no próprio terminal qual o gerenciador de pacotes que estamos utilizando. Estamos utilizando o npm, mas você pode utilizar outros gerenciadores, caso se sinta mais à vontade. Se tudo der certo, ao concluir a instalação e abrir o diretório do projeto, você verá uma estrutura semelhante a da imagem abaixo:

Figura 1 - Estrutura básica do NestJS após a criação do projeto

Nosso objetivo é continuar utilizando o projeto que utilizamos nos artigos anteriores relacionados ao NestJS, onde criamos um sistema de gerenciamento de animais de um zoológico, onde será possível ler, inserir, atualizar e remover animais do cadastro. As consultas serão realizadas via TypeORM (veremos mais sobre ele mais adiante), mas por hora iremos criar os arquivos padrões do projeto, baseado nas estruturas module, service e controller. Para isto, execute o comando a seguir:

nest g module animals
nest g service animals
nest g controller animals

OBS - Se você possui dúvidas sobre a estrutura mencionada, recomendamos que leia o artigo “Utilizando o NestJS”, publicado antes deste. Lá, explicamos a função de cada estrutura.

Uma vez concluídos os comandos supracitados, o projeto deverá ter uma estrutura parecida como esta:

Figura 2 - Estrutura básica do diretório “animals”

Como já criamos uma estrutura de consultas via métodos GET, POST, PATCH e DELETE, iremos aproveitá-los dos artigos anteriores, com a exceção de que nossas consultas na camada service serão totalmente diferentes. Desta forma, por hora, manteremos as consultas da camada service retornando valores nulos, retornos estes que substituiremos até o fim deste artigo com consultas utilizando o TypeORM:

Figura 3 - Implementação da camada Controller

Figura 4 - Implementação da camada Service (sem consultas ainda, pois as realizaremos no decorrer deste artigo)

Figura 5 - DTO criada para validação dos dados da Requisição que estão chegando

O que é ORM?

O ORM (Object-Relational Mapping) é uma técnica que permite mapear entidades de um banco de dados para objetos direto no código. O objetivo é simplificar a interação com o banco de dados, fornecendo uma camada de código mais simples. Com o ORM, não é necessário escrever consultas SQL diretamente, pois a biblioteca cuida dessa parte por si só. Basta passar o objeto JavaScript para o ORM, que ele insere os dados no banco de dados.

Existem diversos tipos de ORM, das quais utilizaremos o TypeORM. Desta forma, instalaremos o mesmo por meio da linha de comando que deve ser executada em um terminal inicializado na raíz do seu projeto:

npm i typeorm --database mysql2 @nestjs/typeorm

OBS - Como é possível verificar na linha de execução descrita acima, utilizaremos o banco de dados Mysql para as consultas que serão realizadas via TypeORM, então é importante que o mesmo esteja instalado em sua máquina, ou no mínimo esteja executado virtualmente nela. Uma vez inicializado o Mysql, estamos prontos para começar as primeiras implementações com o TypeORM. Como estamos utilizando o banco de dados “zoo-functions” dos projetos anteriores, é importante que ele esteja criado no seu Mysql, mesmo que vazio. Execute o comando abaixo para isto, seja no Workbench (caso você utilize), ou no Mysql executado em seu terminal:

CREATE SCHEMA `zoo-functions`;

Criando uma Tabela

O primeiro passo para criar uma tabela utilizando o TypeORM é criar uma Migration para isso, que nada mais é do que uma forma de versionar o esquema do nosso banco de dados, onde cada Migration funciona como um histórico de alterações realizadas nele, como por exemplo criar uma tabela, alterar uma coluna, excluir um relacionamento, etc.

Cada migration possui dois conjuntos de código, conhecidos como Up e Down. A parte Up contém as instruções para executar as alterações no banco de dados, enquanto a parte Down contém as instruções para reverter essas alterações. Isso significa que as migrations têm a capacidade de avançar ou retroceder o banco de dados para qualquer estado anterior que ele tenha tido.

Como já mencionamos, nossa Migration criará uma tabela no banco de dados Zoo-functions. Para tal, basta executar o seguinte comando em um terminal aberto na pasta raiz do projeto:

npx typeorm migration:create src/migrations/createTables

Você perceberá que, na pasta “src” do nosso projeto, foi criado um arquivo com uma sequência de números, seguido de “-createTables.ts”. É ela que iremos manipular para a criação da tabela.

Figura 6 - Estrutura inicial da Migration quando ela é criada

Note que, como foi explicado sobre as Migrations, temos uma função up e outra down, para executar a alteração e revertê-la caso seja necessário, respectivamente. Na função up, criaremos a tabela e suas colunas por meio da função “createTable”, extraída de “queryRunner”. Esta recebe como parâmetros um “new Table”, que por sua vez recebe um objeto com o nome da tabela e as colunas existentes, junto com suas devidas especificações. Veja:

Figura 7 - Criando a tabela por meio da função up - Parte 1

Figura 8 - Criando a tabela por meio da função up - Parte 2

Perceba nas figuras 7 e 8 que as colunas da tabela são criadas por meio de uma chave “columns” que recebe como valor uma lista, onde cada item será uma coluna da tabela. Cada item por sua vez é um objeto, onde podemos definir diversas especificações de cada coluna, sendo obrigatório ao menos o seu nome e seu tipo. Veja a função de cada uma das outras chaves:

  • isPrimary - define quais colunas serão a chave primária da tabela;
  • generationStrategy - define como o valor de um campo será gerado (neste caso, vide figura 8, utilizamos para o id, com o intuito de realizar um incremento sempre que um novo elemento for criado na tabela);
  • isNullable - define se o campo pode ou não receber valores nulos;
  • length - define o limite do tamanho do campo do tipo texto.

Por fim, na função down, utilizamos a função dropTable, para o caso de precisarmos reverter o que foi realizado:

Figura 9 - implementando a reversão da criação da tabela por meio da função down

Entidade

Criamos a entidade para definir a estrutura da tabela para o nosso código. Diferente da Migration que apenas cria as colunas da nossa tabela, a nossa aplicação utilizará a entidade criada para validar se cada consulta irá ou não estar dentro dos padrões  de estrutura esperados da tabela. Para isso, criamos um arquivo com extensão “.ts”, antecedido por “.entity” (por convenção). Depois, criamos uma classe precedida da decorator @Entity, que receberá como parâmetro o nome da tabela.

Depois, criamos cada uma das colunas sob as decorators @Column(), caso seja uma coluna sem especificações, ou @PrimaryGeneratedColumn(“uuid”) para o caso de uma coluna que será uma chave primária a ser preenchida por um conjunto de caracteres aleatórios:

Figura 10 - Criação da entidade Animals

Uma vez concluída a implementação da entidade, precisamos adicioná-la no módulo do qual ela pertencerá. Em imports, adicionamos a seguinte expressão (importada de “@nestjs/typeorm”:

Figura 11 - Importando a entidade no Módulo AnimalsModule

Conectando com o Banco de Dados

Você percebeu que ainda não fizemos a conexão entre o TypeORM e o nosso banco de dados? Isso aconteceu porque preferi que criássemos a Migration primeiro, para depois realizar esta ação, visto que a importação da mesma seria necessária.

Para realizar esta conexão, utilizamos a função “forRoot”, localizada em TypeOrmModule, importado de “@nestjs/typeorm”. Esta função recebe como parâmetro as informações principais tanto da conexão do banco de dados quanto informações sobre as entidades e migrations que utilizaremos:

Figura 12 - Definindo a conexão do TypeORM com o banco de dados

Depois, criaremos um arquivo chamado “DataSourceTable.ts”, que utilizaremos para executar a Migration que criamos. Primeiro utilizamos a importação de DataSource, vinda do próprio typeorm. Ele receberá como parâmetro um objeto com todas as especificações do banco de dados que estamos utilizando, de forma muito análoga aos parâmetros que repassamos na função “forRoot”:

Figura 13 - Criando a conexão com o banco de dados

Nossa aplicação já está pronta para se conectar ao banco de dados e rodar a Migration. Primeiro, inicialize a aplicação com o comando a seguir:

npm run start:dev

Depois, execute a Migration com os comandos descritos abaixo:

npm run build
npx typeorm migration:run -d dist/migrations/DataSourceTable.js

Tudo pronto! Já temos uma tabela “animals” no banco de dados “zoo-functions” com todas as colunas que definimos na Migrations!

Implementando a camada Service

Para finalizar, basta utilizarmos as próprias funções que o TypeORM nos concede para realizar consultas. Ao invés de digitar toda a query Mysql, tudo ficará mais fácil: primeiro, criaremos um construtor que receberá uma decorator @InjectRepository(), que recebe como parâmetro a entidade “Animals” que criamos. Depois, criamos um atributo para esta entidade. é este atributo que utilizaremos para criar as consultas via TypeORM:

Figura 14 - Criando o construtor que irá receber a entidade Animals

Dentre muitas funções existentes, utilizaremos para as funções getAnimals e insertAnimal a “find” (retorna todos os elementos da tabela) e save (registra no banco de dados os dados recebidos:

Figura 15 - Implementação das funções getAnimals e insertAnimals

Por fim, para as funções updateAnimal e deleteAnimal, além das funções find (dessa vez sendo usado para procurar um dado em específico na tabela) e save já explicadas, utilizamos a função preload para preparar a informação, concatenando o id que vem como parâmetro e os demais campos das colunas, que vem em body. A função remove também é utilizada para excluir determinado elemento da tabela:

Figura 16 - Implementação das funções updateAnimal e deleteAnimal

Considerações finais

Neste artigo, aprendemos a utilizar o TypeORM para realizar consultas de leitura, atualização, inserção e remoção em um banco de dados Mysql utilizando o NestJS. No próximo artigo, entenderemos como realizar os relacionamentos de tabelas One-to-One, One-to-Many, Many-to-One e Many-to-Many.

Se você achou esse artigo interessante, gostaria de tirar quaisquer dúvidas ou somente está afim de trocar uma ideia sobre esses e mais assuntos, você também pode entrar em contato comigo, seja por meio do e-mail bruno.cabral.silva2018@gmail.com ou pelo meu perfil do LinkedIn!

Te espero ansiosamente!

💡
As opiniões e comentários expressos neste artigo são de propriedade exclusiva de seu autor e não representam necessariamente o ponto de vista da Revelo.

A Revelo Content Network acolhe todas as raças, etnias, nacionalidades, credos, gêneros, orientações, pontos de vista e ideologias, desde que promovam diversidade, equidade, inclusão e crescimento na carreira dos profissionais de tecnologia.