The Complete Prisma Guide | ORM, Schema, Migration, Queries, Type Safety, Hands-on
What this post covers
This is a complete guide to type-safe database work with Prisma. It walks through schema definition, migrations, CRUD, relations, and Prisma Studio with practical examples.
From the field: Migrating from TypeORM to Prisma, we gained stronger type safety and cut time spent writing queries by about 50%.
Introduction: “ORMs feel too complex”
Real-world scenarios
Scenario 1: Weak type safety
You keep hitting runtime errors. Prisma gives you end-to-end strong typing. Scenario 2: Migrations are painful
You end up doing manual steps. Prisma supports automated migrations from your schema. Scenario 3: Queries get unwieldy
You fall back to raw SQL. Prisma offers a straightforward API.
1. What is Prisma?
Core characteristics
Prisma is a next-generation ORM. Key benefits:
- Type safety: first-class TypeScript support
- Automatic migrations: driven by your schema
- Prisma Studio: GUI for browsing and editing data
- Intuitive API: simpler query authoring
- Performance: optimized query execution
2. Installation and setup
Install
npm install prisma @prisma/client
npx prisma init
.env
DATABASE_URL="postgresql://user:password@localhost:5432/mydb"
Schema definition
Below is a detailed Prisma-based setup. Read through the code while noting what each part does.
// prisma/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[]
profile Profile?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
model Post {
id Int @id @default(autoincrement())
title String
content String?
published Boolean @default(false)
author User @relation(fields: [authorId], references: [id])
authorId Int
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
model Profile {
id Int @id @default(autoincrement())
bio String?
user User @relation(fields: [userId], references: [id])
userId Int @unique
}
Migration
npx prisma migrate dev --name init
npx prisma generate
3. CRUD operations
Create
import { PrismaClient } from '@prisma/client';
const prisma = new PrismaClient();
// Create a single record
const user = await prisma.user.create({
data: {
email: '[email protected]',
name: 'John',
},
});
// Create with nested relations
const userWithPost = await prisma.user.create({
data: {
email: '[email protected]',
name: 'Jane',
posts: {
create: [
{ title: 'First Post', content: 'Hello World' },
{ title: 'Second Post', content: 'Another Post' },
],
},
},
include: {
posts: true,
},
});
Read
// Single record by id
const user = await prisma.user.findUnique({
where: { id: 1 },
});
// Multiple records
const users = await prisma.user.findMany({
where: {
email: {
contains: '@example.com',
},
},
orderBy: {
createdAt: 'desc',
},
take: 10,
});
// With relations
const userWithPosts = await prisma.user.findUnique({
where: { id: 1 },
include: {
posts: {
where: { published: true },
},
profile: true,
},
});
Update
// Update one record
const updatedUser = await prisma.user.update({
where: { id: 1 },
data: {
name: 'John Updated',
},
});
// Update many records
const result = await prisma.user.updateMany({
where: {
email: {
contains: '@example.com',
},
},
data: {
name: 'Updated',
},
});
Delete
The example below uses TypeScript with async/await for efficient I/O. Review each part to see what it does.
// Delete one
const deletedUser = await prisma.user.delete({
where: { id: 1 },
});
// Delete many
const result = await prisma.user.deleteMany({
where: {
email: {
contains: '@test.com',
},
},
});
4. Advanced queries
Aggregation
This TypeScript example uses async/await. Run it yourself to see the behavior.
const result = await prisma.post.aggregate({
_count: { id: true },
_avg: { authorId: true },
_sum: { authorId: true },
_min: { createdAt: true },
_max: { createdAt: true },
});
Group By
This TypeScript example uses async/await. Read through the code while noting the role of each part.
const result = await prisma.post.groupBy({
by: ['authorId'],
_count: {
id: true,
},
having: {
id: {
_count: {
gt: 5,
},
},
},
});
Transaction
const [user, post] = await prisma.$transaction([
prisma.user.create({
data: { email: '[email protected]', name: 'John' },
}),
prisma.post.create({
data: { title: 'First Post', authorId: 1 },
}),
]);
// Interactive transaction
await prisma.$transaction(async (tx) => {
const user = await tx.user.create({
data: { email: '[email protected]', name: 'Jane' },
});
await tx.post.create({
data: { title: 'Post', authorId: user.id },
});
});
5. Prisma Studio
npx prisma studio
Features:
- Browse and edit data
- GUI interface
- Visualize relations
- Quick manual testing
6. Performance optimization
Connection pool
The example below configures PrismaClient in TypeScript with logging enabled so queries, warnings, and errors surface clearly—helpful for debugging and safer operation in development.
const prisma = new PrismaClient({
datasources: {
db: {
url: process.env.DATABASE_URL,
},
},
log: ['query', 'info', 'warn', 'error'],
});
Select optimization
This TypeScript example uses async/await. Run it to verify behavior.
// Fetch only the fields you need
const users = await prisma.user.findMany({
select: {
id: true,
email: true,
},
});
Job search and interviews
Topics like schema design, migrations, and avoiding N+1 queries are common in backend interviews. Pair the database and API question flow in Tech Interview Preparation Guide with the JD-matching section in Developer Resume & Interview Guide to explain how you used Prisma in projects.
Summary and checklist
Key takeaways
- Prisma: a next-generation ORM
- Type safety: strong TypeScript support
- Schema: declarative models
- Migration: generated from schema changes
- Prisma Studio: GUI management
- Performance: optimized queries
Implementation checklist
- Install Prisma
- Define the schema
- Run migrations
- Implement CRUD
- Configure relations
- Add transactions
- Tune performance
Related reading
- The Complete NestJS Guide
- Next.js App Router Guide
- The Complete TypeScript Guide
Keywords in this post
Prisma, ORM, TypeScript, Database, PostgreSQL, MySQL, Backend
Frequently asked questions (FAQ)
Q. How does Prisma compare to TypeORM?
A. Prisma generally offers stronger type safety and a better developer experience. TypeORM exposes more features but tends to be more complex.
Q. Does Prisma support MongoDB?
A. Yes, MongoDB is supported. For relational workloads, PostgreSQL or MySQL are still the most common recommendations.
Q. Can I use raw SQL?
A. Yes. Use prisma.$queryRaw (and related APIs) when you need raw SQL.
Q. Is Prisma production-ready?
A. Yes. Many companies run it reliably in production.