TypeScript ORM

Prisma

Prisma é um ORM schema-first para TypeScript. Descreva seus modelos no schema.prisma, gere um cliente totalmente tipado e utilize migrations para manter o banco de dados sincronizado.

intermediate15 min readUpdated 16 de set. de 2026
prisma/schema.prisma
prisma
// prisma/schema.prisma
datasource db {
  provider = "postgresql"
  url      = env("DATABASE_URL")
}

generator client {
  provider = "prisma-client-js"
}

model User {
  id        String   @id @default(uuid())
  email     String   @unique
  name      String?
  posts     Post[]
  createdAt DateTime @default(now())
}

model Post {
  id        Int      @id @default(autoincrement())
  title     String
  published Boolean  @default(false)
  authorId  String
  author    User     @relation(fields: [authorId], references: [id], onDelete: Cascade)
  createdAt DateTime @default(now())

  @@index([authorId])
}
Lançado
2019
Versão principal
6.x
Escrito em
Cliente TypeScript, engine Rust opcional
Arquivo de schema
schema.prisma
Bancos de dados
PostgreSQL, MySQL, SQLite, SQL Server, MongoDB
Ferramenta de migration
Prisma Migrate

Por que importa

O que torna o Prisma diferente

Tipos derivados do schema

O comando prisma generate emite um cliente cujos métodos e tipos de retorno correspondem aos seus modelos, transformando um campo digitado incorretamente em um erro de compilação.

Um único schema declarativo

Datasource, generator e cada modelo residem em um único arquivo legível que pode ser consumido tanto por pessoas quanto por ferramentas.

Migrations no controle de versão

O Prisma Migrate transforma alterações de schema em arquivos SQL revisáveis que você commita junto com o código que as necessita.

O panorama completo

Três ideias que moldam o Prisma

Um schema declarativo, um cliente gerado e migrations que vivem no controle de versão.

O schema

Declarar

O schema.prisma descreve o datasource, o generator e cada modelo com seus campos, atributos e relações.

O cliente gerado

Tipar

O prisma generate produz o PrismaClient, uma API tipada onde cada modelo se torna uma propriedade e cada query retorna um formato conhecido.

O banco de dados

Armazenar

O Prisma traduz as chamadas do cliente em SQL e aplica as migrations, mas o banco de dados permanece como a fonte da verdade para os dados.

HTML5 de uma olhada

As peças que você realmente usará

Cliente gerado

Importe o PrismaClient e consulte modelos como métodos tipados.

Relações e include

Carregue registros relacionados com include, ou selecione campos específicos com select.

Prisma Migrate

O migrate dev escreve SQL; o migrate deploy o aplica em produção.

Transações

Arrays $transaction sequenciais ou callbacks interativos.

Prisma CLI

generate, migrate, db push, db seed e studio.

Pooling e Accelerate

Ajuste o connection_limit ou utilize um pooler gerenciado.

Modelo de dados

O que a primeira migration cria

Os modelos User e Post tornam-se duas tabelas PostgreSQL. O Prisma adiciona uma chave estrangeira para a relação e um índice único para o campo email.

O que a primeira migration criaTabelas PostgreSQL
  • User.idtextChave primária UUID gerada por @default(uuid())
  • User.emailtextNOT NULL com um índice único de @unique
  • User.nametextAnulável, pois o campo é declarado como String?
  • User.createdAttimestamptzNOT NULL, com padrão now() de @default(now())
  • Post.idserialChave primária inteira com auto-incremento
  • Post.authorIdtextChave estrangeira para User.id com ON DELETE CASCADE

Os modelos User e Post tornam-se duas tabelas PostgreSQL. O Prisma adiciona uma chave estrangeira para a relação e um índice único para o campo email.

Uma breve historia

De backend GraphQL a ORM mainstream

  1. 2016

    Graphcool e um backend GraphQL

    O projeto que se tornaria o Prisma começa como uma camada GraphQL hospedada sobre um banco de dados.

    16
  2. 2019

    Prisma 1

    O primeiro lançamento do ORM posiciona-se à frente do banco de dados e expõe uma API GraphQL para os clientes.

    19
  3. 2020

    Prisma 2 chega ao GA

    Uma reescrita torna o schema a fonte da verdade e introduz o Prisma Client gerado e type-safe.

    20
  4. 2021

    Prisma Migrate chega ao GA

    Migrations declarativas e seeding tornam-se partes prontas para produção do toolkit.

    21
  5. 2024

    Um cliente livre de Rust

    O gerador prisma-client e os driver adapters movem mais partes da stack para TypeScript.

    24

