Prisma com NodeJS

Prisma com NodeJS

Olá leitor, seja bem vindo a uma das aulas mais interessantes da jornada do NodeJS 🥳

De acordo com as movimentações do mercado, mais e mais desenvolvedores vem adotando o PRISMA, como um ORM principal para suas aplicações feitas com NodeJS.

Se você fizer uma breve uma pesquisa no Google pelo termo: "Sequelize VS Prisma" - que são os dois ORMs mais utilizados em aplicações com NodeJS -, verá que existem diversos artigos que comparam a performance entre essas duas bibliotecas. 📊

A grande maioria desses artigos, alegam que a performance do Prisma é bem superior se comparado a outros ORMs, como é o caso do Sequelize e TypeORM (que explicamos sobre eles em lições passadas).

Além disso, grande parte dos projetos empresariais, estão cada vez mais migrando para o Prisma, logo, é de vital importância que você aprenda a trabalhar com ele 😉

Agora chega de papo, e vamos direto ao que interessa!

O que é o Prisma?

Nessa altura do campeonato, acredito que você já deve imaginar que o Prisma, é um ORM (Object-Relational Mapping)... isto é, caso você não tenha pulado a introdução desta lição 😅

No caso dele, ele é um dos ORMs mais modernos e performáticos voltado a aplicações feitas com Javascript, ou Typescript.

Ele facilita a interação entre a aplicação e o banco de dados, permitindo que você escreva consultas utilizando uma sintaxe mais simples e intuitiva, e o melhor, sem precisar escrever queries SQL complexas de forma direta.

Vejamos agora, as principais funcionalidades do Prisma:

Gerenciamento de esquemas: ele usa um arquivo chamado schema.prisma onde você define o modelo do seu banco de dados, incluindo tabelas, colunas, e relacionamentos.

Migrações de banco de dados: permite gerar e aplicar migrações automaticamente, mantendo o banco de dados sincronizado com o modelo definido no código.

Cliente Prisma: ele gera automaticamente um cliente que você pode usar para interagir com o banco de dados, através de métodos JavaScript/TypeScript.

Suporte a diferentes bancos de dados: ele suporta outros bancos de dados, como PostgreSQL, MySQL, SQLite e outros.

É importante ressaltar que o Prisma segue o padrão Data Mapper, e não Active Record. Pois como veremos em tópicos futuros, você pode definir modelos no arquivo schema.prisma, e o código que manipula, os dados ficam separados dos objetos que representam esses dados.

Agora que você já sabe o que o Prisma faz, partiu criar o nosso projeto de testes 😉

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 Prisma 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

Em lições passadas, 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 Prisma 😌

Instalando o Prisma

Para instalar a biblioteca do Prisma no seu projeto, você vai precisar instalar dois pacotes:

prisma: é a uma ferramenta de CLI (Command Line Interface) que nós iremos usar para gerenciar o Prisma no terminal.

@prisma/client: é um pacote gerado que você irá utilizar para interagir com o banco de dados dentro da sua aplicação.

Sendo assim:

npm install prisma --save-dev
npm install @prisma/client

Note que o pacote prisma será utilizado somente no ambiente de desenvolvimento (--save-dev), não sendo necessário ser instalado em um ambiente de produção.

Por fim, não se esqueça de instalar a biblioteca mysql2, também no seu projeto. Pois como iremos utilizar o Prisma com um banco de dados MySQL, tal biblioteca se faz necessária:

npm install mysql2

Feito isso, vamos inicializar o Prisma 😉

Inicializando o Prisma (schema.prisma)

Diferentes dos outros ORMs que nós vimos durante a jornada do NodeJS. O Prisma tem um funcionamento um pouco diferente dos demais.

A primeira diferenciação, se deu durante o processo de instalação, que foi quando precisamos instalar o CLI (Command Line Interface).

No ambiente de desenvolvimento, o CLI é necessário, pois será por meio dele que conseguiremos criar o nosso arquivo de configuração (schema.prisma), e também executar outras operações essenciais, como gerar migrações, aplicar as alterações no banco de dados, e até mesmo visualizar o estado atual do banco.

A segunda diferenciação, é o arquivo de configuração (schema.prisma), que é o local onde iremos declarar todas as nossas entidades, ou seja, todas as nossas tabelas do banco de dados (além de algumas configurações a mais).

Disso isso, agora nós precisamos abrir o terminal (Prompt de Comando) dentro da pasta raiz do seu projeto, e executar o seguinte comando:

npx prisma init

Com este comando, o CLI irá criar uma pasta chamada prisma, dentro da pasta raiz do nosso projeto. E dentro dela, também será criado um arquivo (vazio) chamado de schema.prisma.

Esse arquivo (schema.prisma) é o ponto central para definir os modelos do nosso banco de dados, e todas as configurações associadas a ele.

Perceba também que foi criado um arquivo .env dentro da pasta raiz do seu projeto. Que nada mais é do que um arquivo que controla as suas variáveis de ambiente.

Configurando o seu banco de dados

Na lição que falava sobre MySQL, você chegou a criar uma tabela chamada usuarios com as seguintes características:

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
);

E como todo bom ORM, nós somos obrigados a declarar essas características também dentro da nossa aplicação 😅

