TypeORM com NodeJS

TypeORM com NodeJS

Olá leitor, nesta lição você vai aprender sobre o uso de um ORM bastante conhecido no mundo do NodeJS (principalmente no NestJS), o TypeORM.

Com ele, você consegue executar uma variedade de operações relacionadas ao gerenciamento de banco de dados, em aplicações feitas com Javascript (NodeJS).

Em lições passadas, você aprendeu a configurar e usar o MySQL, além de fazer o uso de um outro ORM bem conhecido, chamado de Sequelize.

Hoje, nós iremos passar um pente fino nas funcionalides mais utilizadas do TypeORM, para que você consiga gerenciar seu banco com dados (MySQL) com ele 😉

Vamos nessa?

O que é o TypeORM?

O TypeORM, nada mais é do que um ORM (Object-Relational Mapper) que pode ser usado em conjunto com o NodeJS.

Seu objetivo, é facilitar a interação entre aplicações feitas com JavaScript (ou TypeScript) em bancos de dados relacionais.

Ele permite que desenvolvedores manipulem bancos de dados de forma programática, usando classes e objetos em vez de escrever SQL manualmente.

Portanto, podemos dizer que o TypeORM abstrai grande parte da complexidade de interação, que acontece de forma direta com bancos de dados.

Fornecendo uma interface orientada a objetos, o que facilita o desenvolvimento e a manutenção de sistemas complexos.

É importante ressaltar que o TypeORM suporta dois tipos de padrões, o Active Record e o Data Mapper.

Neste caso, você pode escolher o que melhor se adapta ao seu projeto. Nesta lição veremos o funcionamento do TypeORM trabalhando exclusivamente com Data Mapper, ok?

Com relação as principais funcionalidades do TypeORM, podemos destacar:

Mapeamento Objeto-Relacional: transforma tabelas de banco de dados em classes e colunas em propriedades de objetos. Isso permite trabalhar com dados do banco como se fossem objetos em JavaScript/TypeScript.

ORM Full-Featured: o TypeORM suporta relacionamentos complexos entre tabelas (como One-to-One, Many-to-One, etc.), herança de classes, e diferentes tipos de bancos de dados (PostgreSQL, MySQL, MariaDB, SQLite, SQL Server, Oracle, etc.).

Migrações: permite gerenciar mudanças no esquema do banco de dados de forma controlada, mantendo histórico de atualizações e permitindo desfazer modificações.

Uso em diferentes ambientes: pode ser utilizado em backends com NodeJS, em aplicativos móveis, e até em aplicações web rodando diretamente no navegador.

Transações e validações: suporta operações transacionais para garantir a integridade dos dados, além de validações de dados nas entidades.

Carregamento Eager e Lazy: permite escolher como os dados relacionados devem ser carregados (imediatamente ou sob demanda), dependendo da necessidade da aplicação.

Criando nosso projeto de testes

Antes de colocarmos a mão na massa, é deveras importante que você configure o seu projeto inicial.

Para isso, eu criei uma pasta chamada de TypeORM dentro da minha área de trabalho (desktop):

Com o seu terminal (Prompt de Comando) aberto na pasta raiz que acabamos de criar, precisamos inicializar o nosso projeto por meio do NPM, sendo assim, execute o seguinte comando abaixo:

npm init -y

A flag -y, como você já deve saber, cria um novo projeto de forma enxuta, respondendo SIM para tudo 😅

Por fim, não se esqueça de criar seu index.js, com uma mensagem bem legal de boas vindas:

console.log('Olá Mundo!');

Criando a sua tabela no banco de dados

Na lição passada, eu te ensinei a configurar algumas coisas, como:

Como está lição se trata de uma jornada, gostaria que você voltasse na lição anterior, e realizasse todos os passos necessários para configurar o MySQL junto com a base de dados (chamada de nodejs), e sem se esquecer de criar também uma tabela chamada de usuarios.

Feito isso, você já esta pronto para o TypeORM 😌

Instalando o TypeORM

Para instalar a biblioteca do TypeORM no seu projeto, é bem simples, com o seu terminal (Prompt de Comando) aberto na pasta raiz do seu projeto, execute o seguinte comando abaixo:

npm install typeorm reflect-metadata mysql2

typeorm: instala a biblioteca principal do TypeORM.

reflect-metadata: é necessário para usar decoradores com o TypeORM (ideal quando você está usando NodeJS com Typescript).

mysql2: é o driver MySQL para se comunicar com o banco de dados.