O guia completo

Prisma: Tudo que voce precisa saber

O que é o Prisma?

O Prisma é um ORM schema-first para TypeScript e Node.js. Você descreve seus dados apenas uma vez no schema.prisma, executa um gerador e obtém um client cujos métodos e tipos de retorno vêm diretamente desse schema. Não há decorators, nem classes de repositório e nem strings SQL escritas à mão para os casos comuns.

A proposta é que o schema do banco de dados se torne um único arquivo declarativo que humanos leem e ferramentas consomem. A partir dele, o Prisma produz um client tipado que você importa no seu código, migrações SQL que você pode revisar e commitar, e uma interface de studio para navegar pelos dados. O Prisma suporta PostgreSQL, MySQL, MariaDB, SQLite, SQL Server, CockroachDB e MongoDB, e o client gerado traduz cada chamada para SQL ou para o protocolo de rede do MongoDB antes de mapear as linhas de volta para objetos simples.

Se você já utilizou um ORM no estilo ActiveRecord, a mudança de mentalidade é que o Prisma é schema-first e client-generated, em vez de class-first e runtime-reflective. Essa única decisão explica a maioria de seus pontos fortes e a maioria de seus custos.

O schema é a fonte da verdade

Tudo começa no schema.prisma. Ele contém três tipos de blocos: um datasource (onde o banco de dados está), um generator (o que emitir) e um model por tabela.

datasource db {
  provider = "postgresql"
  url      = env("DATABASE_URL")
}

generator client {
  provider = "prisma-client-js"
}

model User {
  id        String   @id @default(uuid())
  email     String   @unique
  name      String?
  posts     Post[]
  createdAt DateTime @default(now())
}

Algumas convenções valem a pena ser internalizadas. Um campo que termina em ? é nullable, enquanto um tipo de lista como Post[] é uma relação em vez de uma coluna. Atributos começam com @ para campos e @@ para blocos. @id marca a chave primária, @unique cria um índice único, @default(...) fornece um valor, e @map e @@map renomeiam a coluna ou tabela subjacente quando ela não corresponde ao seu modelo.

O modelo acima mapeia para uma tabela User com as colunas id, email, name e createdAt, além de uma relação virtual posts que o Prisma resolve com um join ou uma segunda query.

Gerando e utilizando o client

O client do Prisma é código gerado. Após editar o schema, execute:

pnpm prisma generate

Isso lê o schema.prisma e grava um client em node_modules/.prisma/client, ou em uma pasta de sua escolha utilizando o gerador mais recente prisma-client. Em seguida, você o instancia uma vez e o reutiliza:

import { PrismaClient } from "@prisma/client";

const prisma = new PrismaClient();

const user = await prisma.user.findUnique({
  where: { email: "[email protected]" },
});

Como o client é gerado, tanto o prisma.user quanto a estrutura do user são conhecidos pelo type checker. Renomeie um campo no schema, gere novamente e todo uso desatualizado falhará na compilação. Esse loop de feedback é o principal motivo pelo qual as equipes adotam o Prisma.

Em ambientes serverless ou com hot-reloading, evite criar um novo client por requisição ou por reload. Em vez disso, anexe um ao objeto global:

import { PrismaClient } from "@prisma/client";

const globalForPrisma = globalThis as unknown as { prisma?: PrismaClient };

export const prisma = globalForPrisma.prisma ?? new PrismaClient();

if (process.env.NODE_ENV !== "production") globalForPrisma.prisma = prisma;

Lendo dados

O Prisma expõe um método para cada formato de leitura. findUnique busca uma única linha por um campo único, findFirst busca uma linha que corresponda a um filtro arbitrário e findMany retorna uma lista.

const post = await prisma.post.findUnique({ where: { id: 42 } });

