Prisma Architecture
┌──────────────┐
│ schema.prisma│
└──────┬───────┘
│
▼
┌──────────────┐
│ Migration │
│ Engine │
└──────┬───────┘
│
▼
┌──────────────┐
│ Database │
└──────┬───────┘
│
▼
┌──────────────┐
│ Prisma Client│
└──────────────┘
Prisma is:
- Schema Language
- Migration Engine
- Query Engine
- Type Generator
Prisma Setup
Initialize:
npx prisma initCreates:
prisma/
└─ schema.prisma
.envschema.prisma
This is Prisma’s DSL (Domain Specific Language).
Example:
model User {
id Int @id @default(autoincrement())
email String @unique
name String?
}Think:
SQL + TypeScript
combined into one fileDatasource
Prisma must know where your database lives.
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}This tells Prisma:
Database Type
+
Connection StringExample:
DATABASE_URL="postgresql://..."Generator
Generator tells Prisma what code to generate.
generator client {
provider = "prisma-client-js"
}After generation:
prisma.user.findMany()becomes available.
Models
Model = Database Table
model User {
id Int @id
}becomes:
CREATE TABLE users (
id INTEGER PRIMARY KEY
)Under The Hood
When Prisma sees:
model User {
id Int @id
}It stores an internal representation similar to:
{
"name": "User",
"fields": [
{
"name": "id",
"type": "Int",
"primary": true
}
]
}Prisma then uses this metadata to:
- Generate SQL
- Generate TS types
- Generate Query APIs
One schema → many outputs.
Model Fields
model User {
id Int
name String
age Int
}Field = Column
id INTEGER
name TEXT
age INTEGEROptional Fields
name String?Question mark means:
NULLEquivalent:
name TEXT NULLDefault Values
createdAt DateTime @default(now())SQL:
DEFAULT CURRENT_TIMESTAMPMigration Basics
After changing schema:
npx prisma migrate devWhat happens?
Step 1
Prisma reads schema.
model User {
id Int @id
}Step 2
Compares with previous schema.
Old Schema
vs
New SchemaThis is called a diff.
Step 3
Generates SQL.
CREATE TABLE User (
id INTEGER PRIMARY KEY
)Step 4
Stores migration.
prisma/migrations/Example:
202601010101_init/containing SQL files.
Prisma migrations are basically:
Schema Diff
→
SQL
→
Version HistoryPrisma Client
Generate:
npx prisma generateCreates:
const prisma = new PrismaClient()Think of Prisma Client as:
Typed Query BuilderInstead of:
SELECT *
FROM users
WHERE age > 18You write:
prisma.user.findMany({
where: {
age: {
gt: 18
}
}
})Under The Hood Of Client
You write:
prisma.user.findMany()Prisma converts this internally to a query representation.
Something conceptually like:
{
"model": "User",
"action": "findMany"
}Then Query Engine translates to:
SELECT * FROM usersDatabase returns rows.
Rows become JS objects.
Relationships
This is where Prisma becomes powerful.
One User
Many Posts
User
├─ Post
├─ Post
└─ PostSQL:
posts.user_idPrisma:
model User {
id Int @id @default(autoincrement())
posts Post[]
}
model Post {
id Int @id @default(autoincrement())
userId Int
user User @relation(
fields: [userId],
references: [id]
)
}Mental model:
posts Post[]means
One user has many posts
user Usermeans
Each post belongs to one user
Prisma uses these declarations to generate:
FOREIGN KEY(userId)
REFERENCES User(id)Attributes
Attributes begin with @
Example:
id Int @idMeaning:
This field is primary keyCommon ones:
@idPrimary key.
@uniqueUnique index.
@default()Default value.
@relation()Relationship.
Enums
Instead of:
status = "ACTIVE"or
status = "INACTIVE"You define:
enum UserStatus {
ACTIVE
INACTIVE
}Use:
status UserStatusGenerated TypeScript:
type UserStatus =
| "ACTIVE"
| "INACTIVE"Create
await prisma.user.create({
data: {
email: "a@test.com"
}
})Generated SQL:
INSERT INTO users(email)
VALUES('a@test.com')Conceptually.
Read
Single record:
await prisma.user.findUnique({
where: {
id: 1
}
})SQL:
SELECT *
FROM users
WHERE id = 1
LIMIT 1Many records:
await prisma.user.findMany()Filtering
await prisma.user.findMany({
where: {
age: {
gt: 18
}
}
})SQL:
WHERE age > 18Operators:
gt
gte
lt
lte
contains
startsWith
endsWithThese map directly to SQL operators.
Relationship Filtering
Example:
Users who have published posts.
await prisma.user.findMany({
where: {
posts: {
some: {
published: true
}
}
}
})Conceptually:
EXISTS(
SELECT *
FROM posts
WHERE posts.user_id = users.id
)Include
Load related data.
await prisma.user.findMany({
include: {
posts: true
}
})Returns:
[
{
id: 1,
posts: [...]
}
]Prisma internally generates additional joins or optimized queries depending on the situation.
Update
await prisma.user.update({
where: {
id: 1
},
data: {
name: "Raja"
}
})SQL:
UPDATE users
SET name = 'Raja'
WHERE id = 1Connect Existing Relationships
Suppose:
User exists.
Post exists.
Connect:
await prisma.post.update({
where: {
id: 1
},
data: {
user: {
connect: {
id: 10
}
}
}
})Internally:
UPDATE posts
SET user_id = 10
WHERE id = 1Delete
await prisma.user.delete({
where: {
id: 1
}
})SQL:
DELETE
FROM users
WHERE id = 1