Observação: não é totalmente necessário instalar a biblioteca do reflect-metadata, pois aqui nós iremos usar a biblioteca usando puramente Javascript (e não Typescript).

Feito isso, vamos organizar algumas pastas em nosso projeto 😉

Organizando as pastas do projeto

No caso da biblioteca do TypeORM, vamos criar novas pastas para que possamos modularizar a nossa aplicação (dar uma organizada legal).

No meu caso, dentro da pasta raiz do projeto, eu precisei criar uma pasta chamada src, que vai conter duas outras pastas, chamadas de db e entity.

TypeORM/
├── node_modules/
├── src/
│   ├── db/
│   │   
│   ├── entity/
│   │
├── index.js
├── package.json

Veja como ficou a organização de pastas na ilustração abaixo:

A pasta src, é uma pasta global onde iremos armazenar a lógica do módulos que vamos utilizar com o TypeORM.

Já a pasta db, é a pasta onde iremos modularizar a conexão com o nosso banco de dados (MySQL). Lá existirá um arquivo responsável por realizar uma comunicação com a nossa base de dados (nodejs).

A pasta entity, é o local onde ficarão os arquivos que representarão as entidades do nosso projeto, ou seja, classes do Javascript que representam nossas tabelas do banco de dados.

Criando nosso módulo de conexão com o banco de dados (MySQL)

Dentro da pasta src > db, vamos criar um novo arquivo chamado de connection.js, com o seguinte código:

require('reflect-metadata');// Importa o reflect-metadata, necessário para o TypeORM (não é necessário caso não esteja usando Typescript))
const { createConnection } = require('typeorm');// Importa a função createConnection do TypeORM

// Função para conectar ao banco de dados
const connectDB = async () => {
  try {
    const connection = await createConnection({
      type: 'mysql',
      host: 'localhost',
      port: 3306,
      username: 'root', // Seu usuário MySQL
      password: 'password', // Sua senha MySQL
      database: 'testdb', // Seu banco de dados MySQL
      entities: [],// Representam as entidades do seu banco, voltaremos a isso mais tarde...
      synchronize: true, // Cria automaticamente as tabelas no banco de dados
    });

    console.log('Conectado ao banco de dados');
    return connection;
  } catch (error) {
    console.log('Erro ao conectar ao banco de dados: ', error);
    throw error;
  }
};

module.exports = connectDB;

No caso do comando acima, estamos importando a biblioteca do TypeORM junto com o reflect-metadata, que é crucial para ser usado em conjunto com o TypeORM.

Observação: Só importe o reflect-metadata, caso estiver usando o Typescript com NodeJS.

O reflect-metadata é uma biblioteca que fornece uma API para adicionar e ler metadados em classes, métodos, propriedades e parâmetros no JavaScript/TypeScript.

Ela se tornou essencial para o funcionamento de bibliotecas que utilizam decoradores, como é o caso do TypeORM.

Uma vez que, os decoradores precisam armazenar informações adicionais sobre as classes e seus elementos, e o JavaScript por si só não oferece uma API nativa para isso 😉

Em seguida, criamos uma constante (connectDB) que vai armazenar uma instancia de conexão com o banco de dados (MySQL). Dentro dessa classe, você pode informar alguns parâmetros, como:

type: é o tipo do banco de dados

host: é o endereço do banco de dados (poderia ser 127.0.0.1, ou qualquer IP ou URL).

port: é a porta do banco de dados (geralmente é configurado na porta 3306).

username: é o nome do usuário MySQL que você configurou em lições passadas.

password: é a senha do usuário MySQL acima.

database: é o nome do seu banco de dados (no nosso caso, ele é nodejs).

entities: representam as entidades do seu banco, voltaremos a isso mais tarde...

synchronize: se estiver setado como TRUE, ele vai criar automaticamente as tabelas no banco de dados (se necessário), sempre após uma conexão.

Por fim, estamos exportando esse arquivo dentro de um módulo, para que ele seja reutilizado dentro do seu index.js.

Observação: não se esqueça de alterar os parâmetros para os valores corretos de conexão com o seu banco de dados, ok?

Testando a conexão com o banco de dados

Com o seu arquivo principal aberto (index.js), vamos chamar o nosso módulo de conexão, a fim de testar se está tudo OK:

const connectDB = require('./db/connection'); // Importa o arquivo de conexão com o banco de dados

(async () => {
  try {
    const connection = await connectDB(); // Conecta ao banco de dados
    
    // Se você chegou aqui, significa que a conexão foi bem-sucedida
    console.log('Conexão testada com sucesso!');
  } catch (error) {
    // Caso haja algum erro na conexão
    console.error('Erro ao testar a conexão:', error);
  }
})();