No caso do Prisma, nós podemos fazer isso por meio do arquivo schema.prisma que acabou de ser criado no tópico anterior.

Além disso, vamos precisar configurar a conexão com o banco de dados MySQL, também dentro deste arquivo.

Todas as informações passadas acima, podem ser reproduzidas dentro do schema.prisma, da seguinte forma:

// Conexão com o banco de dados
datasource db {
  provider = "mysql"
  url      = env("DATABASE_URL")
}

// Gerador do Prisma Client
generator client {
  provider = "prisma-client-js"
}

// Definição do modelo Usuario
model Usuario {
  id           Int      @id @default(autoincrement())
  nome         String   @db.VarChar(100)
  email        String   @db.VarChar(100)
  senha        String   @db.VarChar(255)
  data_criacao DateTime @default(now())
}

datasource db: é um objeto que define a conexão com o banco de dados que o Prisma vai usar, ele conta com duas chaves:

  • provider = "mysql": indica que o Prisma vai se conectar a um banco de dados MySQL. Outros exemplos de providers poderiam ser 'postgresql' ou 'sqlite'.
  • url = env("DATABASE_URL"): aqui nós estamos definindo que o Prisma vai usar a variável de ambiente DATABASE_URL (existente dentro do arquivo .env), para definir a URL de conexão do banco de dados. 

Observação: será no arquivo .env que nós iremos especificar o endereço, usuário, senha, e também o nome do banco. Veremos como fazer isso no próximo tópico 😌

generator client: o Prisma usa generators para criar nossos código automaticamente. Neste caso, estamos gerando o Prisma Client.

provider = "prisma-client-js": aqui estamos definindo que usaremos o Prisma Client como o provedor, mais especificamente a sua versão em Javascript (js).

O Prisma Client é uma biblioteca gerada automaticamente pelo sistema, e que permite que você interaja com o banco de dados usando JavaScript (ou TypeScript) de forma segura e tipada.

Após rodar o comando npx prisma generate (AINDA NÃO É A HORA DE EXECUTAR ESTE COMANDO), o Prisma vai gerar um código que você poderá usar na sua aplicação, para acessar e manipular os dados do seu banco de dados.

model Usuario: aqui estamos definindo o modelo Usuario, que corresponde à tabela usuarios no banco de dados. Cada propriedade do modelo define uma coluna na tabela:

  • id Int @id @default(autoincrement()): cria uma coluna id do tipo inteiro (Int), define como chave primária (@id), e configura o campo para ser autoincrementado. Isso equivale ao AUTO_INCREMENT no MySQL.
  • nome String @db.VarChar(100): define a coluna nome do tipo string com o limite de 100 caracteres (@db.VarChar(100)), similar a VARCHAR(100) no MySQL.
  • email String @db.VarChar(100): define a coluna email como uma string com o limite de 100 caracteres.
  • senha String @db.VarChar(255): define a coluna senha como uma string com o limite de 255 caracteres, o que é comum para armazenar senhas criptografadas.
  • data_criacao DateTime @default(now()): define a coluna data_criacao como um DateTime, e o valor padrão será a data e hora atuais no momento da inserção do registro (@default(now())).

É importante ressaltar que todas as suas tabelas devem estar informadas dentro de objetos no schema.prisma, ok? Basta seguir a mesma sintaxe do model Usuario em suas tabelas futuras 🤓

Agora só falta fazermos uma pequena modificação dentro do nosso arquivo .env 🙃

Configurando a conexão com a base de dados no arquivo .env

Por padrão, o Prisma faz o uso do arquivo .env (variáveis de ambiente) para recuperar a URL de acesso ao banco de dados.

E diferente dos outros ORMs, o Prisma nos pede para configurarmos esse acesso dentro de uma única linha:

DATABASE_URL="mysql://usuario:senha@localhost:3306/nome_do_banco"

Sendo assim, no arquivo .env, que foi criado na pasta raiz do seu projeto, você precisa especificar o endereço, usuário, senha, e nome do banco, como descrito no exemplo acima.

Só não se esqueça de informar seus dados corretamente dentro do arquivo .env.

Configurações avançadas no schema.prisma

Anteriormente, você aprendeu a criar seus models dentro do arquivo schema.prisma, e apesar de ser algo relativamente simples, existem alguns poréns que precisamos nos atentar, principalmente quando temos uma tabela um pouco mais customizada.

Como faço para mudar o nome da minha tabela

Existem casos em que a sua tabela não levará o mesmo nome do seu model. Como é o caso em que você possui uma tabela chamada de meus-usuarios.

Em casos como esses, o ideal é que você declare a anotação @@map, de forma a indicar ao Prisma que aquele modelo é pertencente a uma determinada tabela do seu banco.

model Usuario {
  id           Int      @id @default(autoincrement())
  nome         String   @db.VarChar(100)
  email        String   @db.VarChar(100)
  senha        String   @db.VarChar(255)
  data_criacao DateTime @default(now())

  // Mapeia o modelo 'Usuario' para a tabela 'meus-usuarios'
  @@map("meus-usuarios")
}

O modelo continua sendo chamado Usuario dentro do código do Prisma. Mas o mapeamento está apontando para a tabela meus-usuarios do seu banco de dados.  

Como o Prisma identifica o nome da tabela? Como ele sabe que o model Usuario pertence a tabela 'usuarios'❓  

Por padrão, o Prisma mapeia os modelos para as tabelas no banco de dados, usando uma convenção de nomenclatura. Onde segue a seguinte regra:

"O nome do modelo é convertido para minúsculas e, se houver múltiplos modelos, o Prisma adiciona um "s" no final para formar o plural".

Sendo assim, o nosso modelo Usuario se torna a tabela usuarios no banco de dados, e assim sucessivamente. Se você tivesse uma tabela chamada projetos, bastaria que você criasse um modelo chamado Projeto.

Lembre-se de que você pode usar a anotação @@map sempre que precisar se conectar com tabelas que levam nomes não convêncionais.

Como funciona a formatação do arquivo schema.prisma? Tenho que dar espaçamentos para que tudo pareça tabelado, igual planilhas do EXCEL❓    

A formatação do arquivo schema.prisma não exige um espaçamento ou indentação específicos, mas seguir uma boa prática de formatação pode tornar o arquivo mais legível e organizado.

Veja algumas diretrizes que podemos seguir:

  • Use quebras de linha para separar seções distintas (como datasource, generator, e model), para facilitar a leitura.
  • Use indentação consistente para as propriedades dentro dos modelos. Isso ajuda a visualizar claramente a hierarquia e as relações.
  • Adicione comentários quando necessário para esclarecer o propósito de cada seção ou modelo. Isso é útil para você e para outros que possam ler o código.
  • Espaçamentos dizem ao Prisma a separação de cada informação dentro de um model (eles não são opcionais, você realmente precisa deles).
// Conexão com o banco de dados
datasource db {
  provider = "mysql"             // Provedor do banco de dados
  url      = env("DATABASE_URL") // URL de conexão
}

// Gerador do Prisma Client
generator client {
  provider = "prisma-client-js" // Gerador do Prisma Client
}

// Definição do modelo Usuario
model Usuario {
  id           Int      @id @default(autoincrement()) // ID do usuário
  nome         String   @db.VarChar(100)              // Nome do usuário
  email        String   @db.VarChar(100)              // Email do usuário
  senha        String   @db.VarChar(255)              // Senha do usuário
  data_criacao DateTime @default(now())                // Data de criação
}

No código acima, se id, Int@id @default(autoincrement()) estivessem na mesma linha sem espaçamentos:

model Usuario {
  idInt@id @default(autoincrement()) // ID do usuário
  ....
}

o Prisma nunca iria reconhecer que temos uma coluna id do tipo inteiro... Por esse motivo que você precisa dar pelo menos um espaçamento entre as declarações.

Mas é claro, você não é obrigado a dar espaçamento o suficiente para manter tudo tabelado (igual uma planilha do excel), basta que você separe as coisas:

// Definição do modelo Usuario
model Usuario {
  id Int @id @default(autoincrement()) // ID do usuário
  nome String @db.VarChar(100)              // Nome do usuário
  email String @db.VarChar(100)              // Email do usuário
  senha String @db.VarChar(255)              // Senha do usuário
  data_criacao DateTime @default(now())                // Data de criação
}

Entretanto, se você conseguir deixar tabelado e organizado, a visualização será melhor, observe:

// Definição do modelo Usuario
model Usuario {
  id           Int      @id @default(autoincrement()) // ID do usuário
  nome         String   @db.VarChar(100)              // Nome do usuário
  email        String   @db.VarChar(100)              // Email do usuário
  senha        String   @db.VarChar(255)              // Senha do usuário
  data_criacao DateTime @default(now())                // Data de criação
}

Além dos tipos identificadores das colunas (Int, String, DateTime), temos mais quais❓   

O Prisma suporta uma variedade de tipos de dados para atender a diferentes necessidades, como números, booleanos, textos longos, dados estruturados em JSON e dados binários.

Vamos ver alguns exemplos:

model Produto {
  id         Int      @id @default(autoincrement())
  nome       String   @db.VarChar(100)
  preco      Decimal  @db.Decimal(10, 2)
  ativo      Boolean  @default(true)
  estoque    Int
  descricao  String   @db.Text
  tags       Json
  imagem     Bytes
  criado_em  DateTime @default(now())
}

Por que as colunas estoque, tags e imagem estão vazias (sem usar o @...)?

No contexto do Prisma, quando você define um modelo e especifica os tipos de dados como Int, Json, ou Bytes, a definição em si não contém valores, pois os mesmos não são necessários.

Geralmente informamos tais valores (@...) quando queremos limitar a quantidade de caracteres, ou adicionar uma informação importante.

Como funciona a anotação @db, e quais os diferentes tipos de dados que podemos utilizar

A anotação @db é usada para especificar tipos de dados do banco de dados que podem não corresponder exatamente aos tipos de dados padrão do Prisma

@db.VarChar(n): representa uma coluna do tipo VARCHAR com um comprimento máximo de n caracteres.

nome String @db.VarChar(100)

@db.Text: é usado para colunas que armazenam textos longos, equivalente ao tipo TEXT no MySQL.

descricao String @db.Text

@db.Int: representa um número inteiro.

idade Int @db.Int

@db.Float: Representa um número de ponto flutuante.

altura Float @db.Float

