Guia Completo do Prisma Schema: Bancos de Dados, Modelos, Mapeamento e Mais

Posted by

O Prisma Schema é o núcleo da configuração ao trabalhar com o Prisma ORM , uma das ferramentas mais poderosas e modernas para manipulação de bancos de dados em aplicações Node.js e TypeScript. Ele vai muito além de simplesmente mapear tabelas e campos — é através do schema que definimos modelos, tipos escalares, relacionamentos, restrições de unicidade, índices, enums, políticas de exclusão e atualização em cascata, além de customizações profundas como mapeamento de nomes entre código e banco de dados.

Este guia completo foi criado para você entender desde os conceitos fundamentais até as funcionalidades avançadas do Prisma Schema. Seja você um desenvolvedor iniciante ou experiente, este conteúdo aborda todos os aspectos essenciais para modelar sua base de dados com clareza, eficiência e alinhada às melhores práticas.

O que é o Prisma Schema

O Prisma Schema é um arquivo de configuração escrito em uma linguagem específica chamada Prisma Schema Language. Ele define:

  • A conexão com o banco de dados
  • Os modelos (tabelas)
  • As relações entre os modelos
  • Atributos de campos e mapeamentos específicos

Esse arquivo é responsável por manter a coerência entre sua aplicação e o banco de dados subjacente.

Bancos de Dados Suportados pelo Prisma

O Prisma atualmente suporta os seguintes bancos de dados:

  • PostgreSQL
  • MySQL
  • SQLite
  • SQL Server
  • MongoDB (somente para NoSQL)

Cada banco tem suas particularidades, mas o Prisma abstrai essas diferenças, permitindo que você escreva código agnóstico ao tipo de banco usado.

Exemplo de bloco datasource no schema:

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

Diferenças entre Bancos de Dados no Prisma

O Prisma suporta múltiplos bancos de dados, incluindo PostgreSQL , MySQL , SQLite , SQL Server e MongoDB. Cada banco tem particularidades:

  • Mapeamento de tipos : O Prisma usa conectores para mapear tipos escalares (como String, Int) para tipos nativos do banco. Por exemplo, String pode se tornar VARCHAR no MySQL ou TEXT no PostgreSQL.
  • Recursos específicos : Bancos como PostgreSQL permitem extensões (ex.: uuid-ossp), que podem ser declaradas no schema via @@schema.

E muitas outras devemos consultar a documentação em caso de apresentar problemas.

Como Definir Modelos no Prisma

Modelos representam entidades ou tabelas no banco de dados. Cada modelo é criado com a palavra-chave model e contém campos com tipos e atributos específicos.

Exemplo:

model User {
  id        Int      @id @default(autoincrement())
  email     String   @unique
  name      String?
  posts     Post[]
}

Campos podem ter atributos como:

  • @id: define a chave primária
  • @default: valor padrão
  • @unique: garante unicidade
  • @updatedAt: registra data de atualização

Mapeando Modelos e Campos com @@map e @map no Prisma Schema

No Prisma, por padrão, o nome de um modelo é diretamente convertido no nome da tabela ou collection no banco de dados. Da mesma forma, os nomes dos campos são usados como nomes das colunas. Porém, nem sempre esses nomes coincidem com as convenções ou estruturas já existentes na base de dados.

Para lidar com essa situação de forma prática e declarativa, o Prisma oferece dois atributos poderosos: @@map e @map.

O que é @@map?

O atributo @@map é usado para alterar o nome da tabela ou collection no banco de dados, sem afetar o nome do modelo no schema do Prisma. Isso é útil quando você está trabalhando com bases legadas ou precisa seguir uma nomenclatura específica no banco.

Exemplo:

model User {
  id   Int    @id
  name String

  @@map("usuarios")
}

Neste caso, o modelo User será mapeado para a tabela chamada usuarios no banco de dados. O nome do modelo permanece igual no código, mas a tabela física recebe o nome desejado.

E o que é @map?

Enquanto @@map atua no nível do modelo, o atributo @map é aplicado nos campos e serve para alterar o nome da coluna correspondente no banco de dados.

Exemplo:

model User {
  id        Int     @id @map("user_id")
  firstName String  @map("nome")
  lastName  String  @map("sobrenome")

  @@map("usuarios")
}

Aqui, cada campo do modelo foi mapeado para uma coluna com um nome diferente:

  • iduser_id
  • firstNamenome
  • lastNamesobrenome
  • E o modelo inteiro aponta para a tabela usuarios