O comando acima, executa uma função assíncrona (async), que realiza uma tentativa (try) de comunicação com o banco de dados (await connectDB()).

Ao executar o seu código (node ./index.js), você poderá se deparar com a seguinte mensagem no console:

Isso significa que a conexão foi um sucesso, e já estamos prontos para dar o próximo passo, que envolve a criação das nossas primeiras entidades (classes que representam tabelas do nosso banco de dados) 😋

Criando nossa primeira entidade (Data Mapper)

Em lições passadas, você aprendeu a criar uma tabela chamada de usuarios dentro do seu banco de dados com MySQL, estou certo?

Em caso afirmativo, acredito que você executou a seguinte query abaixo:

CREATE TABLE usuarios (
  id INT AUTO_INCREMENT PRIMARY KEY,
  nome VARCHAR(100) NOT NULL,
  email VARCHAR(100) NOT NULL,
  senha VARCHAR(255) NOT NULL,
  data_criacao datetime NOT NULL
);

Sendo assim, chegou o momento de transformarmos essa tabela em uma classe do Javascript, para posteriormente, ela possa atuar em conjunto com o TypeORM.

No nosso caso, estaremos usando o padrão Data Mapper para criar nossas entidades, e isso significa que a entidade que representa a tabela usuarios, deve ser definida como uma classe simples, e todas as nossas operações do banco de dados, deverão ser realizadas usando um repositório (explicaremos como criá-lo mais tarde), ou o EntityManager.

Portanto, vamos criar um novo arquivo chamado de Usuarios.js dentro da pasta entity:

TypeORM/
├── node_modules/
├── src/
│   ├── db/
│   │   connection.js
│   ├── entity/
│   │   Usuarios.js
├── index.js
├── package.json

Observação: caso você tenha mais tabelas no seu banco de dados, lembre-se que cada uma delas deverão seguir a lógica que estamos fazendo neste tópico, ou seja, você vai precisar criar um arquivo .js com o nome (ideal) que represente a sua tabela.

Dentro do arquivo Usuarios.js, iremos inserir a seguinte lógica:

const { EntitySchema } = require('typeorm');

const Usuarios = new EntitySchema({
  name: 'Usuarios',
  tableName: 'usuarios',
  columns: {
    id: {
      type: Number,
      primary: true,
      generated: true,
    },
    nome: {
      type: String,
      length: 100,
    },
    email: {
      type: String,
      length: 100,
    },
    senha: {
      type: String,
      length: 255,
    },
    data_criacao: {
      type: 'datetime',
    },
  },
});

module.exports = Usuarios;

vamos às explicações 😉

const { EntitySchema } = require('typeorm');

No código acima, você está importando o EntitySchema do pacote TypeORM. O EntitySchema é uma forma de definir entidades no TypeORM sem usar decorators, permitindo que você use o JavaScript puro.

const Usuarios = new EntitySchema({
  name: 'Usuarios',
  tableName: 'usuarios',
  columns: {
    ...
  },
});

No código acima, você está criando uma nova instância de EntitySchema, que define a entidade Usuarios. Os parâmetros principais são:

name: nome da entidade que será usada internamente pelo TypeORM.

tableName: nome da tabela no banco de dados (neste caso, informamos o nome 'usuarios').

columns: {
  id: {
    type: Number,
    primary: true,
    generated: true,
  },
  nome: {
    type: String,
    length: 100,
  },
  email: {
    type: String,
    length: 100,
  },
  senha: {
    type: String,
    length: 255,
  },
  data_criacao: {
    type: 'datetime',
  },
},

id: diz que dentro da tabela teremos uma coluna com essa nomenclatura, com as seguintes regras:

  • type: define que a coluna id será do tipo Number.
  • primary: define que a coluna id será do tipo chave primária.
  • generated: define que o valor será gerado automáticamente (auto-incremento).

nome: diz que dentro da tabela (usuarios), teremos uma coluna com essa nomenclatura, seguindo as regras:

  • type: define que a coluna nome será do tipo String.
  • lenght: especifica que o comprimento máximo do valor daquele campo é de 100 caracteres.

A mesma lógica se replica para as colunas senha e data_criacao.

Todas as colunas acima não aceitam valores nulos, para adicionar essa opção basta adicionar mais uma chave, como por exemplo:

nullable: true,

Note que a chave type, segue a mesma nomenclatura do SQL para definir os tipos de campos aceitos. Veja abaixo a lista de todos os tipos aceitos no TypeORM com o MySQL:

int, bigint, bit, decimal, money, numeric, smallint, smallmoney, tinyint, float, real, date, datetime2, datetime, datetimeoffset, smalldatetime, time, char, varchar, text, nchar, nvarchar, ntext, binary, image, varbinary, hierarchyid, sql_variant, timestamp, uniqueidentifier, xml, geometry, geography, rowversion

Não deixe de consultar a documentação da biblioteca para aprender mais sobre os tipos aceitos em outros banco de dados.

Adicionando nossa entidade (Usuarios) no arquivo de conexão com o banco de dados

Lembra do parâmetro entities que existe dentro do método createConnection que configuramos no arquivo connection.js?

Então... sempre quando criamos uma nova entidade, nós precisamos importá-la para dentro do nosso arquivo de conexão (connection.js), e posteriormente, declará-la dentro daquele parâmetro. 

Sendo assim:

require('reflect-metadata');// Importa o reflect-metadata, necessário para o TypeORM
const { createConnection } = require('typeorm');// Importa a função createConnection do TypeORM

const Usuarios = require('../entity/Usuarios');

// Função para conectar ao banco de dados
const connectDB = async () => {
  try {
    const connection = await createConnection({
      type: 'mysql',// Tipo do banco de dados
      host: 'localhost',// Endereço do banco de dados (poderia ser 127.0.0.1, ou qualquer IP ou URL)
      port: 3306, // Porta do banco de dados
      username: 'root', // Seu usuário MySQL
      password: '....', // Sua senha MySQL
      database: 'nodejs', // Seu banco de dados MySQL
      entities: [Usuarios],// Representam as entidades do seu banco, voltaremos a isso mais tarde...
      synchronize: true, // Cria automaticamente as tabelas no banco de dados
    });

    console.log('Conectado ao banco de dados');
    return connection;
  } catch (error) {
    console.log('Erro ao conectar ao banco de dados: ', error);
    throw error;
  }
};

module.exports = connectDB;

Caso desejar, você pode automatizar esse processo, adicionando uma funcionalidade de busca automática dentro da pasta entity, por exemplo:

require('reflect-metadata'); // Importa o reflect-metadata, necessário para o TypeORM
const { createConnection } = require('typeorm'); // Importa a função createConnection do TypeORM
const fs = require('fs'); // Módulo para manipulação de arquivos
const path = require('path'); // Módulo para trabalhar com caminhos de arquivos

// Função para conectar ao banco de dados
const connectDB = async () => {
  try {
    // Caminho para o diretório das entidades
    const entitiesPath = path.join(__dirname, '../entity');

    // Lê todos os arquivos no diretório de entidades e importa apenas os arquivos .js
    const entities = fs.readdirSync(entitiesPath)
      .filter(file => file.endsWith('.js')) // Filtra apenas arquivos .js
      .map(file => require(path.join(entitiesPath, file))); // Importa cada entidade

    const connection = await createConnection({
      type: 'mysql', // Tipo do banco de dados
      host: 'localhost', // Endereço do banco de dados (poderia ser 127.0.0.1, ou qualquer IP ou URL)
      port: 3306, // Porta do banco de dados
      username: 'root', // Seu usuário MySQL
      password: 'Flatronl1718s@', // Sua senha MySQL
      database: 'nodejs', // Seu banco de dados MySQL
      entities: entities, // Passa as entidades importadas
      synchronize: true, // Cria automaticamente as tabelas no banco de dados
    });

    console.log('Conectado ao banco de dados');
    return connection;
  } catch (error) {
    console.log('Erro ao conectar ao banco de dados: ', error);
    throw error;
  }
};

module.exports = connectDB;

No comando acima, estamos usando o módulo FS para buscar os arquivos existentes na pasta entity, e fazer a importação de maneira automática 🤖

Ao executar o index.js novamente (node ./index.js), veremos que tudo estará funcionando conforme o planejado:

Inserindo dados na tabela (Data Mapper)

O processo de inserção de dados na sua tabela (usuario), ocorre de forma bem simples. Basicamente você precisa importar a sua entidade no seu arquivo principal, e após isso, fazer o uso de 3 métodos diferentes, observe:

const connectDB = require('./src/db/connection'); // Certifique-se de que a conexão está correta
const Usuarios = require('./src/entity/Usuarios'); // Importando a entidade

(async () => {
  try {
    const connection = await connectDB(); // Conecta ao banco de dados

    // Obter o repositório da entidade Usuarios
    const usuarioRepository = connection.getRepository(Usuarios);

    // Criar um novo usuário
    const novoUsuario = usuarioRepository.create({
      nome: 'Nome do Usuário',
      email: 'email@exemplo.com',
      senha: 'senhaSegura',
      data_criacao: new Date(), // data atual
    });

    // Salvar o novo usuário no banco de dados
    await usuarioRepository.save(novoUsuario);

    console.log('Usuário inserido com sucesso!', novoUsuario);
  } catch (error) {
    console.error('Erro ao inserir usuário:', error);
  }
})();

getRepository: dizemos ao TypeORM que vamos utilizar a tabela usuarios, que por sua vez, está declarada dentro da entidade Usuario.

create: dizemos ao TypeORM que vamos fazer um INSERT dentro da tabela usuarios. Alí dentro nós setamos as colunas e valores que desejamos inserir.

save: após tudo configurado, executamos a query de INSERT.

Se tudo estiver OK, você receberá a seguinte mensagem no console:

Indicando que a inserção foi um sucesso! 😉

Buscando dados do banco de dados (Data Mapper)

Com relação a busca de dados (SELECT), o processo é relativamente simples, isto é, dependendo da complexidade da busca que você quer realizar 😅

Para uma busca simples, onde queremos retornar todos os valores da tabela usuarios sem distinção, podemos usar o método find para isso, observe:

const connectDB = require('./src/db/connection'); 
const Usuarios = require('./src/entity/Usuarios'); 

(async () => {
  try {
    const connection = await connectDB();
    const usuarioRepository = connection.getRepository(Usuarios);

    const todosUsuarios = await usuarioRepository.find();
    console.log('Todos os usuários:', todosUsuarios);
  } catch (error) {
    console.error('Erro ao buscar usuários:', error);
  }
})();

Para uma busca com filtro, podemos adicionar um parâmetro do tipo where no findOne da seguinte forma:

const connectDB = require('./src/db/connection'); 
const Usuarios = require('./src/entity/Usuarios'); 

(async () => {
  try {
    const connection = await connectDB();
    const usuarioRepository = connection.getRepository(Usuarios);

    const usuarioEspecifico = await usuarioRepository.findOne({
      where: { email: 'email@exemplo.com' },
    });
    console.log('Usuário específico:', usuarioEspecifico);
  } catch (error) {
    console.error('Erro ao buscar usuário:', error);
  }
})();

Para uma busca especializada, onde queremos retornar apenas duas colunas, podemos usar o parâmetro select no método find da seguinte forma:

const connectDB = require('./src/db/connection'); 
const Usuarios = require('./src/entity/Usuarios'); 

(async () => {
  try {
    const connection = await connectDB();
    const usuarioRepository = connection.getRepository(Usuarios);

    const nomesEmails = await usuarioRepository.find({
      select: ['nome', 'email'],
    });
    console.log('Nomes e emails:', nomesEmails);
  } catch (error) {
    console.error('Erro ao buscar nomes e emails:', error);
  }
})();

Caso você queria realizar uma busca implementando um certo limite, você pode usar o parâmetro limit no método find:

const connectDB = require('./src/db/connection'); 
const Usuarios = require('./src/entity/Usuarios'); 

(async () => {
  try {
    const connection = await connectDB();
    const usuarioRepository = connection.getRepository(Usuarios);

    const usuariosLimitados = await usuarioRepository.find({
      take: 5,
    });
    console.log('Usuários limitados:', usuariosLimitados);
  } catch (error) {
    console.error('Erro ao buscar usuários limitados:', error);
  }
})();

Além disso, você também pode implementar uma paginação, para isso você vai precisar informar os parâmetros skip e take, da seguinte forma:

const connectDB = require('./src/db/connection'); 
const Usuarios = require('./src/entity/Usuarios'); 

(async () => {
  try {
    const connection = await connectDB();
    const usuarioRepository = connection.getRepository(Usuarios);

    const pagina = 1; 
    const limite = 5; 
    const usuariosPaginados = await usuarioRepository.find({
      skip: (pagina - 1) * limite,
      take: limite,
    });
    console.log(`Usuários na página ${pagina}:`, usuariosPaginados);
  } catch (error) {
    console.error('Erro ao buscar usuários paginados:', error);
  }
})();

Fácil, não?

Implementando o JOIN com TypeORM (Data Mapper)

Vamos supor que você queira gerenciar um sistema de gerenciamento de projetos, onde cada usuário pode ter vários projetos associados. 

Ou seja, supondo que você tenha um usuário chamado Maria, e que você quer associar este usuário a dois projetos: ProjetoA e ProjetoB. Como você faria isso com o TypeORM?

