TypeScript com Prisma: inferência de tipos do banco
1. Introdução ao Prisma e sua integração com TypeScript
Prisma é um ORM moderno para Node.js e TypeScript que revoluciona a forma como desenvolvedores interagem com bancos de dados. Sua principal vantagem é a geração automática de tipos TypeScript a partir do schema do banco, eliminando a necessidade de escrever interfaces manualmente e reduzindo drasticamente erros em tempo de execução.
O fluxo de trabalho com Prisma segue três etapas fundamentais: definição do schema → execução de migrações → geração do cliente tipado. Esse ciclo garante que o código TypeScript esteja sempre sincronizado com a estrutura real do banco de dados.
2. Configuração inicial do Prisma em um projeto TypeScript
Para iniciar, instale as dependências necessárias:
npm install prisma @prisma/client
npm install -D ts-node @types/node
Crie o arquivo schema.prisma na raiz do projeto:
// schema.prisma
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
model User {
id Int @id @default(autoincrement())
email String @unique
name String?
posts Post[]
}
Gere o cliente Prisma:
npx prisma generate
Configure o tsconfig.json para suportar módulos modernos:
{
"compilerOptions": {
"target": "ES2020",
"module": "commonjs",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"outDir": "./dist"
},
"include": ["src/**/*"]
}
3. Modelagem de dados e tipos gerados automaticamente
Ao definir modelos no schema, o Prisma gera automaticamente interfaces TypeScript correspondentes. Por exemplo, para o modelo User, o Prisma cria:
// Gerado automaticamente pelo Prisma
interface User {
id: number;
email: string;
name: string | null;
posts: Post[];
}
Você pode modelar relacionamentos complexos:
model Post {
id Int @id @default(autoincrement())
title String
content String?
published Boolean @default(false)
author User @relation(fields: [authorId], references: [id])
authorId Int
comments Comment[]
}
model Comment {
id Int @id @default(autoincrement())
text String
post Post @relation(fields: [postId], references: [id])
postId Int
}
4. Inferência de tipos nas consultas CRUD
O Prisma infere tipos automaticamente em todas as operações CRUD:
import { PrismaClient } from '@prisma/client';
const prisma = new PrismaClient();
// Retorno totalmente tipado
const users = await prisma.user.findMany();
// users: User[]
const user = await prisma.user.findUnique({
where: { id: 1 }
});
// user: User | null
// Seleção de campos específicos
const userEmail = await prisma.user.findUnique({
where: { id: 1 },
select: { email: true, name: true }
});
// userEmail: { email: string; name: string | null } | null
Use Prisma.PromiseReturnType para tipar funções que encapsulam consultas:
import { Prisma } from '@prisma/client';
async function getUserWithPosts(userId: number) {
return prisma.user.findUnique({
where: { id: userId },
include: { posts: true }
});
}
type GetUserWithPostsResult = Prisma.PromiseReturnType<typeof getUserWithPosts>;
// GetUserWithPostsResult: User & { posts: Post[] } | null
5. Tipagem de filtros, ordenação e paginação
Todos os parâmetros de consulta são fortemente tipados:
// Filtros tipados
const activeUsers = await prisma.user.findMany({
where: {
email: { contains: "@example.com" },
posts: { some: { published: true } }
},
orderBy: { name: 'asc' },
take: 10,
skip: 0
});
// Tipos de filtros disponíveis
type UserWhereInput = Prisma.UserWhereInput;
type UserOrderByInput = Prisma.UserOrderByWithRelationInput;
// Paginação com cursor
const paginatedUsers = await prisma.user.findMany({
take: 10,
cursor: { id: lastUserId },
skip: 1, // Skip the cursor
orderBy: { id: 'asc' }
});
6. Relacionamentos e joins tipados
O Prisma gera tipos específicos para relações, garantindo segurança total:
// Incluir relações tipadas
const userWithRelations = await prisma.user.findUnique({
where: { id: 1 },
include: {
posts: {
include: {
comments: true
}
}
}
});
// userWithRelations: User & { posts: (Post & { comments: Comment[] })[] } | null
// Tratamento de null em relações opcionais
if (userWithRelations) {
const postTitles = userWithRelations.posts.map(post => post.title);
// postTitles: string[]
}
// Relações opcionais
const postWithOptionalAuthor = await prisma.post.findUnique({
where: { id: 1 },
include: { author: true }
});
// author pode ser null se não existir
7. Transações e operações em lote com tipos
Transações mantêm a tipagem completa:
// Transação tipada
const [user, post] = await prisma.$transaction([
prisma.user.create({
data: { email: "user@example.com", name: "John" }
}),
prisma.post.create({
data: { title: "First Post", authorId: 1 }
})
]);
// user: User, post: Post
// Operações em lote
const createdUsers = await prisma.user.createMany({
data: [
{ email: "user1@example.com", name: "User 1" },
{ email: "user2@example.com", name: "User 2" }
]
});
// createdUsers: { count: number }
const updatedPosts = await prisma.post.updateMany({
where: { published: false },
data: { published: true }
});
// updatedPosts: { count: number }
8. Boas práticas e dicas de manutenção
Para manter a tipagem segura e o código sustentável:
// Evitar any - usar tipos gerados
// ❌ Ruim
function processUser(user: any) {
return user.email;
}
// ✅ Bom
function processUser(user: Prisma.UserGetPayload<{}>) {
return user.email;
}
// Validação em runtime com Prisma.Validator
import { Prisma } from '@prisma/client';
const userData: Prisma.UserCreateInput = {
email: "test@example.com",
name: "Test User"
};
// Versionamento com migrações
// npx prisma migrate dev --name add_user_role
// Lidar com mudanças sem quebrar tipagem
// Sempre executar npx prisma generate após alterações no schema
Dicas importantes:
- Execute npx prisma generate sempre que modificar o schema
- Use Prisma.UserGetPayload para tipos parciais
- Mantenha as migrações versionadas no controle de versão
- Utilize Prisma.UserCreateInput e Prisma.UserUpdateInput para validação de dados
Referências
- Documentação oficial do Prisma com TypeScript — Guia completo sobre segurança de tipos com Prisma e TypeScript
- Prisma: Inferência de tipos automática — Como trabalhar com estruturas parciais de modelos
- TypeScript Deep Dive: Prisma — Tutorial avançado sobre integração TypeScript com Prisma
- Prisma Migrate: Gerenciamento de schema — Documentação oficial sobre migrações e versionamento
- Prisma Client CRUD operations — Exemplos práticos de operações CRUD com tipos inferidos