const latest = await prisma.post.findFirst({
  where: { published: true },
  orderBy: { createdAt: "desc" },
});

const posts = await prisma.post.findMany({
  where: {
    published: true,
    title: { contains: "prisma", mode: "insensitive" },
    authorId: { in: [userId] },
  },
  orderBy: { createdAt: "desc" },
  take: 20,
  skip: 0,
});

Os operadores de filtro são nomeados em vez de simbólicos: equals, not, in, notIn, lt, lte, gt, gte, contains, startsWith, endsWith e mode. Combine-os com arrays AND, OR e NOT:

const posts = await prisma.post.findMany({
  where: {
    OR: [
      { title: { contains: "orm" } },
      { author: { email: { endsWith: "@example.com" } } },
    ],
    NOT: { published: false },
  },
});

take e skip implementam a paginação por offset. Para conjuntos de resultados grandes, prefira a paginação por cursor, que pagina a partir da última linha em vez de contar desde o início:

const page = await prisma.post.findMany({
  take: 20,
  skip: 1,
  cursor: { id: lastSeenId },
  orderBy: { id: "asc" },
});

findUnique não aceitará um where que não seja único; use findFirst para esses casos. As variantes findUniqueOrThrow e findFirstOrThrow rejeitam a requisição com um erro em vez de retornar null, o que remove a necessidade de uma verificação condicional quando a linha obrigatoriamente deve existir.

Gravando dados

As operações de criação, atualização e exclusão são igualmente tipadas. create, update, upsert e delete operam em uma única linha, enquanto createMany, updateMany e deleteMany operam em conjuntos.

const user = await prisma.user.create({
  data: { email: "[email protected]", name: "Ada" },
});

await prisma.user.update({
  where: { id: user.id },
  data: { name: "Ada Lovelace" },
});

await prisma.user.upsert({
  where: { email: "[email protected]" },
  update: { name: "Ada Lovelace" },
  create: { email: "[email protected]", name: "Ada" },
});

await prisma.user.delete({ where: { id: user.id } });

Para inserções em massa, createMany emite um único INSERT e é drasticamente mais rápido do que fazer um loop sobre create:

await prisma.post.createMany({
  data: [
    { title: "Hello", authorId: user.id },
    { title: "World", authorId: user.id },
  ],
  skipDuplicates: true,
});

A desvantagem é que createMany não consegue gravar relações aninhadas; ele serve apenas para linhas simples. updateMany e deleteMany aceitam os mesmos filtros que findMany, portanto, a ausência de um where realmente afeta todas as linhas.

Relações, include e select

As relações são declaradas em ambos os lados. User.posts é uma lista e Post.author é um valor único, vinculados por @relation(fields: [authorId], references: [id]) no lado proprietário.

Por padrão, o Prisma retorna apenas colunas escalares. Para carregar uma relação, você adiciona include:

const user = await prisma.user.findUnique({
  where: { id: userId },
  include: {
    posts: {
      where: { published: true },
      orderBy: { createdAt: "desc" },
      take: 10,
    },
  },
});

select é a ferramenta mais específica: ela escolhe exatamente quais campos retornar, tanto para o modelo quanto para relações aninhadas.

const users = await prisma.user.findMany({
  select: {
    id: true,
    email: true,
    posts: {
      select: { title: true },
      where: { published: true },
    },
    _count: { select: { posts: true } },
  },
});

Você não pode combinar select e include no mesmo nível, porque select já responde à pergunta sobre o que retornar. Utilize select quando um endpoint tiver um formato de resposta fixo, e include quando você realmente quiser o registro relacionado completo. Retornar linhas inteiras para depois filtrá-las em JavaScript desperdiça largura de banda e memória, portanto, select evita uma regressão comum.

Escritas aninhadas

Um dos recursos mais interessantes do Prisma é a capacidade de gravar um pai e seus filhos em uma única chamada. As operações aninhadas de create, connect, update e delete são todas executadas dentro de uma transação implícita.

const user = await prisma.user.create({
  data: {
    email: "[email protected]",
    posts: {
      create: [
        { title: "First post" },
        { title: "Second post", published: true },
      ],
    },
  },
  include: { posts: true },
});

Para conectar linhas existentes em vez de criá-las, utiliza-se connect, e para desconectar uma relação, utiliza-se disconnect:

await prisma.post.update({
  where: { id: postId },
  data: {
    author: { connect: { email: "[email protected]" } },
  },
});

As escritas aninhadas mantêm os dados relacionados consistentes sem que você precise abrir uma transação manualmente, que é exatamente o tipo de controle que um ORM deve assumir.

Migrations em desenvolvimento e produção

O Prisma Migrate transforma a diferença entre o seu schema e o seu banco de dados em arquivos SQL versionados em prisma/migrations.

pnpm prisma migrate dev --name add_posts
pnpm prisma migrate deploy
pnpm prisma migrate status

migrate dev é um comando de desenvolvimento. Ele compara o schema com o banco de dados, escreve uma nova migration, a aplica e regenera o client. Ele também utiliza um shadow database para detectar drift, portanto, a role de desenvolvimento precisa de permissão para criar e excluir bancos de dados. Se um drift for encontrado, ele poderá sugerir o reset do banco de dados, o que deleta os dados.

migrate deploy é o comando de produção. Ele aplica as migrations pendentes e nada mais: sem geração, sem shadow database e sem resets. Execute-o em seu pipeline de deploy antes que o novo código comece a receber tráfego.

Para protótipos descartáveis, prisma db push ignora completamente o histórico de migrations e força o banco de dados a corresponder ao schema:

pnpm prisma db push

db push é rápido e conveniente, mas não deixa rastro de auditoria e pode excluir colunas ou tabelas para ajustar o schema. Nunca o aponte para produção. Outros comandos úteis são prisma migrate reset para excluir, recriar e re-seedar um banco de dados de desenvolvimento, e prisma migrate diff para mostrar o que seria alterado sem aplicar as mudanças.

Seeding

Os dados de seed devem ficar em prisma/seed.ts. Registre-os em package.json para que o Prisma saiba como executá-los:

{
  "prisma": {
    "seed": "tsx prisma/seed.ts"
  }
}
import { PrismaClient } from "@prisma/client";

const prisma = new PrismaClient();

async function main() {
  await prisma.user.upsert({
    where: { email: "[email protected]" },
    update: {},
    create: {
      email: "[email protected]",
      name: "Ada",
      posts: { create: [{ title: "Welcome" }] },
    },
  });
}

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

Execute-os com pnpm prisma db seed. Como o seeding é executado após migrate dev e migrate reset, um banco de dados recém-criado nunca estará vazio, o que torna o onboarding e os testes previsíveis.

Transações

O Prisma possui duas APIs de transação. A forma sequencial recebe um array de operações e as executa em ordem:

const [debit, credit] = await prisma.$transaction([
  prisma.account.update({
    where: { id: 1 },
    data: { balance: { decrement: 5000 } },
  }),
  prisma.account.update({
    where: { id: 2 },
    data: { balance: { increment: 5000 } },
  }),
]);

A forma interativa fornece um cliente de transação e permite que você tome decisões com base nos resultados:

await prisma.$transaction(async (tx) => {
  const sender = await tx.account.findUniqueOrThrow({ where: { id: 1 } });
  if (sender.balance < 5000) throw new Error("insufficient_funds");

  await tx.account.update({
    where: { id: 1 },
    data: { balance: { decrement: 5000 } },
  });
  await tx.account.update({
    where: { id: 2 },
    data: { balance: { increment: 5000 } },
  });
});

Use tx para cada query dentro de uma transação interativa; usar o cliente prisma externo ignora a transação. Você pode ajustar o comportamento com maxWait, que controla quanto tempo esperar por uma conexão, e timeout, que limita a duração máxima da transação, além de definir o nível de isolamento com isolationLevel. Mantenha as transações curtas e nunca mantenha uma aberta durante uma chamada HTTP ou interação do usuário.

SQL puro quando você precisar

O query builder cobre a maioria das leituras, mas consultas de relatórios e recursos específicos do banco de dados às vezes exigem SQL. $queryRaw é um tagged template que parametriza os valores para você:

import { Prisma } from "@prisma/client";