Para isso, você precisa se certificar de que possui duas entidades:

Usuarios.js:

const { EntitySchema } = require('typeorm');

const Usuarios = new EntitySchema({
  name: 'Usuarios',
  tableName: 'usuarios',
  columns: {
    id: {
      type: Number,
      primary: true,
      generated: true,
    },
    nome: {
      type: String,
      length: 100,
    },
    email: {
      type: String,
      length: 100,
      unique: true,
    },
    senha: {
      type: String,
      length: 255,
    },
    data_criacao: {
      type: 'datetime',
    },
  },
  relations: {
    projetos: {
      type: 'one-to-many',
      target: 'Projetos',
      inverseSide: 'usuario',
    },
  },
});

module.exports = Usuarios;

Projetos.js:

const { EntitySchema } = require('typeorm');

const Projetos = new EntitySchema({
  name: 'Projetos',
  tableName: 'projetos',
  columns: {
    id: {
      type: Number,
      primary: true,
      generated: true,
    },
    nome: {
      type: String,
      length: 100,
    },
    usuarioId: {
      type: Number,
    },
  },
  relations: {
    usuario: {
      type: 'many-to-one',
      target: 'Usuarios',
      joinColumn: { name: 'usuarioId' },
      cascade: true,
    },
  },
});

module.exports = Projetos;

Observação: caso você não tiver uma tabela no banco de dados relacionado a projetos, pode rodar a sua aplicação tranquilamente que o TypeORM vai se encarregar de criá-la para você.

Note que usamos a chave relations, onde estamos indicando a correlação entre essas duas tabelas.

Para saber mais sobre a chave relations, não deixe de consultar a documentação do TypeORM.

Por fim, basta que você use a consulta da seguinte forma:

const connectDB = require('./src/db/connection'); 
const Usuarios = require('./src/entity/Usuarios'); 

(async () => {
  try {
    const connection = await connectDB();
    const usuarioRepository = connection.getRepository(Usuarios);

    // Buscar o usuário e seus projetos
    const usuarioComProjetos = await usuarioRepository
      .createQueryBuilder('usuario')
      .leftJoinAndSelect('usuario.projetos', 'projeto')
      .where('usuario.nome = :nome', { nome: 'Maria' })
      .getOne();

    console.log('Projetos de Maria:', usuarioComProjetos.projetos);
  } catch (error) {
    console.error('Erro ao buscar projetos:', error);
  }
})();

Note que usamos o comando leftJoinAndSelect informando a correlação das colunas em ambas as tabelas.

.createQueryBuilder('usuario'): cria uma nova instância do QueryBuilder para a entidade Usuarios.

.leftJoinAndSelect('usuario.projetos', 'projeto'): faz uma junção à esquerda (LEFT JOIN) entre a tabela usuarios e a tabela projetos, permitindo que você busque dados relacionados.

.where('usuario.nome = :nome', { nome: 'Maria' }): especifica que você está procurando registros, onde o campo nome da tabela usuarios é igual a um valor específico.

.getOne(): executa a consulta de modo a retornar um único resultado.

Atualizando o banco de dados (Data Mapper)

Para atualizar o banco de dados, podemos usar o método update de algumas formas diferentes.

Para fazer um update validando por colunas (WHERE), você pode fazer isso da seguinte forma:

const connectDB = require('./src/db/connection'); 
const Usuarios = require('./src/entity/Usuarios'); 

(async () => {
  try {
    const connection = await connectDB();
    const usuarioRepository = connection.getRepository(Usuarios);

    // Atualizar o usuário com id = 1
    await usuarioRepository.update(1, { nome: 'Novo Nome' });
    
    console.log('Usuário atualizado com sucesso!');
  } catch (error) {
    console.error('Erro ao atualizar usuário:', error);
  }
})();

Para fazer um update validando por duas colunas, podemos usar o where:

const connectDB = require('./src/db/connection'); 
const Usuarios = require('./src/entity/Usuarios'); 

(async () => {
  try {
    const connection = await connectDB();
    const usuarioRepository = connection.getRepository(Usuarios);

    // Verificar se o usuário com id = 1 e nome = 'Nome Antigo' existe
    const usuario = await usuarioRepository.findOne({ where: { id: 1, nome: 'Nome Antigo' } });

    if (usuario) {
      // Atualizar o usuário com id = 1
      await usuarioRepository.update(1, { nome: 'Novo Nome' });
      console.log('Usuário atualizado com sucesso!');
    } else {
      console.log('Usuário não encontrado com o id e nome especificados.');
    }
  } catch (error) {
    console.error('Erro ao atualizar usuário:', error);
  }
})();

Removendo dados do banco de dados (Data Mapper)

Para remover seus dados do banco, você pode usar o método delete de algumas formas diferentes:

Para remover um dado usando o id do usuário, basta apenas informar um valor numérico:

const connectDB = require('./src/db/connection'); 
const Usuarios = require('./src/entity/Usuarios'); 

(async () => {
  try {
    const connection = await connectDB();
    const usuarioRepository = connection.getRepository(Usuarios);

    // Deletar o usuário com id = 1
    await usuarioRepository.delete(1);
    
    console.log('Usuário deletado com sucesso!');
  } catch (error) {
    console.error('Erro ao deletar usuário:', error);
  }
})();

Para remover um dado, usando uma validação que envolve mais colunas, você pode usar o where da seguinte forma:

const connectDB = require('./src/db/connection'); 
const Usuarios = require('./src/entity/Usuarios'); 

(async () => {
  try {
    const connection = await connectDB();
    const usuarioRepository = connection.getRepository(Usuarios);

    // Verificar se o usuário com id = 1 e nome = 'Nome do Usuário' existe
    const usuario = await usuarioRepository.findOne({ where: { id: 1, nome: 'Nome do Usuário' } });

    if (usuario) {
      // Deletar o usuário com id = 1
      await usuarioRepository.delete(1);
      console.log('Usuário deletado com sucesso!');
    } else {
      console.log('Usuário não encontrado com o id e nome especificados.');
    }
  } catch (error) {
    console.error('Erro ao deletar usuário:', error);
  }
})();

Operadores com TypeORM

Até o momento, você aprendeu a fazer comparações diretas em conjunto com a clausula where no TypeORM.

Mas como ficaria o uso dos outros operadores, tais como >, <, <=, >=, =, ==, not, in, like, between... com TypeORM?

No caso eles, você precisa importar no seu projeto as funções MoreThan, LessThan, MoreThanOrEqual, LessThanOrEqual, In, Between, Not, Like da biblioteca do TypeORM da seguinte forma:

const { MoreThan, LessThan, MoreThanOrEqual, LessThanOrEqual, In, Between, Not, Like } = require('typeorm');

E em seguida, basta encapsular dentro da comparação, vejamos alguns exemplos:

Operador > (maior que):

const usuariosMaiorQue = await usuarioRepository.find({
  where: {
    idade: MoreThan(30) // idade > 30
  }
});

Operador < (menor que):

const usuariosMenorQue = await usuarioRepository.find({
  where: {
    idade: LessThan(30) // idade < 30
  }
});

Operador >= (maior ou igual):

const usuariosMaiorOuIgual = await usuarioRepository.find({
  where: {
    idade: MoreThanOrEqual(30) // idade >= 30
  }
});

Operador <= (menor ou igual):

const usuariosMenorOuIgual = await usuarioRepository.find({
  where: {
    idade: LessThanOrEqual(30) // idade <= 30
  }
});

Operador IN:

const usuariosIn = await usuarioRepository.find({
  where: {
    idade: In([25, 30, 35]) // idade IN (25, 30, 35)
  }
});

Operador BETWEEN:

const usuariosBetween = await usuarioRepository.find({
  where: {
    idade: Between(25, 30) // idade BETWEEN 25 AND 30
  }
});

Operador NOT:

const usuariosNot = await usuarioRepository.find({
  where: {
    idade: Not(30) // idade != 30
  }
});

Operador LIKE:

const usuariosLike = await usuarioRepository.find({
  where: {
    nome: Like('%João%') // nome LIKE '%João%'
  }
});

Vejamos um exemplo completo:

const connectDB = require('./src/db/connection'); 
const Usuarios = require('./src/entity/Usuarios'); 
const { MoreThan, LessThan, MoreThanOrEqual, LessThanOrEqual, In, Between, Not, Like } = require('typeorm');

(async () => {
  try {
    const connection = await connectDB();
    const usuarioRepository = connection.getRepository(Usuarios);

    // Exemplo de consulta com diferentes operadores
    const usuariosFiltrados = await usuarioRepository.find({
      where: {
        idade: MoreThan(30),     // idade > 30
        nome: Like('%Maria%'),   // nome LIKE '%Maria%'
        cidade: In(['São Paulo', 'Rio de Janeiro']), // cidade IN ('São Paulo', 'Rio de Janeiro')
        salario: Between(2000, 5000), // salario BETWEEN 2000 AND 5000
        ativo: Not(false)        // ativo != false
      }
    });
    
    console.log('Usuários filtrados:', usuariosFiltrados);
  } catch (error) {
    console.error('Erro ao buscar usuários:', error);
  }
})();

Note que podemos usá-los em conjunto com outros operadores!

Criando um Repositório para lidar com suas entidades (Data Mapper)

Quando trabalhamos com orientação a objetos, o ideal é que tenhamos um módulo específico para lidar com todas as requisições que nós fazemos a uma determinada entidade.

Isso aliviaria um pouco o seu arquivo principal (index.js), e ainda deixaria o seu código mais organizado! 

Sendo assim, dentro da pasta src, vamos criar uma nova pasta chamada de repositories, onde dentro dela vai existir um arquivo chamado de UsuariosRepository.js:

// src/repositories/UsuariosRepository.js
const connectDB = require('../db/connection');
const Usuarios = require('../entity/Usuarios');

class UsuariosRepository {
  constructor() {
    this.connection = null;
    this.usuarioRepository = null;
  }

  async connect() {
    if (!this.connection) {
      this.connection = await connectDB();
      this.usuarioRepository = this.connection.getRepository(Usuarios);
    }
  }

  async createUsuario(data) {
    await this.connect(); // Conecta ao banco, se ainda não estiver conectado

    const novoUsuario = this.usuarioRepository.create({
      nome: data.nome,
      email: data.email,
      senha: data.senha,
      data_criacao: new Date(),
    });

    await this.usuarioRepository.save(novoUsuario);
    return novoUsuario;
  }
}

module.exports = new UsuariosRepository();

O código acima, nada mais é do que uma classe especializada em realizar operações com o nosso banco de dados.

Para usá-la, é bem simples, basta que você execute uma das 3 lógicas apresentada abaixo, no seu index.js:

// src/index.js ou onde você quiser chamar o insert
const usuariosRepository = require('./repositories/UsuariosRepository');

(async () => {
  try {
    const novoUsuario = await usuariosRepository.createUsuario({
      nome: 'Nome do Usuário',
      email: 'email@exemplo.com',
      senha: 'senhaSegura',
    });

    console.log('Usuário inserido com sucesso!', novoUsuario);
  } catch (error) {
    console.error('Erro ao inserir usuário:', error);
  }
})();

Você também pode fazer isso por meio de uma função:

// src/index.js ou onde você quiser chamar o insert
const usuariosRepository = require('./repositories/UsuariosRepository');

function inserirUsuario() {
  usuariosRepository.createUsuario({
    nome: 'Nome do Usuário',
    email: 'email@exemplo.com',
    senha: 'senhaSegura',
  })
  .then(novoUsuario => {
    console.log('Usuário inserido com sucesso!', novoUsuario);
  })
  .catch(error => {
    console.error('Erro ao inserir usuário:', error);
  });
}

// Chama a função para inserir o usuário
inserirUsuario();

Ou você também pode fazer isso por meio de uma função assíncrona de alto nível, observe:

async function inserirUsuario() {
  try {
    const novoUsuario = await usuariosRepository.createUsuario({
      nome: 'Nome do Usuário',
      email: 'email@exemplo.com',
      senha: 'senhaSegura',
    });
    console.log('Usuário inserido com sucesso!', novoUsuario);
  } catch (error) {
    console.error('Erro ao inserir usuário:', error);
  }
}

// Chama a função para inserir o usuário
inserirUsuario();

No caso do comando acima, o UsuarioRepository contém apenas os métodos de inseção no banco de dados (INSERT) relacionados com a tabela usuarios, mas nada impede que você adicione outras lógicas como: SELECT, UPDATE e DELETE.

Observação: caso você tenha uma outra tabela do banco de dados, não se esqueça de criar uma entidade e um repositorio para essa tabela, ok?

Arquivos da lição

Os arquivos que você viu durante o decorrer desta lição, podem ser encontrados neste link.

Conclusão

Nesta lição, você aprendeu a usar o TypeORM em conjunto com suas aplicações em NodeJS.

O conteúdo sobre TypeORM não se esgota por aqui, e ainda há muito o que aprender com ele. Sendo assim, não deixe de consultar a documentação da biblioteca.

E até a próxima lição 😉

Criadores de Conteúdo

Foto do William Lima
William Lima
Fundador da Micilini

Inventor nato, escreve conteudos de programação para o portal da micilini.

Torne-se um MIC 🤖

Mais de 100 mic's já estão conectados na plataforma.