JavaScript Avançado

Prisma ORM em Node.js: Modelagem, Migrations e Queries Tipadas na Prática

9 min de leitura

Prisma ORM em Node.js: Modelagem, Migrations e Queries Tipadas na Prática

Introdução ao Prisma ORM O Prisma é um ORM (Object-Relational Mapping) moderno para Node.js que resolve um dos maiores desafios da programação backend: gerenciar dados de forma segura e com type safety completo. Diferentemente de ORMs tradicionais como Sequelize ou TypeORM, o Prisma oferece uma abordagem schema-first, onde você define seu modelo de dados uma única vez e tudo mais é gerado automaticamente. Isso elimina redundância e reduz drasticamente bugs relacionados a tipos de dados. Neste artigo, você aprenderá a configurar Prisma, modelar seus dados, executar migrations e escrever queries type-safe. Vamos direto ao essencial, sem teorias desnecessárias. Configuração Inicial e Modelagem de Dados Instalação e Setup Comece criando um projeto Node.js e instalando Prisma: Isso cria um arquivo e um diretório com o arquivo . Configure a variável de ambiente com seu banco de dados: Definindo Modelos com Prisma Schema O Prisma Schema é onde tudo começa. Ele define sua estrutura de dados em uma sintaxe limpa e intuitiva.

<h2>Introdução ao Prisma ORM</h2>

<p>O Prisma é um ORM (Object-Relational Mapping) moderno para Node.js que resolve um dos maiores desafios da programação backend: gerenciar dados de forma segura e com type safety completo. Diferentemente de ORMs tradicionais como Sequelize ou TypeORM, o Prisma oferece uma abordagem schema-first, onde você define seu modelo de dados uma única vez e tudo mais é gerado automaticamente. Isso elimina redundância e reduz drasticamente bugs relacionados a tipos de dados.</p>

<p>Neste artigo, você aprenderá a configurar Prisma, modelar seus dados, executar migrations e escrever queries type-safe. Vamos direto ao essencial, sem teorias desnecessárias.</p>

<h2>Configuração Inicial e Modelagem de Dados</h2>

<h3>Instalação e Setup</h3>

<p>Comece criando um projeto Node.js e instalando Prisma:</p>

<pre><code class="language-bash">npm init -y

npm install @prisma/client

npm install -D prisma

npx prisma init</code></pre>

<p>Isso cria um arquivo <code>.env</code> e um diretório <code>prisma/</code> com o arquivo <code>schema.prisma</code>. Configure a variável de ambiente com seu banco de dados:</p>

<pre><code class="language-env">DATABASE_URL=&quot;postgresql://user:password@localhost:5432/meu_banco&quot;</code></pre>

<h3>Definindo Modelos com Prisma Schema</h3>

<p>O Prisma Schema é onde tudo começa. Ele define sua estrutura de dados em uma sintaxe limpa e intuitiva. Veja um exemplo real:</p>

<pre><code class="language-prisma">// prisma/schema.prisma

datasource db {

provider = &quot;postgresql&quot;

url = env(&quot;DATABASE_URL&quot;)

}

generator client {

provider = &quot;prisma-client-js&quot;

}

model User {

id Int @id @default(autoincrement())

email String @unique

name String

createdAt DateTime @default(now())

posts Post[] // Relação um-para-muitos

}

model Post {

id Int @id @default(autoincrement())

title String

content String

published Boolean @default(false)

authorId Int

author User @relation(fields: [authorId], references: [id])

comments Comment[]

}

model Comment {

id Int @id @default(autoincrement())

text String

postId Int

post Post @relation(fields: [postId], references: [id])

}</code></pre>

<p>Cada modelo mapeia diretamente para uma tabela no banco de dados. Os atributos <code>@id</code>, <code>@unique</code> e <code>@default</code> definem constraints e comportamentos padrão. As relações (como <code>posts Post[]</code>) permitem navegação bidirecional tipada entre entidades.</p>

<h2>Migrations: Sincronizando Banco e Schema</h2>

<h3>Criando e Executando Migrations</h3>

<p>Migrations são a forma como Prisma controla versões de seu banco de dados. Após modificar o schema, execute:</p>

<pre><code class="language-bash">npx prisma migrate dev --name nome_da_migracao</code></pre>

<p>Isso cria um arquivo SQL em <code>prisma/migrations/</code> e aplica no banco automaticamente. Por exemplo, adicionar um novo campo:</p>

<pre><code class="language-prisma">model User {

id Int @id @default(autoincrement())

email String @unique

name String

phone String? // Novo campo, opcional

createdAt DateTime @default(now())

posts Post[]

}</code></pre>

<p>Execute <code>npx prisma migrate dev --name add_phone_to_user</code> e o Prisma gera:</p>

<pre><code class="language-sql">ALTER TABLE &quot;User&quot; ADD COLUMN &quot;phone&quot; TEXT;</code></pre>

<p>Para sincronizar sem criar nova migração (desenvolvimento), use:</p>

<pre><code class="language-bash">npx prisma db push</code></pre>

<h3>Reset e Replicação</h3>

<p>Em desenvolvimento, você pode resetar o banco:</p>

<pre><code class="language-bash">npx prisma migrate reset</code></pre>

<p>Isso desfaz todas as migrations, recria o schema do zero e executa seeds. Para produção, sempre revise as migrations antes de executar.</p>

<h2>Queries Tipadas e Operações com Prisma Client</h2>

<h3>Operações CRUD Básicas</h3>

<p>O Prisma Client fornece métodos type-safe para queries. TypeScript infere os tipos automaticamente:</p>

<pre><code class="language-typescript">import { PrismaClient } from &#039;@prisma/client&#039;;

const prisma = new PrismaClient();

// CREATE

const newUser = await prisma.user.create({

data: {

email: &#039;joao@example.com&#039;,

name: &#039;João Silva&#039;

}

});

// READ