const rows = await prisma.$queryRaw<
  { day: Date; revenue: bigint }[]
>(Prisma.sql`
  SELECT date_trunc('day', created_at) AS day,
         sum(total_cents)             AS revenue
  FROM orders
  WHERE status = 'paid'
  GROUP BY 1
  ORDER BY 1 DESC
`);

Use $queryRaw para SELECT e $executeRaw para escritas. Ambos aceitam tagged templates, que evitam SQL injection porque os valores interpolados tornam-se parâmetros vinculados (bound parameters). $queryRawUnsafe e $executeRawUnsafe existem para SQL genuinamente dinâmico e devem ser tratados como perigosos: nunca interpole entradas de usuário neles. Prisma.sql e Prisma.join permitem que você componha fragmentos mantendo a parametrização intacta.

Pooling, serverless e Accelerate

Cada instância do PrismaClient possui um pool de conexões. Em um servidor de longa duração, é exatamente isso que você deseja. Em uma plataforma serverless, cada instância de função cria seu próprio cliente e, consequentemente, seu próprio pool; assim, um pico de tráfego pode esgotar as max_connections do banco de dados em segundos.

A primeira alavanca é a connection string. Limite o pool e o tempo de espera:

DATABASE_URL="postgresql://user:pass@host:5432/shop?connection_limit=5&pool_timeout=10"

Em funções onde cada instância processa uma requisição por vez, connection_limit=1 geralmente é a escolha correta. Atrás do PgBouncer, adicione ?pgbouncer=true para que o Prisma pare de usar prepared statements, que o transaction pooling não consegue preservar. Se você não puder alterar os limites de conexão do banco de dados, o Prisma Accelerate atua na frente como um pooler e cache gerenciados; você envolve o cliente com a extensão dele e roteia as queries através de um endpoint global. O antigo Data Proxy resolvia o mesmo problema e foi substituído pelo Accelerate.

Independentemente da sua escolha, a regra é a mesma: reutilize um único cliente por processo e não permita que o número de instâncias da aplicação multiplique as conexões além do que o banco de dados suporta.

A questão do N+1

Um problema de N+1 ocorre quando você busca uma lista e, em seguida, executa uma consulta por linha para carregar uma relação. O Prisma evita a forma clássica disso porque include e select carregam as relações como parte da mesma chamada: para um findMany com uma relação, ele executa uma consulta para os pais e outra para os filhos, e não uma para cada pai.

Você mesmo reintroduz o problema ao criar um loop:

const users = await prisma.user.findMany();

for (const user of users) {
  // One query per user: this is the N+1.
  const posts = await prisma.post.findMany({ where: { authorId: user.id } });
}

Em vez disso, carregue a relação na consulta original:

const users = await prisma.user.findMany({
  include: { posts: true },
});

Para leituras profundamente aninhadas, a estratégia padrão do Prisma executa uma consulta por nível de relação. Quando uma única consulta com join é materialmente mais rápida, a opção relationLoadStrategy: "join" instrui o Prisma a usar um LEFT JOIN. Meça o desempenho antes de alterar, pois as duas estratégias trocam a quantidade de round trips pela duplicação de colunas do pai.

Boas práticas

  • Trate o schema.prisma como a fonte da verdade e altere-o através de migrations, nunca manualmente.
  • Instancie o PrismaClient apenas uma vez por processo e reutilize-o; proteja o hot reload com um global.
  • Use select para formatos de resposta fixos e include apenas quando precisar de registros completos.
  • Prefira paginação por cursor em vez de offsets grandes em skip.
  • Use createMany para inserts em massa e escritas aninhadas para linhas relacionadas.
  • Mantenha transações interativas curtas e sempre use o cliente tx dentro delas.
  • Utilize $queryRaw com tagged templates, nunca as variantes Unsafe, quando precisar de SQL.
  • Configure o connection_limit e, em ambientes serverless, use um pooler como Accelerate ou PgBouncer.
  • Faça commit das pastas de migration e execute migrate deploy no CI/CD em vez de db push.

Erros comuns

  • Executar prisma db push contra a produção e perder colunas.
  • Criar um novo PrismaClient por requisição e esgotar as conexões.
  • Esquecer o prisma generate após uma alteração de schema e depois se perguntar por que os tipos estão desatualizados.
  • Buscar linhas completas com include e filtrar os campos no JavaScript.
  • Usar o client externo dentro de uma transação interativa, o que sai silenciosamente da transação.
  • Ignorar o where no updateMany ou deleteMany e atualizar todas as linhas.
  • Assumir que findUnique aceita qualquer filtro; ele requer um campo único.
  • Interpolar entrada de usuário em $queryRawUnsafe.
  • Manter uma transação aberta durante a espera de uma chamada de API externa.

Próximos passos

O Prisma é uma das formas de interagir com um banco de dados relacional usando TypeScript. Compare-o com o Drizzle, que mantém o SQL visível e dispensa o cliente gerado, e com o TypeORM, a abordagem baseada em classes e decorators que surgiu antes de ambos. Por trás de cada um deles está o próprio banco de dados, portanto, o guia de PostgreSQL oferece um aprofundamento que vale a pena, independentemente do ORM. Se você ainda está escolhendo um runtime para o servidor que hospedará tudo isso, comece pelo Node.js.

Na pratica

Schema, query, escrita aninhada, transação

As quatro formas que você mais escreverá em um projeto Prisma.

prisma/schema.prisma
datasource db {
  provider = "postgresql"
  url      = env("DATABASE_URL")
}

generator client {
  provider = "prisma-client-js"
}

model User {
  id        String   @id @default(uuid())
  email     String   @unique
  name      String?
  posts     Post[]
  createdAt DateTime @default(now())
}

model Post {
  id        Int      @id @default(autoincrement())
  title     String
  published Boolean  @default(false)
  authorId  String
  author    User     @relation(fields: [authorId], references: [id], onDelete: Cascade)
  createdAt DateTime @default(now())

  @@index([authorId])
}

Buscando apenas o necessário

O select escolhe campos exatos para a resposta, enquanto o include carrega linhas relacionadas inteiras que você pode acabar descartando.

Prefira
const emails = await prisma.user.findMany({
  select: { email: true },
});
Evite
const users = await prisma.user.findMany({
  include: { posts: true },
});

// Most of each row is discarded in JavaScript.
const emails = users.map((u) => u.email);

Migrar versus prototipar

O migrate dev escreve SQL versionado que você pode revisar e implantar. O db push força o schema sem histórico e é destinado a bancos de dados descartáveis.

Prefira
pnpm prisma migrate dev --name add_posts
pnpm prisma migrate deploy
Evite
# No migration files, no audit trail,
# and columns can be dropped to fit.
pnpm prisma db push

Trade-offs

O Prisma deve ser o seu ORM?

O Prisma otimiza para segurança e produtividade. Essa troca vale a pena para a maioria dos códigos de aplicação, mas menos quando você precisa de controle total.

Strengths

  • Produtivo desde o primeiro modelo

    O schema, o cliente gerado e a ferramenta de migration compartilham o mesmo modelo mental, então há pouquíssimo código de integração para escrever.

  • Segurança imposta pelo compilador

    Os tipos são derivados do schema, o que detecta campos renomeados, formatos de argumentos incorretos e relações ausentes antes do runtime.

  • Migrations e seeding integrados

    Prisma Migrate, scripts de seed e Prisma Studio cobrem o trabalho diário com banco de dados sem a necessidade de bibliotecas extras.

Trade-offs

  • Menos controle sobre o SQL

    Você troca parte do controle de query pela abstração. Relatórios complexos geralmente exigem SQL puro, onde a segurança de tipos é menor.

  • Um cliente gerado é uma etapa de build

    Alterar o schema significa regenerar o cliente; esquecer de fazer isso no CI ou em um clone novo produz erros de tipo confusos.

  • Serverless exige atenção

    Um cliente por instância de função multiplica as conexões, portanto, implantações serverless precisam de limites de conexão ou um pooler como o Accelerate.

Perguntas frequentes

Perguntas frequentes

Keep learning

Related topics from the roadmap.

$ comecar a aprender

Pronto para aprender Prisma?

Nosso tutorial interativo te guia por Prisma passo a passo — com quizzes e codigo real que voce pode executar no navegador.