Essa abordagem permite alinhar perfeitamente o schema do Prisma com a estrutura física do banco, mesmo que haja diferenças de idioma, estilo ou convenção. Esse recurso é especialmente valioso em projetos que exigem integração com bancos de dados já estruturados ou que seguem padrões específicos de nomenclatura. Ao usar @@map e @map, você mantém o código organizado e legível no backend, enquanto respeita a estrutura existente no banco de dados.

Criando Índices com @@index

Índices melhoram a performance de consultas em colunas frequentemente usadas em buscas. No Prisma, você pode criar índices usando o atributo @@index.

Exemplo:

model Product {
  id       Int    @id
  name     String
  category String
  @@index([category])
}

Este exemplo cria um índice na coluna category, acelerando as consultas filtradas por categoria.

Garantindo Valores Únicos com @unique e Restrições Compostas no Prisma Schema

No Prisma Schema, garantir a unicidade de valores em um ou mais campos é essencial para manter a integridade dos dados. Para isso, o Prisma oferece o atributo @unique, que pode ser aplicado a campos individuais ou usados em conjunto para criar restrições compostas.

Usando @unique em Campos Individuais

O uso mais comum do @unique é na definição de campos que devem conter valores únicos em toda a tabela, como e-mails, CPFs ou códigos personalizados:

model User {
  id    Int     @id @default(autoincrement())
  email String  @unique
  name  String
}

Neste exemplo, o campo email não poderá ter valores duplicados no banco de dados. Essa regra é aplicada pelo Prisma como uma restrição UNIQUE no nível do banco de dados.

Criando Restrições Únicas Compostas com @@unique

Em alguns casos, a unicidade deve ser verificada em combinações de campos, como por exemplo, evitar que um mesmo usuário possa se inscrever mais de uma vez no mesmo curso. Para isso, utilizamos a diretiva @@unique:

model Enrollment {
  id        Int     @id @default(autoincrement())
  userId    Int
  courseId  Int
  user      User    @relation(fields: [userId], references: [id])
  course    Course  @relation(fields: [courseId], references: [id])

  @@unique([userId, courseId])
}

Este modelo garante que a combinação entre userId e courseId seja única, evitando registros duplicados nessa relação.

Diferenças entre Bancos de Dados

Embora o conceito de unicidade seja padrão, sua implementação pode variar entre bancos:

  • PostgreSQL/MySQL: Ambos suportam índices UNIQUE nativos.
  • SQLite: Também suporta unicidade, mas com algumas limitações em tipos de dados grandes.
  • MongoDB (Prisma com provedor relacional): A unicidade é simulada via índice único no documento relacionado.

É importante lembrar que campos com @unique podem aceitar valores NULL dependendo da configuração do banco, embora o comportamento exato varie — PostgreSQL permite múltiplos NULLs, enquanto outros sistemas podem tratá-los de forma diferente.

Usando Enum no Prisma

Enums são usados para restringir os valores possíveis de um campo. Eles ajudam a garantir a integridade dos dados.

Definição de enum:

enum Role {
  ADMIN
  USER
  GUEST
}

Uso no modelo:

model User {
  id   Int   @id
  role Role
}

No banco de dados, o Prisma geralmente armazena enums como texto, embora alguns bancos como PostgreSQL ofereçam suporte nativo a tipos enum.

Diferença entre Decimal e Float no Prisma

No Prisma, os tipos Decimal e Float representam números com casas decimais, mas possuem diferenças importantes que impactam diretamente a integridade dos dados:

  • Decimal: Representa números de precisão exata, sendo ideal para valores monetários ou qualquer dado onde erros de arredondamento não podem ocorrer. No banco de dados, o tipo Decimal é mapeado como DECIMAL(65,30) no PostgreSQL e MySQL. Exemplo:
  model Product {
    id    Int     @id
    price Decimal
  }
  • Float: Representa números de ponto flutuante, com precisão aproximada. É mais eficiente em termos de armazenamento e processamento, sendo indicado para cálculos científicos ou situações onde pequenas imprecisões são aceitáveis. Mapeia para DOUBLE PRECISION no PostgreSQL ou FLOAT no MySQL. Exemplo:
  model Measurement {
    id    Int   @id
    value Float
  }

A escolha entre esses dois tipos depende da necessidade de precisão absoluta (use Decimal) ou desempenho computacional (use Float).

Tipos de Campos Disponíveis no Prisma