const user = await prisma.user.findUnique({

where: { email: &#039;joao@example.com&#039; },

include: { posts: true } // Carrega relacionamentos

});

// UPDATE

const updated = await prisma.user.update({

where: { id: 1 },

data: { name: &#039;João Updated&#039; }

});

// DELETE

await prisma.user.delete({

where: { id: 1 }

});

// LISTAR COM FILTROS

const recentPosts = await prisma.post.findMany({

where: {

published: true,

createdAt: {

gte: new Date(&#039;2024-01-01&#039;)

}

},

orderBy: { createdAt: &#039;desc&#039; },

take: 10

});</code></pre>

<h3>Queries Avançadas e Relações</h3>

<p>Prisma permite queries complexas com type safety total. Veja operações mais sofisticadas:</p>

<pre><code class="language-typescript">// Agregações

const totalPosts = await prisma.post.count({

where: { published: true }

});

// Nested writes (criar pai e filho simultaneamente)

const userWithPost = await prisma.user.create({

data: {

email: &#039;maria@example.com&#039;,

name: &#039;Maria&#039;,

posts: {

create: [

{ title: &#039;Primeiro Post&#039;, content: &#039;Conteúdo aqui&#039; },

{ title: &#039;Segundo Post&#039;, content: &#039;Mais conteúdo&#039; }

]

}

},

include: { posts: true }

});

// Transações para múltiplas operações atomicamente

await prisma.$transaction([

prisma.post.deleteMany({ where: { authorId: 5 } }),

prisma.user.delete({ where: { id: 5 } })

]);

// Raw queries quando necessário

const users = await prisma.$queryRaw`

SELECT * FROM &quot;User&quot; WHERE email LIKE ${%example%}

`;</code></pre>

<p>O IntelliSense do TypeScript autocompleta campos e relações, evitando erros em tempo de desenvolvimento. Se você escrever <code>prisma.user.findMany({ where: { invalidField: true } })</code>, TypeScript avisa imediatamente.</p>

<h2>Boas Práticas e Otimização</h2>

<h3>Gerenciamento de Conexões</h3>

<p>Crie uma instância única do Prisma Client e reutilize-a:</p>

<pre><code class="language-typescript">// lib/prisma.ts

export const prisma = new PrismaClient();

// Importar em qualquer arquivo

import { prisma } from &#039;./lib/prisma&#039;;</code></pre>

<p>Para aplicações serverless, considere usar pool de conexões:</p>

<pre><code class="language-env">DATABASE_URL=&quot;postgresql://user:pass@localhost/db?schema=public&quot;</code></pre>

<h3>Seleção Eficiente de Campos</h3>

<p>Use <code>select</code> para trazer apenas os dados necessários, reduzindo payload:</p>

<pre><code class="language-typescript">const users = await prisma.user.findMany({

select: {

id: true,

email: true,

name: true,

posts: {

select: { title: true }

}

}

});</code></pre>

<h3>Tratamento de Erros</h3>

<p>Prisma lança exceções tipadas. Trate-as apropriadamente:</p>

<pre><code class="language-typescript">import { Prisma } from &#039;@prisma/client&#039;;

try {

await prisma.user.create({

data: { email: &#039;duplicado@example.com&#039;, name: &#039;Teste&#039; }

});

} catch (error) {

if (error instanceof Prisma.PrismaClientKnownRequestError) {

if (error.code === &#039;P2002&#039;) {

console.log(&#039;Email já existe&#039;);

}

}

}</code></pre>

<h2>Conclusão</h2>

<p>Você aprendeu que <strong>Prisma oferece type safety automático</strong>, eliminando erros de tipos em queries — algo que ORMs antigos não fazem nativamente. Segundo, <strong>migrations são simples e versionadas</strong>, facilitando colaboração em equipe e deploy seguro. Terceiro, <strong>a sintaxe é intuitiva e produtiva</strong>, reduzindo boilerplate significativamente em relação a SQL puro ou ORMs concorrentes.</p>

<p>Prisma transforma a forma como você trabalha com dados, tornando o código mais confiável e mantível. Pratique criando um projeto pequeno e explore a documentação oficial para casos de uso avançados.</p>

<h2>Referências</h2>

<ul>

<li><a href="https://www.prisma.io/docs/" target="_blank" rel="noopener noreferrer">Prisma Documentation - Official Docs</a></li>

<li><a href="https://www.prisma.io/docs/reference/api-reference/prisma-schema-reference" target="_blank" rel="noopener noreferrer">Prisma Schema Reference</a></li>

<li><a href="https://www.prisma.io/docs/concepts/components/prisma-client" target="_blank" rel="noopener noreferrer">Prisma Client CRUD Operations</a></li>

<li><a href="https://www.prisma.io/docs/concepts/components/prisma-migrate" target="_blank" rel="noopener noreferrer">Database Migrations Guide</a></li>

<li><a href="https://www.prisma.io/docs/concepts/more/develop-the-prisma-orm" target="_blank" rel="noopener noreferrer">Prisma with TypeScript Best Practices</a></li>

</ul>

Comentários

Mais em JavaScript Avançado

Como Usar Monorepo com TypeScript: Turborepo, Paths e Shared Packages em Produção
Como Usar Monorepo com TypeScript: Turborepo, Paths e Shared Packages em Produção

O que é Monorepo e por que usar Turborepo? Um monorepo é um repositório único...

Design Patterns em JavaScript: Strategy, Decorator e Composite: Do Básico ao Avançado
Design Patterns em JavaScript: Strategy, Decorator e Composite: Do Básico ao Avançado

Strategy Pattern: Flexibilidade no Comportamento O Strategy Pattern encapsula...

Como Usar Arquitetura de Frontend: Flux, MVC, MVVM e Event-Driven Design em Produção
Como Usar Arquitetura de Frontend: Flux, MVC, MVVM e Event-Driven Design em Produção

MVC: O Fundamento Model-View-Controller é o padrão mais tradicional em arquit...