@db.Decimal(precision, scale): usado para números decimais com precisão exata. precision é o total de dígitos, e scale é o número de dígitos após a vírgula.

preco Decimal @db.Decimal(10, 2)

@db.Boolean: representa um valor booleano (verdadeiro ou falso).

ativo Boolean @db.Boolean

@db.DateTime: representa uma data e hora.

criado_em DateTime @db.DateTime

@db.Date: representa apenas uma data (sem tempo).

aniversario DateTime @db.Date

@db.Time: representa apenas um horário.

hora Time @db.Time

@db.Bytes: é usado para armazenar dados binários.

arquivo Bytes @db.Bytes

@db.Json: permite armazenar dados em formato JSON.

configuracoes Json @db.Json

Par mais informações sobre anotações do Prisma, não deixe de consultar a documentação da biblioteca.

Anteriormente você me disse que é no arquivo schema.prisma, que eu deveria representar todas as tabelas que existem em meu banco de dados, tem como me dar um exemplo de diversas tabelas dentro desse arquivo❓  

Mas é claro, observe o arquivo (schema.prisma) contendo mais de uma tabela:

// Conexão com o banco de dados
datasource db {
  provider = "mysql"
  url      = env("DATABASE_URL")
}

// Gerador do Prisma Client
generator client {
  provider = "prisma-client-js"
}

// Definição do modelo Usuario
model Usuario {
  id           Int      @id @default(autoincrement())
  nome         String   @db.VarChar(100)
  email        String   @db.VarChar(100) @unique
  senha        String   @db.VarChar(255)
  data_criacao DateTime @default(now())
}

// Definição do modelo Produto
model Produto {
  id         Int      @id @default(autoincrement())
  nome       String   @db.VarChar(100)
  preco      Decimal  @db.Decimal(10, 2)
  estoque    Int
  descricao  String   @db.Text
}

// Definição do modelo Pedido
model Pedido {
  id         Int      @id @default(autoincrement())
  usuarioId  Int
  produtoId  Int
  quantidade Int      @default(1)
  data_pedido DateTime @default(now())
}

Note que as tabelas estão separadas dentro dos seus respectivos models, seguindo as regras de espaçamentos.

Rodando migrações com o Prisma

Nós já temos o Prisma instalado, o arquivo de configurações com nossos models (schema.prisma) já setados, mas ainda falta um passo para garantirmos a sua utilização, precisamos rodar a nossa migração.

Com o seu terminal (Prompt de Comando) aberto na pasta raiz do seu projeto, execute o seguinte comando:

npx prisma migrate dev --name init

npx prisma: o npx é uma ferramenta que vem com o NodeJS, e permite que você execute pacotes diretamente do repositório npm sem precisar instalá-los globalmente. Aqui, nós estamos executando o CLI do Prisma.

migrate dev: o comando migrate é utilizado para gerenciar migrações no Prisma. O subcomando dev indica que você está realizando a migração em um ambiente de desenvolvimento, e isso significa que o Prisma irá:

  • Criar uma nova migração com base nas alterações no seu arquivo schema.prisma.
  • Aplicar essa migração ao banco de dados.
  • Atualizar o arquivo de migrações para manter o histórico.

E se eu estiver em um ambiente de produção?

Em ambientes de produção, você pode usar migrate deploy (em vez de dev) para aplicar migrações de forma mais controlada e segura, sem a criação de novos arquivos de migração.

--name init: o parâmetro --name permite que você forneça um nome descritivo para a migração que está acontecendo (para quem entende de Git, é como se o comando --name atuasse como um commit -m).

Neste caso, init é um nome comum para a primeira migração que cria as tabelas iniciais do banco de dados. Você pode escolher qualquer nome que faça sentido para a mudança que está sendo feita, como por exemplo:

  • minha-migracao-inicial
  • migracao-1.0.0

Portanto, com o comando acima, você consegue configurar a estrutura inicial do banco de dados, e manter um histórico das migrações feitas.

Após a execução do código, a seguinte mensagem será mostrada no console:

Indicando que uma pasta migrations acaba de ser criada, junto com um arquivo .sql que representa o seu banco de dados.

Além disso, se você olhar o seu banco de dados com algum gerenciador (MySQL WorkBench ou HeidiSQL), verá que duas tabelas foram criadas:

A tabela _prisma_migrations é criada automaticamente pelo Prisma no seu banco de dados quando você executa suas migrações. Essa tabela serve para rastrear o histórico das migrações aplicadas ao banco de dados. 

Já a tabela usuarios nada mais é do que a sua tabela que está relacionada ao model Usuario.

Se você estiver usando um banco de dados que não está vazio, o Prisma pode não aplicar migrações para evitar perder dados existentes. Para forçar a migração, você pode tentar:

npx prisma migrate reset

Se você está apenas testando e não se importa com dados existentes, você pode recriar o banco de dados e tentar novamente. Para isso, o ideal é que você execute o comando abaixo:

npx prisma migrate dev --create-only

Feito isso, já estamos prontos para testar nossa conexão com o banco de dados 🙂

Testando sua conexão com o banco de dados

Para testar sua conexão com a sua base dados por meio do Prisma, basta abrir o seu arquivo principal (index.js), e colar o seguinte comando:

const { PrismaClient } = require('@prisma/client');
const prisma = new PrismaClient();

async function main() {
  // Apenas tenta conectar ao banco de dados
  await prisma.$connect();
  console.log('Conexão com o banco de dados estabelecida com sucesso!');
}

main()
  .catch(e => {
    console.error('Erro ao conectar ao banco de dados:', e);
    process.exit(1);
  })
  .finally(async () => {
    await prisma.$disconnect();
    console.log('Conexão com o banco de dados encerrada.');
  });

await prisma.$connect(): tenta estabelecer uma conexão com o banco de dados. Se a conexão for bem-sucedida, uma mensagem será exibida.

Tratamento de Erros: se ocorrer um erro durante a conexão, ele será capturado e exibido pelo catch no console.

Desconexão: o método prisma.$disconnect() é chamado para encerrar a conexão com o banco de dados, independentemente do sucesso ou falha da operação.

E sim, é ideal que você sempre feche a comunicação com o seu banco de dados após realizar suas operações, pois manter a conexão aberta pode afetar a performance da sua aplicação.

Se tudo ocorrer bem, você receberá a seguinte mensagem no console:

O que indica que a comunicação com o seu banco de dados por parte da sua aplicação foi um sucesso 🥳

Toda vez que eu for configurar uma aplicação do zero usando NodeJS e Prisma, eu sou obrigado a executar esses códigos de migrações no terminal

Sim, felizmente precisamos fazer isso toda vez, pois o Prisma requer que tais configurações sejam feitas no terminal. Você só precisa fazer isso uma única vez, ou sempre quando adicionar novos modelos no seu arquivo schema.prisma.

Agora, chegou o tão esperado momento em que a gente aprende a fazer CRUD com o Prisma 🤓

Inserindo dados no banco de dados

Para fazer o INSERT com o Prisma, nós usamos o método create, logo após informar a chave prisma.seumodel, por exemplo:

const { PrismaClient } = require('@prisma/client');
const prisma = new PrismaClient();

async function main() {
  const novoUsuario = await prisma.usuario.create({
    data: {
      nome: 'João Silva',
      email: 'joao.silva@example.com',
      senha: 'senhaMuitoSegura',
      data_criacao: new Date('2023-09-21T10:00:00Z'),  // Definindo a data (é opcional, pois devido as configurações do banco, ele foi setado para adicionar automaticamente)
    },
  });

  console.log('Usuário criado:', novoUsuario);
}

main()
  .catch(e => {
    console.error(e);
    process.exit(1);
  })
  .finally(async () => {
    await prisma.$disconnect();
  });

No comando acima estamos executando um INSERT dentro da tabela usuarios, por meio do model Usuario.

Lembre-se que qualquer operação que você fizer com o Prisma, o ideal é que você importe o @prisma/client.

Não é necessário importar o model que será utilizado, pois o Prisma já o reconhece por de baixo dos panos.

Veja o retorno no console:

Selecionando dados no banco de dados

Existem varias formas de se fazer um SELECT com o Prisma, vejamos abaixo alguns exemplos de como isso pode ser feito 🤩

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

const { PrismaClient } = require('@prisma/client');
const prisma = new PrismaClient();

async function selectAll() {
  const usuarios = await prisma.usuario.findMany();
  console.log('Todos os usuários:', usuarios);
}

selectAll()
  .catch(e => console.error(e))
  .finally(async () => {
    await prisma.$disconnect();
  });

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

const { PrismaClient } = require('@prisma/client');
const prisma = new PrismaClient();

async function selectByEmail(email) {
  const usuario = await prisma.usuario.findMany({
    where: {
      email: email,
    },
  });
  console.log('Usuário encontrado:', usuario);
}

selectByEmail('email@example.com')  // Substitua pelo email desejado
  .catch(e => console.error(e))
  .finally(async () => {
    await prisma.$disconnect();
  });

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

const { PrismaClient } = require('@prisma/client');
const prisma = new PrismaClient();

async function selectNomeEmail() {
  const usuarios = await prisma.usuario.findMany({
    select: {
      nome: true,
      email: true,
    },
  });
  console.log('Usuários (Nome e Email):', usuarios);
}

selectNomeEmail()
  .catch(e => console.error(e))
  .finally(async () => {
    await prisma.$disconnect();
  });

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

const { PrismaClient } = require('@prisma/client');
const prisma = new PrismaClient();

async function selectWithLimit() {
  const usuarios = await prisma.usuario.findMany({
    take: 5,  // Limita o retorno a 5 usuários
  });
  console.log('Primeiros 5 usuários:', usuarios);
}

selectWithLimit()
  .catch(e => console.error(e))
  .finally(async () => {
    await prisma.$disconnect();
  });

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 { PrismaClient } = require('@prisma/client');
const prisma = new PrismaClient();

async function selectWithPagination(page = 1, perPage = 5) {
  const skip = (page - 1) * perPage;
  const usuarios = await prisma.usuario.findMany({
    skip: skip,  // Pula os registros anteriores
    take: perPage,  // Limita ao número de registros por página
  });
  console.log(`Página ${page}:`, usuarios);
}

selectWithPagination(1, 5)  // Substitua o número da página e limite por página
  .catch(e => console.error(e))
  .finally(async () => {
    await prisma.$disconnect();
  });

Supondo que você queria fazer uma busca por determinado id, você pode usar o método findUnique:

const { PrismaClient } = require('@prisma/client');
const prisma = new PrismaClient();

async function selectById(id) {
  const usuario = await prisma.usuario.findUnique({
    where: {
      id: id,  // O campo ID precisa ser passado aqui
    },
  });

  console.log('Usuário encontrado:', usuario);
}

selectById(1)  // Substitua pelo ID que deseja buscar
  .catch(e => console.error(e))
  .finally(async () => {
    await prisma.$disconnect();
  });

Fácil, não?

Implementando o JOIN com Prisma

No mundo do SQL, nós temos o comando JOIN, que é responsável por unir colunas em comum em tabelas diferentes, com o objetivo de retornar registros casados.

Com o Prisma, isso é possível graças ao comando relations que nós podemos adicionar ainda durante a criação do arquivo schema.prisma.

Vamos supor que nós ainda temos a tabela usuarios, mas que agora, temos uma nova tabela chamada de projetos, e que essa tabela projetos possua uma correção com o id da tabela usuarios.

Para isso, nós precisamos correlacionar isso no schema.prisma da seguinte forma:

// Conexão com o banco de dados
datasource db {
  provider = "mysql"
  url      = env("DATABASE_URL")
}

// Gerador do Prisma Client
generator client {
  provider = "prisma-client-js"
}

// Definição do modelo Usuario
model Usuario {
  id           Int       @id @default(autoincrement())
  nome         String    @db.VarChar(100)
  email        String    @db.VarChar(100)
  senha        String    @db.VarChar(255)
  data_criacao DateTime  @default(now())
  
  // Relacionamento com a tabela Projeto (Um usuário pode ter vários projetos)
  projetos     Projeto[]
}

// Definição do modelo Projeto
model Projeto {
  id           Int       @id @default(autoincrement())
  nome         String    @db.VarChar(255)
  descricao    String    @db.Text
  usuarioId    Int       // Chave estrangeira referenciando a tabela Usuario
  
  // Definição da relação com a tabela Usuario
  usuario      Usuario   @relation(fields: [usuarioId], references: [id])

  data_criacao DateTime  @default(now())
}

projetos Projeto[]: dentro do modelo Usuario, essa linha define que um usuário pode ter vários projetos (relação 1).

usuarioId Int: no modelo Projeto, esta é a chave estrangeira que aponta para a tabela Usuario.

@relation: define a relação entre usuarioId na tabela Projeto e id na tabela Usuario.

Depois de fazer essas alterações, não se esqueça de rodar a migração no seu terminal (Prompt de Comando):

npx prisma migrate dev --name add-projetos-relation

No comando acima estamos realizando a migração, e dando um nome (que servirá para o histórico) 'add-projetos-relation', indicando que houve uma atualização de correlação entre tabelas.

Por fim, basta executar seu SELECT junto com o parâmetro include informando o model que será utilizado:

const { PrismaClient } = require('@prisma/client');
const prisma = new PrismaClient();

async function selectUserWithProjects(userId) {
  const usuario = await prisma.usuario.findUnique({
    where: {
      id: userId,
    },
    include: {
      projetos: true,  // Inclui todos os projetos relacionados ao usuário
    },
  });

  console.log('Usuário e seus projetos:', usuario);
}

selectUserWithProjects(1)  // Substitua pelo ID do usuário
  .catch(e => console.error(e))
  .finally(async () => {
    await prisma.$disconnect();
  });

include: { projetos: true }: faz um "JOIN" entre Usuario e Projeto, retornando também todos os projetos relacionados ao usuário.

usuario.projetos: o array projetos conterá todos os registros da tabela Projeto associados ao usuario.

Atualizando dados do banco de dados

Para realizarmos um UPDATE usando o Prisma, nós fazemos o udo do método update para isso, vejamos alguns exemplos.

Para realizar um update usando apenas o id da tabela usuarios, podemos fazer isso da seguinte forma, usando o where:

const { PrismaClient } = require('@prisma/client');
const prisma = new PrismaClient();

async function updateById(id, novosDados) {
  const usuarioAtualizado = await prisma.usuario.update({
    where: {
      id: id,  // Atualiza o usuário com o ID específico
    },
    data: novosDados,  // Os novos dados que deseja atualizar
  });

  console.log('Usuário atualizado:', usuarioAtualizado);
}

updateById(1, { nome: 'João Atualizado', email: 'novoemail@example.com' })  // Substitua por ID e dados desejados
  .catch(e => console.error(e))
  .finally(async () => {
    await prisma.$disconnect();
  });

Note que estamos usando o parâmetro data para informar em uma estrutura de objetos, os dados que serão atualizados.

Para realizar um update informando mais duas colunas dentro do where, isso pode ser feito como descrito abaixo:

const { PrismaClient } = require('@prisma/client');
const prisma = new PrismaClient();

async function updateByIdAndEmail(id, email, novosDados) {
  const usuarioAtualizado = await prisma.usuario.updateMany({
    where: {
      id: id,      // Condição pelo ID
      email: email,  // Condição pelo email
    },
    data: novosDados,  // Novos dados para atualizar
  });

  console.log('Usuário atualizado:', usuarioAtualizado);
}

updateByIdAndEmail(1, 'joao.silva@example.com', { nome: 'João Silva Atualizado' })  // Substitua pelo ID, email e dados desejados
  .catch(e => console.error(e))
  .finally(async () => {
    await prisma.$disconnect();
  });

update: atualiza apenas um registro que corresponda à condição.

updateMany: atualiza todos os registros que correspondam às condições fornecidas.

Removendo dados do banco de dados

Para remover certos dados do seu banco, você pode usar o método delete em conjunto com o where da seguinte forma:

const { PrismaClient } = require('@prisma/client');
const prisma = new PrismaClient();

async function deleteById(id) {
  const usuarioDeletado = await prisma.usuario.delete({
    where: {
      id: id,  // Deleta o usuário com o ID específico
    },
  });

  console.log('Usuário deletado:', usuarioDeletado);
}

deleteById(1)  // Substitua pelo ID desejado
  .catch(e => console.error(e))
  .finally(async () => {
    await prisma.$disconnect();
  });

Caso desejar, você pode informar mais colunas dentro do where, obverse como fazer isso abaixo:

const { PrismaClient } = require('@prisma/client');
const prisma = new PrismaClient();

async function deleteByIdAndEmail(id, email) {
  const usuarioDeletado = await prisma.usuario.deleteMany({
    where: {
      id: id,      // Condição pelo ID
      email: email,  // Condição pelo email
    },
  });

  console.log('Usuário(s) deletado(s):', usuarioDeletado);
}

deleteByIdAndEmail(1, 'joao.silva@example.com')  // Substitua pelo ID e email desejados
  .catch(e => console.error(e))
  .finally(async () => {
    await prisma.$disconnect();
  });

Usando Operadores durante nossas consultas

Até o momento, você aprendeu a fazer alguns CRUDs na sua base de dados em conjunto com o comando where, onde sempre fizemos uma comparação direta, ou seja, sempre usamos o comparador de igualdade (=) dentro do where.

O Prisma possui suporte a diversos operadores, tais como:

Igualdade: { campo: valor } ou { campo: { equals: valor } }.

Não igual: { campo: { not: valor } }.

Maior que: { campo: { gt: valor } }.

Maior ou igual: { campo: { gte: valor } }.

Menor que: { campo: { lt: valor } }.

Menor ou igual: { campo: { lte: valor } }.

Entre (between): { campo: { gte: valor1, lte: valor2 } }.

In: { campo: { in: [valor1, valor2] } }.

Not In: { campo: { notIn: [valor1, valor2] } }.

Null: { campo: null }.

Not Null: { campo: { not: null } }.

Vejamos alguns exemplos de uso de cada um deles nos próximo tópicos 😉

Operador de Comparação: Maior que (>) ou Menor que (<)

Podemos usar os operadores de comparação, não de forma direta, mas usando alguns comandos fornecidos pelo Prisma, tais como:

  • gt: Greater than (maior que).
  • gte: Greater than or equal to (maior ou igual).
  • lt: Less than (menor que).
  • lte: Less than or equal to (menor ou igual).

Vejamos um exemplo de uma busca por um usuários que foram criados após uma data específica:

const { PrismaClient } = require('@prisma/client');
const prisma = new PrismaClient();

async function selectUsersCreatedAfter(date) {
  const usuarios = await prisma.usuario.findMany({
    where: {
      data_criacao: {
        gt: date,  // "gt" (greater than) é usado para "maior que"
      },
    },
  });

  console.log('Usuários criados depois de', date, ':', usuarios);
}

selectUsersCreatedAfter(new Date('2023-01-01'))
  .catch(e => console.error(e))
  .finally(async () => {
    await prisma.$disconnect();
  });

Operador de Negação (not)

Podemos usar o operador not que representa negação no Prisma, veja um exemplo:

async function selectUsersNotWithEmail(email) {
  const usuarios = await prisma.usuario.findMany({
    where: {
      email: {
        not: email,  // "not" para valores diferentes
      },
    },
  });

  console.log('Usuários com email diferente de', email, ':', usuarios);
}

selectUsersNotWithEmail('joao.silva@example.com')
  .catch(e => console.error(e))
  .finally(async () => {
    await prisma.$disconnect();
  });

Operador de Nulo

Podemos verificar se uma coluna possui valores nulos (null) apenas informando este tipo de dado de forma direta, veja um exemplo:

async function selectUsersWithNullPassword() {
  const usuarios = await prisma.usuario.findMany({
    where: {
      senha: null,  // Verifica valores "null"
    },
  });

  console.log('Usuários sem senha definida:', usuarios);
}

selectUsersWithNullPassword()
  .catch(e => console.error(e))
  .finally(async () => {
    await prisma.$disconnect();
  });

Operador Between

Podemos buscar usuários que foram criados entre duas datas, para isso podemos usar o comando gte e lte em conjunto, por exemplo:

async function selectUsersBetweenDates(startDate, endDate) {
  const usuarios = await prisma.usuario.findMany({
    where: {
      data_criacao: {
        gte: startDate,  // "greater than or equal to"
        lte: endDate,    // "less than or equal to"
      },
    },
  });

  console.log('Usuários criados entre', startDate, 'e', endDate, ':', usuarios);
}

selectUsersBetweenDates(new Date('2023-01-01'), new Date('2023-12-31'))
  .catch(e => console.error(e))
  .finally(async () => {
    await prisma.$disconnect();
  });

Operador in e NotIn

Caso desejar, você consegue buscar usuários cujo o id esteja dentro de uma lista específica, por exemplo:

async function selectUsersInIds(ids) {
  const usuarios = await prisma.usuario.findMany({
    where: {
      id: {
        in: ids,  // "in" para valores dentro da lista
      },
    },
  });

  console.log('Usuários com IDs', ids, ':', usuarios);
}

selectUsersInIds([1, 2, 3])
  .catch(e => console.error(e))
  .finally(async () => {
    await prisma.$disconnect();
  });

Combinações entre operadores

Além disso, com o Prisma também é possível combinar diversos operadores em apenas uma única consulta.

Por exemplo, buscar usuários com data de criação maior que uma data específica, e cujo e-mail não seja nulo.

async function selectUsersWithConditions(date) {
  const usuarios = await prisma.usuario.findMany({
    where: {
      data_criacao: {
        gt: date,  // Usuários criados após a data
      },
      email: {
        not: null,  // Com e-mail definido (não nulo)
      },
    },
  });

  console.log('Usuários criados depois de', date, 'e com e-mail:', usuarios);
}

selectUsersWithConditions(new Date('2023-01-01'))
  .catch(e => console.error(e))
  .finally(async () => {
    await prisma.$disconnect();
  });

Criando repositorios que se comunicam com nossas tabelas

É uma boa prática, criarmos repositorios que lidam diretamente com a tabela usuario, o que dê uma certa forma, abstrai certo nível de complexidade da nossa aplicação principal (index.js).

Para isso, você pode criar uma pasta chamada de repositories na pasta raiz do seu projeto:

Onde dentro dela, você armazene todas as suas classes responsáveis por lídar com as tabelas do seu banco de dados.

No nosso caso, vamos criar um arquivo chamado usuarioRepository.js dentro da pasta repositories. Fiz isso pois quero criar um arquivo que vai lidar diretamente (e especificamente) com a tabela de usuarios.

usuarioRepository.js:

const { PrismaClient } = require('@prisma/client');
const prisma = new PrismaClient();

class UsuarioRepository {
  async create(data) {
    return await prisma.usuario.create({
      data,
    });
  }

  async findAll() {
    return await prisma.usuario.findMany();
  }

  async findById(id) {
    return await prisma.usuario.findUnique({
      where: { id },
    });
  }

  async update(id, data) {
    return await prisma.usuario.update({
      where: { id },
      data,
    });
  }

  async delete(id) {
    return await prisma.usuario.delete({
      where: { id },
    });
  }

  async disconnect() {
    await prisma.$disconnect(); // Método para desconectar o Prisma
  }
}

module.exports = new UsuarioRepository();

A classe acima implementa as tarefas básicas de um CRUD.

Para usar esta classe, basta importá-la dentro do seu index.js e usá-la como se fosse qualquer outra classe do Javascript, por exemplo:

const usuarioRepository = require('./repositories/usuarioRepository');

// Função para criar um usuário
async function createUser() {
  const newUser = {
    nome: 'João Silva',
    email: 'joao.silva@example.com',
    senha: 'senhaSegura123',
    data_criacao: new Date(),
  };
  
  const createdUser = await usuarioRepository.create(newUser);
  console.log('Usuário criado:', createdUser);
}

// Função para buscar todos os usuários
async function getAllUsers() {
  const users = await usuarioRepository.findAll();
  console.log('Todos os usuários:', users);
}

// Função para buscar usuário por ID
async function getUserById(id) {
  const user = await usuarioRepository.findById(id);
  if (user) {
    console.log('Usuário encontrado:', user);
  } else {
    console.log('Usuário não encontrado.');
  }
}

// Função para atualizar um usuário por ID
async function updateUser(id) {
  const updatedData = {
    nome: 'João Atualizado',
    email: 'joao.atualizado@example.com',
  };

  const updatedUser = await usuarioRepository.update(id, updatedData);
  console.log('Usuário atualizado:', updatedUser);
}

// Função para deletar um usuário por ID
async function deleteUser(id) {
  const deletedUser = await usuarioRepository.delete(id);
  console.log('Usuário deletado:', deletedUser);
}

// Chamando as funções para testar
async function main() {
  await createUser();          // Cria um novo usuário
  await getAllUsers();         // Busca todos os usuários
  await getUserById(1);        // Busca um usuário pelo ID
  await updateUser(1);         // Atualiza um usuário pelo ID
  await deleteUser(1);         // Deleta um usuário pelo ID
}

main()
  .catch(e => console.error(e))
  .finally(async () => {
    // Encerra a conexão com o Prisma chamando o método disconnect do repositório
    await usuarioRepository.disconnect();
  });

Dessa forma, você abstrai parte da lógica da sua aplicação principal, e modulariza as classes responsáveis por se comunicar de forma direta com suas tabelas atráves do Prisma 😉

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 utilizar um dos ORMs mais robustos e peformáticos feitos para o NodeJS, o Prisma.

O conteúdo sobre Prisma 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.  

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.