O Prisma suporta uma variedade de tipos escalares, que são automaticamente mapeados para os tipos nativos do banco de dados utilizado. Alguns dos principais tipos são:

  • Int: Valores inteiros, usado para chaves primárias ou contadores.
  • String: Texto variável, mapeado como VARCHAR ou TEXT, dependendo do tamanho esperado.
  • Boolean: Representa verdadeiro ou falso.
  • DateTime: Armazena datas e horas, geralmente mapeadas como TIMESTAMP ou DATETIME.
  • Json: Permite armazenar objetos JSON, útil para dados flexíveis ou semi-estruturados.
  • Bytes: Usado para armazenar dados binários, como imagens ou arquivos.

Exemplo de modelo usando múltiplos tipos:

model User {
  id         Int      @id @default(autoincrement())
  name       String
  email      String   @unique
  isActive   Boolean
  createdAt  DateTime @default(now())
  metadata   Json?
}

Esses tipos oferecem flexibilidade para lidar com diferentes cenários de negócio e garantem a consistência dos dados na camada de aplicação.

Como Implementar UUID no Prisma

UUIDs (Universally Unique Identifiers) são identificadores únicos que evitam colisões e melhoram a segurança ao substituir IDs sequenciais. O uso de UUIDs é especialmente útil em sistemas distribuídos ou APIs públicas.

Definição no Schema

Para usar UUIDs no Prisma, basta declarar o campo como String e configurar um valor padrão com a função uuid():

model Order {
  id        String   @id @default(uuid())
  userId    String
  createdAt DateTime @default(now())
}

No PostgreSQL, o Prisma reconhece a função uuid() e gera automaticamente um UUID v4. Já no MySQL, essa função não está disponível nativamente, então é necessário implementar via triggers ou bibliotecas externas como uuid.js.

Mapeamento de UUID no PostgreSQL

O PostgreSQL possui um tipo nativo para armazenar UUIDs: o UUID. Isso permite que os valores sejam validados diretamente no banco e otimiza o armazenamento e desempenho ao trabalhar com esse tipo de dado.

No Prisma, você pode declarar um campo como UUID da seguinte maneira:

model User {
  id   String   @id @default(uuid()) @db.Uuid
  name String
}

A diretiva @db.Uuid indica ao Prisma que deve mapear esse campo para o tipo UUID nativo do PostgreSQL. Assim, o valor será armazenado de forma mais eficiente e segura.

Além disso, o PostgreSQL suporta extensões como uuid-ossp, que permitem gerar UUIDs de diferentes versões (v1, v4, etc.) diretamente no banco, caso necessário.

Mapeamento de UUID no MySQL

Diferentemente do PostgreSQL, o MySQL não possui um tipo de dado específico chamado UUID. Nesse caso, os UUIDs são geralmente armazenados como strings no formato textual com 36 caracteres (CHAR(36) ou VARCHAR(36)), seguindo o padrão como 550e8400-e29b-41d4-a716-446655440000.

Exemplo de definição no schema do Prisma:

model Order {
  id        String   @id @default(uuid())
  createdAt DateTime @default(now())
}

Por padrão, o Prisma mapeará esse campo como CHAR(36) no MySQL. Para deixar explícito, você pode usar a anotação @db.Char(36):

model Order {
  id        String   @id @default(uuid()) @db.Char(36)
  createdAt DateTime @default(now())
}

É importante destacar que o MySQL não tem funções nativas tão robustas quanto as do PostgreSQL para geração e manipulação de UUIDs. Por isso, recomenda-se gerar esses valores na aplicação ou utilizar bibliotecas como uuid.js ou nanoid.

Considerações sobre performance

Embora UUIDs tragam vantagens como segurança e distribuição, seu uso como chave primária em tabelas grandes pode impactar negativamente o desempenho no MySQL, especialmente por causa da aleatoriedade dos valores e consequente fragmentação de índices.

O que são onDelete e onUpdate no Prisma?

Os atributos onDelete e onUpdate são usados em relacionamentos para definir o que acontece com os registros filhos quando um registro pai é excluído ou atualizado. Eles são configurados dentro do atributo @relation e mapeiam diretamente para as ações ON DELETE e ON UPDATE do SQL.

Ações Disponíveis

As ações suportadas pelo Prisma são:

  • Cascade: Exclui ou atualiza automaticamente os registros filhos quando o registro pai é alterado.
  • SetNull: Define os campos estrangeiros como NULL quando o registro pai é excluído ou atualizado.
  • Restrict: Impede a exclusão ou atualização do registro pai se existirem registros filhos.
  • NoAction: Comporta-se como Restrict (padrão em alguns bancos de dados).

Exemplo de uso:


model User {
id Int @id @default(autoincrement())
name String
posts Post[] @relation(onDelete: Cascade, onUpdate: NoAction)
}
model Post {
id Int @id @default(autoincrement())
title String
userId Int
user User @relation(fields: [userId], references: [id])
}

Neste caso, se um User for excluído, todos os seus posts também serão excluídos (Cascade). Se o id do User for atualizado, a ação NoAction impedirá a alteração se houver posts associados.

Relacionamentos entre Modelos no Prisma

Relacionamentos são definidos com o atributo @relation, que vincula campos de dois modelos. O Prisma suporta relacionamentos one-to-one , one-to-many e many-to-many , além de relações auto-referenciais (self-relations) e compostas.

1. Relacionamento One-to-Many

O exemplo acima ilustra um relacionamento one-to-many entre User e Post. O campo userId em Post referencia o id de User.

2. Relacionamento One-to-One

model User {
id Int @id
profile Profile? @relation
}
model Profile {
id Int @id
bio String
userId Int @unique
user User @relation(fields: [userId], references: [id])
}

Aqui, cada User pode ter um Profile único, e vice-versa.

3. Relacionamento Many-to-Many

Para relacionamentos muitos-para-muitos, o Prisma cria uma tabela intermediária automaticamente. Essa tabela contém duas chaves estrangeiras que vinculam os registros das tabelas relacionadas.

Exemplo:

model User {
id Int @id
courses Course[] @relation(name: "UserToCourse")
}
model Course {
id Int @id
users User[] @relation(name: "UserToCourse")
}

Quando aplicado, o Prisma gera uma tabela intermediária com a seguinte estrutura:

model _CourseToUser {
A Int
B Int
course Course @relation(resolvedFrom: "A")
user User @relation(resolvedFrom: "B")

@@id([A, B])
}

Essa tabela _CourseToUser armazena pares de IDs que conectam User e Course, garantindo a integridade do relacionamento.

4. Self-Relations (Relações Auto-Referenciais)

Relações auto-referenciais ocorrem quando um modelo se relaciona consigo mesmo, como em hierarquias:

model Employee {
id Int @id
name String
managerId Int?
manager Employee? @relation("EmployeeToManager", fields: [managerId], references: [id], onDelete: NoAction, onUpdate: NoAction)
subordinates Employee[] @relation("EmployeeToManager")
}

Em bancos de dados como MySQL, ao usar relationMode: "prisma", é obrigatório definir onDelete: NoAction e onUpdate: NoAction em self-relations para evitar erros de validação.

Diferenças entre Bancos de Dados

O comportamento de onDelete e onUpdate pode variar conforme o banco de dados:

  • PostgreSQL/MySQL: Suportam ações nativas como CASCADE e SET NULL.
  • SQL Server: Requer que relações auto-referenciais usem NoAction.
  • MongoDB: Como é um banco NoSQL, o Prisma emula essas ações na camada do ORM.

Além disso, ao realizar introspecção, o Prisma compara as ações definidas no banco com as do schema e as ajusta automaticamente se forem diferentes.

Erros Comuns e Boas Práticas

1. Erro: “A self-relation must have onDelete and onUpdate…”

Este erro ocorre no MySQL quando relationMode está definido como "prisma" e uma self-relation não tem onDelete: NoAction e onUpdate: NoAction. A solução é explicitar essas ações no schema.

2. Ações Incompatíveis com o Banco

Certifique-se de que o banco de dados suporte as ações definidas. Por exemplo, SetNull requer que o campo estrangeiro seja opcional (Int?) e o banco permita valores nulos.

3. Teste Antes de Migrar

Use o comando prisma migrate dev para aplicar mudanças e verificar conflitos antes de implantar em produção.

Conclusão

Trabalhar com o Prisma Schema significa ter controle total sobre a camada de persistência de dados, sem abrir mão da simplicidade e produtividade oferecidas por um ORM moderno. Com ele, você consegue modelar sua base de dados de forma declarativa, garantindo consistência, segurança e flexibilidade ao longo do ciclo de vida do projeto.

Este guia apresentou os principais recursos do schema do Prisma, desde os fundamentos até os casos mais complexos, como relacionamentos compostos, uso de UUIDs, políticas de exclusão em cascata e diferenças entre bancos de dados como PostgreSQL, MySQL e MongoDB.

Independentemente de você estar começando um novo projeto ou migrando uma base existente, entender e dominar o Prisma Schema é essencial para desenvolver aplicações escaláveis, bem arquitetadas e fáceis de manter.

Agora que você tem todas as ferramentas e conhecimentos necessários, está pronto para aplicar essas técnicas no seu próximo projeto com Prisma e tirar proveito máximo dessa poderosa ferramenta de modelagem de dados.

One comment

Leave a Reply

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *