Node.js 45 🟢 Modern Object-Relational Mapping (ORM) with Prisma
Prisma is a next-generation ORM for Node.js and TypeScript that takes a different approach from the traditional ORMs. It is built around a declarative schema file, a generated type-safe client, and an integrated migration system. The schema is the single source of truth: it defines the data models, the relations, and the database connection, and the Prisma Client is generated from it with full TypeScript types . The result is a query API that is validated at compile time, with autocompletion and type inference for every query, including the nested relations.
The recent releases have changed the architecture significantly. Prisma 6.16 removed the Rust query engine and replaced it with a TypeScript-based query compiler, which reduced the bundle size by roughly 90% and made queries up to three times faster . Prisma 7 made the driver adapter mandatory for direct database connections and moved the generated client out of node_modules into the project source code . The current release, Prisma 8, is a ground-up TypeScript rewrite that adds a SQL query builder, TypeScript schema authoring, and extensibility through model collections and middleware .
This chapter covers three areas. First, why Prisma exists — the type-safety problem, the schema-first approach, and the difference from the traditional ORMs. Second, how the schema and the client work — the Prisma Schema Language, the models, the relations, the migration workflow, and the generated client. Third, the CRUD operations and the patterns — the create, findMany, update, delete, the nested writes, the transactions, and the error handling. The chapter ends with a complete example session, a quick reference, best practices, common pitfalls, real-world examples, and diagrams showing the schema and client flow.
Key point: Prisma is a schema-first ORM for Node.js and TypeScript. The Prisma schema defines the data models and the database connection, and the Prisma Client is generated from it with full type safety. The client provides a fluent query API for the CRUD operations, the nested writes, and the transactions. Prisma Migrate generates and applies the SQL migrations from the schema. The current release is Prisma 8, which includes a SQL query builder and TypeScript schema authoring .
Why Prisma exists
The type-safety problem. The traditional database tools force a trade-off between productivity and control. The raw SQL gives full control but lacks the type safety and is error-prone. The traditional ORMs improve the productivity but abstract too much, leading to the impedance mismatch and the performance pitfalls like the n+1 problem. Prisma takes a different approach: the queries are validated at compile time, and the result types are inferred from the schema. A typo in a column name is a compile error, not a runtime error .
The schema-first problem. In Prisma, the schema is the single source of truth. The data models, the relations, and the database connection are all defined in one file, and the client and the migrations are generated from it. The schema is the contract, and the code is derived from the contract. The alternative — the code-first approach where the schema is derived from the models — is the approach of TypeORM and Sequelize. The schema-first approach makes the data model explicit and the migrations deterministic .
The migration problem. Prisma Migrate generates the SQL migrations from the schema and applies them to the database. The workflow is: modify the schema, run prisma migrate dev to generate and apply the migration in development, and run prisma migrate deploy to apply the pending migrations in production. The migration history is stored in the prisma/migrations folder, and the _prisma_migrations table tracks the applied migrations . The migration files are the version control for the database schema, and they are committed with the application code.
The client-generation problem. The Prisma Client is generated from the schema, and it exposes a query API tailored to the models. The client is type-safe: the queries are validated at compile time, and the results are typed. The generation is a build step, and the client must be regenerated whenever the schema changes. In Prisma 7 and later, the generated client is placed in the project source code instead of node_modules, so the dev tools and the file watchers see it as part of the application .
The modern-architecture problem. Prisma 6.16 removed the Rust query engine and replaced it with a TypeScript query compiler. The Rust engine was fast, but it limited the contributions (the contributors needed the Rust experience) and the communication layer between the Rust and the JavaScript runtime was slow. The TypeScript compiler removes the binary dependency, reduces the bundle size, and improves the query performance . Prisma 7 made the driver adapter mandatory, which standardizes the database connectivity across the Node.js, the serverless, and the edge environments .
The trade-off. Prisma is a full ORM, and it abstracts the SQL. The complex queries may need the raw SQL escape hatch, and the generated SQL must be inspected for the performance tuning. The schema-first approach requires the schema to be the source of truth, which is a discipline. The trade-off is between the productivity and the type safety of the ORM and the directness and the control of the raw SQL. For the server-side applications with the stable data models, Prisma is a strong choice .
a. The Prisma schema
The Prisma schema is a file named schema.prisma that contains three parts: the data source, the generator, and the data model.
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
generator client {
provider = "prisma-client"
output = "../src/generated/prisma"
}
model User {
id Int @id @default(autoincrement())
email String @unique
name String?
role Role @default(USER)
posts Post[]
createdAt DateTime @default(now())
}
model Post {
id Int @id @default(autoincrement())
title String
published Boolean @default(false)
author User @relation(fields: [authorId], references: [id])
authorId Int
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
enum Role {
USER
ADMIN
}
The datasource block specifies the database provider and the connection URL. The generator block specifies the client generator and the output path. The data model is the collection of the models, and each model maps to a table in the relational database or a collection in MongoDB .
The models. A model is defined with the model keyword, and it has the fields. The @id attribute marks the primary key. The @default attribute provides the default value. The @unique attribute creates the unique index. The @updatedAt attribute updates the field on every write .
The relations. A relation connects two models. The @relation attribute specifies the foreign key and the referenced field. A one-to-many relation is defined by the list field on the one side (posts Post[]) and the relation field on the many side (author User @relation(...)). The fields argument specifies the foreign key column, and the references argument specifies the referenced column .
The scalar types. Prisma supports String, Int, BigInt, Float, Decimal, Boolean, DateTime, Bytes, Json, Jsonb, and the database-specific types. The Decimal type is stored as a string in the client to avoid the precision loss, and the Bytes type is a Uint8Array .
b. The Prisma Client
The Prisma Client is generated from the schema with prisma generate. The client is instantiated once and reused throughout the application.
import { PrismaClient } from './generated/prisma';
import { PrismaPg } from '@prisma/adapter-pg';
const adapter = new PrismaPg({ connectionString: process.env.DATABASE_URL });
const prisma = new PrismaClient({ adapter });
export default prisma;
In Prisma 7 and later, the driver adapter is mandatory for the direct database connections. The @prisma/adapter-pg package provides the PostgreSQL adapter, and the equivalent packages exist for the other databases . The client should be instantiated once per application, because each instance opens its own connection pool. In the development with the hot reload, the globalThis pattern prevents the duplicate instances .
const globalForPrisma = globalThis as unknown as { prisma?: PrismaClient };
export const prisma = globalForPrisma.prisma ?? createPrismaClient();
if (process.env.NODE_ENV !== 'production') globalForPrisma.prisma = prisma;
The CRUD operations. The client provides the methods for the create, the read, the update, and the delete.
// Create
const user = await prisma.user.create({
data: { email: 'alice@example.com', name: 'Alice' },
});
// Read all
const users = await prisma.user.findMany();
// Read by unique field
const user = await prisma.user.findUnique({ where: { email: 'alice@example.com' } });
// Read with a filter
const admins = await prisma.user.findMany({ where: { role: 'ADMIN' } });
// Update
const updated = await prisma.user.update({
where: { id: 1 },
data: { name: 'Alice Smith' },
});
// Delete
await prisma.user.delete({ where: { id: 1 } });
The create method inserts a row and returns the created record. The findMany returns the array of the records, and the findUnique returns a single record by the unique field. The update modifies the record, and the delete removes it .
The select and the include. The select argument limits the returned fields, and the include argument fetches the related records.
// Select specific fields
const users = await prisma.user.findMany({
select: { id: true, name: true, email: true },
});
// Include related records
const usersWithPosts = await prisma.user.findMany({
include: { posts: true },
});
The select is the performance optimization: it limits the columns that are returned. The include fetches the relations, and in Prisma 5 and later, it uses a single JOIN for the one-to-many relations .
The nested writes. The create and the update methods support the nested writes, which create or update the related records in the same query.
const user = await prisma.user.create({
data: {
email: 'alice@example.com',
name: 'Alice',
posts: {
create: [
{ title: 'First post' },
{ title: 'Second post' },
],
},
},
include: { posts: true },
});
The create inside the posts creates the related posts and links them to the user. The connect option links to an existing record, and the create option creates a new one .
c. The migrations and the lifecycle
The migration workflow. The migration workflow is: modify the schema, run prisma migrate dev to generate and apply the migration in development, and run prisma migrate deploy to apply the pending migrations in production .
# Development: generate and apply
npx prisma migrate dev --name add_user_role
# Production: apply the pending migrations
npx prisma migrate deploy
The migrate dev command generates the SQL migration file from the schema change, applies it to the development database, and updates the _prisma_migrations table. The migrate deploy command applies the pending migrations to the production database and does not reset the database or generate the artifacts .
The seeding. The seed script populates the database with the initial data. It is registered in the prisma.config.ts file and run with prisma db seed .
// prisma/seed.ts
import prisma from '../src/prisma';
async function main() {
await prisma.user.deleteMany();
await prisma.user.create({
data: { email: 'alice@example.com', name: 'Alice' },
});
}
main().catch((e) => console.error(e)).finally(() => prisma.$disconnect());
The transactions. The $transaction method runs a series of queries in a transaction. The interactive form takes a callback that receives the transaction client, and the queries inside the callback run on the same connection .
const result = await prisma.$transaction(async (tx) => {
const user = await tx.user.findUniqueOrThrow({ where: { id: 1 } });
if (user.role !== 'ADMIN') throw new Error('Forbidden');
return tx.post.create({ data: { title: 'New post', authorId: user.id } });
});
The transaction commits when the callback resolves, and it rolls back when the callback throws. The tx client must be used inside the callback, not the outer prisma client .
The error handling. The Prisma errors are the instances of PrismaClientKnownRequestError, PrismaClientValidationError, and the others. The known request error has a code that identifies the error type.
import { Prisma } from './generated/prisma';
try {
await prisma.user.create({ data: { email: 'alice@example.com' } });
} catch (e) {
if (e instanceof Prisma.PrismaClientKnownRequestError) {
if (e.code === 'P2002') throw new ConflictError('Email already exists');
if (e.code === 'P2025') throw new NotFoundError('Record not found');
}
throw e;
}
The P2002 code is the unique constraint violation, and the P2025 is the record not found. The error handling is the pattern for the API responses .
The soft delete. The soft delete is the pattern where the records are marked as deleted instead of being removed. The queries must filter the deleted records explicitly.
const activeUsers = await prisma.user.findMany({ where: { deletedAt: null } });
await prisma.user.update({ where: { id: 1 }, data: { deletedAt: new Date() } });
The explicit filter is the recommended approach over the middleware, because the middleware hides the behavior and is hard to debug .
Complete Example Session
// ============================================
// PART 1: THE SCHEMA
// ============================================
// prisma/schema.prisma
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
generator client {
provider = "prisma-client"
output = "../src/generated/prisma"
}
model User {
id Int @id @default(autoincrement())
email String @unique
name String?
role Role @default(USER)
posts Post[]
createdAt DateTime @default(now())
}
model Post {
id Int @id @default(autoincrement())
title String
published Boolean @default(false)
author User @relation(fields: [authorId], references: [id])
authorId Int
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
enum Role {
USER
ADMIN
}
// ============================================
// PART 2: THE CLIENT
// ============================================
// src/prisma.js
import { PrismaClient } from './generated/prisma';
import { PrismaPg } from '@prisma/adapter-pg';
const adapter = new PrismaPg({ connectionString: process.env.DATABASE_URL });
const prisma = new PrismaClient({ adapter });
export default prisma;
// ============================================
// PART 3: THE CREATE
// ============================================
async function createUser() {
const user = await prisma.user.create({
data: { email: 'alice@example.com', name: 'Alice' },
});
console.log(user.id); // the generated id
return user;
}
// ============================================
// PART 4: THE READ
// ============================================
async function findUsers() {
// All users
const all = await prisma.user.findMany();
// By unique field
const byEmail = await prisma.user.findUnique({
where: { email: 'alice@example.com' },
});
// With a filter
const admins = await prisma.user.findMany({ where: { role: 'ADMIN' } });
// With the selected fields
const names = await prisma.user.findMany({
select: { id: true, name: true },
});
// With the related records
const withPosts = await prisma.user.findMany({
include: { posts: true },
});
return { all, byEmail, admins, names, withPosts };
}
// ============================================
// PART 5: THE NESTED WRITE
// ============================================
async function createUserWithPosts() {
const user = await prisma.user.create({
data: {
email: 'bob@example.com',
name: 'Bob',
posts: {
create: [
{ title: 'First post' },
{ title: 'Second post' },
],
},
},
include: { posts: true },
});
return user;
}
// ============================================
// PART 6: THE UPDATE AND THE DELETE
// ============================================
async function updateUser(id, data) {
return prisma.user.update({ where: { id }, data });
}
async function deleteUser(id) {
return prisma.user.delete({ where: { id } });
}
// ============================================
// PART 7: THE TRANSACTION
// ============================================
async function createPostForAdmin(userId, title) {
return prisma.$transaction(async (tx) => {
const user = await tx.user.findUniqueOrThrow({ where: { id: userId } });
if (user.role !== 'ADMIN') throw new Error('Forbidden');
return tx.post.create({ data: { title, authorId: userId } });
});
}
// ============================================
// PART 8: THE ERROR HANDLING
// ============================================
async function safeCreateUser(email, name) {
try {
return await prisma.user.create({ data: { email, name } });
} catch (e) {
if (e instanceof Prisma.PrismaClientKnownRequestError) {
if (e.code === 'P2002') throw new Error('Email already exists');
}
throw e;
}
}
// ============================================
// PART 9: THE CURSOR PAGINATION
// ============================================
async function getPosts(cursor, limit = 20) {
const items = await prisma.post.findMany({
where: { published: true },
orderBy: [{ createdAt: 'desc' }, { id: 'desc' }],
take: limit + 1,
...(cursor && { cursor: { id: cursor }, skip: 1 }),
});
const hasNextPage = items.length > limit;
if (hasNextPage) items.pop();
return {
items,
nextCursor: hasNextPage ? items[items.length - 1].id : null,
};
}
// ============================================
// PART 10: THE COMPLETE APPLICATION
// ============================================
import express from 'express';
const app = express();
app.use(express.json());
app.get('/users', async (req, res) => {
const users = await prisma.user.findMany({
select: { id: true, email: true, name: true, role: true },
});
res.json(users);
});
app.get('/users/:id', async (req, res) => {
const user = await prisma.user.findUnique({
where: { id: parseInt(req.params.id, 10) },
});
if (!user) return res.status(404).json({ error: 'User not found' });
res.json(user);
});
app.post('/users', async (req, res) => {
try {
const user = await prisma.user.create({ data: req.body });
res.status(201).json(user);
} catch (e) {
if (e instanceof Prisma.PrismaClientKnownRequestError && e.code === 'P2002') {
return res.status(409).json({ error: 'Email already exists' });
}
res.status(500).json({ error: 'Internal server error' });
}
});
app.patch('/users/:id', async (req, res) => {
const user = await prisma.user.update({
where: { id: parseInt(req.params.id, 10) },
data: req.body,
});
res.json(user);
});
app.delete('/users/:id', async (req, res) => {
await prisma.user.delete({ where: { id: parseInt(req.params.id, 10) } });
res.status(204).send();
});
app.listen(3000);
The ten parts show the schema, the client, the create, the read, the nested write, the update and delete, the transaction, the error handling, the cursor pagination, and the complete application.
Quick Reference
The Schema Blocks
| Block | Purpose |
|---|---|
datasource | The database provider and the URL |
generator | The client generator and the output |
model | The data models |
enum | The enumerated types |
The Model Attributes
| Attribute | Purpose |
|---|---|
@id | The primary key |
@default | The default value |
@unique | The unique index |
@relation | The foreign key relation |
@updatedAt | The update timestamp |
@map | The database column name |
@@map | The database table name |
The CRUD Methods
| Method | Purpose |
|---|---|
create({ data }) | Insert a record |
findMany({ where, select, include }) | Query multiple |
findUnique({ where }) | Query by unique field |
findFirst({ where }) | Query the first match |
update({ where, data }) | Modify a record |
delete({ where }) | Remove a record |
upsert({ where, create, update }) | Update or create |
The Migration Commands
| Command | Purpose |
|---|---|
prisma migrate dev | Generate and apply in development |
prisma migrate deploy | Apply the pending in production |
prisma migrate reset | Drop, create, migrate, seed |
prisma db seed | Run the seed script |
prisma generate | Regenerate the client |
prisma studio | Open the data browser |
The Error Codes
| Code | Meaning |
|---|---|
P2002 | The unique constraint violation |
P2025 | The record not found |
P2003 | The foreign key constraint violation |
Best Practices
✅ Do This:
// Use the singleton pattern for the client
const prisma = globalForPrisma.prisma ?? new PrismaClient(); // ✅
// Use the select for the performance
prisma.user.findMany({ select: { id: true, name: true } }); // ✅
// Use the include for the relations
prisma.user.findMany({ include: { posts: true } }); // ✅
// Use the transactions for the multi-step operations
prisma.$transaction(async (tx) => { ... }); // ✅
// Use the cursor pagination for the large datasets
prisma.post.findMany({ take: limit + 1, cursor: { id: cursor }, skip: 1 }); // ✅
// Handle the P2002 errors
if (e.code === 'P2002') throw new ConflictError(); // ✅
// Use the schema-first workflow
// Modify the schema and run prisma migrate dev // ✅
❌ Don’t Do This:
// Don't create a new PrismaClient per request
const prisma = new PrismaClient(); // per request // ❌
// Don't use the outer client in a transaction
prisma.$transaction(async (tx) => { await prisma.user.create(...); }); // ❌
// Don't use the offset pagination for the feeds
prisma.post.findMany({ skip: page * 20, take: 20 }); // unstable // ❌
// Don't forget the error handling
await prisma.user.create({ data }); // no try/catch // ❌
// Don't use the raw SQL without the Prisma.sql
prisma.$queryRawUnsafe(`SELECT * FROM users WHERE id = ${id}`); // ❌
// Don't forget to regenerate the client after the schema change
// prisma generate // ❌
Common Pitfalls
| Pitfall | Why It Happens | Fix |
|---|---|---|
| Connection exhaustion | A new client per request | Use the singleton |
| The n+1 problem | The relations loaded in a loop | Use include |
| The unique constraint error | No P2002 handling | Add the error handling |
The transaction does not use tx | The outer client is used | Use the tx client |
| The generated client is stale | No prisma generate | Regenerate |
| The migration drift | A manual schema change | Use migrate dev |
| The unstable pagination | The offset pagination | Use the cursor pagination |
Real-World Examples
1. The Client Singleton
const globalForPrisma = globalThis;
export const prisma = globalForPrisma.prisma ?? new PrismaClient();
2. The Create
const user = await prisma.user.create({ data: { email: 'alice@example.com' } });
3. The Find Many
const users = await prisma.user.findMany({ where: { role: 'ADMIN' } });
4. The Find Unique
const user = await prisma.user.findUnique({ where: { email: 'alice@example.com' } });
5. The Select
const names = await prisma.user.findMany({ select: { id: true, name: true } });
6. The Include
const withPosts = await prisma.user.findMany({ include: { posts: true } });
7. The Nested Write
prisma.user.create({ data: { email, posts: { create: [{ title }] } } });
8. The Transaction
prisma.$transaction(async (tx) => { ... });
9. The Cursor Pagination
prisma.post.findMany({ take: 21, cursor: { id: cursor }, skip: 1 });
10. The Error Handling
if (e.code === 'P2002') throw new ConflictError();
Visual
The Schema and Client Flow
┌──────────────────────────────────────────────────────────────┐
│ THE SCHEMA AND CLIENT FLOW │
│ │
│ prisma/schema.prisma │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ datasource db { ... } │ │
│ │ generator client { ... } │ │
│ │ model User { ... } │ │
│ │ model Post { ... } │ │
│ └──────────────────────────────────────────────────────┘ │
│ │ │
│ │ prisma generate │
│ ▼ │
│ src/generated/prisma/ │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ The type-safe client │ │
│ │ ├── user.create() │ │
│ │ ├── user.findMany() │ │
│ │ ├── user.findUnique() │ │
│ │ ├── user.update() │ │
│ │ └── user.delete() │ │
│ └──────────────────────────────────────────────────────┘ │
│ │ │
│ │ prisma migrate dev │
│ ▼ │
│ prisma/migrations/ │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ 20240101_init/migration.sql │ │
│ │ 20240102_add_role/migration.sql │ │
│ └──────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ The database │
│ │
└──────────────────────────────────────────────────────────────┘
The CRUD Operations
┌──────────────────────────────────────────────────────────────┐
│ THE CRUD OPERATIONS │
│ │
│ CREATE: │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ prisma.user.create({ data: { email, name } }) │ │
│ │ prisma.user.createMany({ data: [...] }) │ │
│ └──────────────────────────────────────────────────────┘ │
│ │
│ READ: │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ prisma.user.findMany({ where, select, include }) │ │
│ │ prisma.user.findUnique({ where: { email } }) │ │
│ │ prisma.user.findFirst({ where }) │ │
│ └──────────────────────────────────────────────────────┘ │
│ │
│ UPDATE: │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ prisma.user.update({ where: { id }, data }) │ │
│ │ prisma.user.updateMany({ where, data }) │ │
│ └──────────────────────────────────────────────────────┘ │
│ │
│ DELETE: │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ prisma.user.delete({ where: { id } }) │ │
│ │ prisma.user.deleteMany({ where }) │ │
│ └──────────────────────────────────────────────────────┘ │
│ │
└──────────────────────────────────────────────────────────────┘
The Transaction
┌──────────────────────────────────────────────────────────────┐
│ THE TRANSACTION │
│ │
│ const result = await prisma.$transaction(async (tx) => { │
│ │ │
│ │ The tx client is scoped to the transaction. │
│ │ │
│ ├── const user = await tx.user.findUniqueOrThrow(...) │
│ │ │
│ ├── if (user.role !== 'ADMIN') throw new Error() │
│ │ │
│ └── return tx.post.create({ data: { ... } }) │
│ }); │
│ │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ The callback resolves → COMMIT │ │
│ │ The callback throws → ROLLBACK │ │
│ └──────────────────────────────────────────────────────┘ │
│ │
│ The tx client MUST be used inside the callback. │
│ The outer prisma client bypasses the transaction. │
│ │
└──────────────────────────────────────────────────────────────┘
The Cursor Pagination
┌──────────────────────────────────────────────────────────────┐
│ THE CURSOR PAGINATION │
│ │
│ const items = await prisma.post.findMany({ │
│ where: { published: true }, │
│ orderBy: [{ createdAt: 'desc' }, { id: 'desc' }], │
│ take: limit + 1, │
│ ...(cursor && { cursor: { id: cursor }, skip: 1 }), │
│ }); │
│ │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ 1. Fetch limit + 1 items. │ │
│ │ 2. If items.length > limit, there is a next page. │ │
│ │ 3. Pop the extra item. │ │
│ │ 4. Return the next cursor (the last item's id). │ │
│ └──────────────────────────────────────────────────────┘ │
│ │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ The id is the secondary sort to prevent the │ │
│ │ unstable pagination when the timestamps are equal. │ │
│ └──────────────────────────────────────────────────────┘ │
│ │
└──────────────────────────────────────────────────────────────┘
Summary
| Item | Value |
|---|---|
| Prisma | A schema-first ORM for Node.js and TypeScript |
| Prisma schema | The data source, the generator, and the data model |
| Prisma Client | The generated type-safe query API |
| Prisma Migrate | The migration system |
| Prisma Studio | The data browser |
| Current release | Prisma 8 |
| The driver adapter | Mandatory in Prisma 7+ |
| The client singleton | The pattern for the connection pool |
| The nested writes | The create and connect inside the create/update |
The $transaction | The transaction wrapper |
| The error codes | P2002, P2025, P2003 |
Key takeaways:
- Prisma is a schema-first ORM for Node.js and TypeScript. The schema defines the data models and the database connection, and the client and the migrations are generated from it. The queries are validated at compile time, and the result types are inferred from the schema .
- The Prisma schema is the single source of truth. It contains the data source, the generator, and the data model. The models map to the tables, and the relations connect them. The
@id,@default,@unique, and@relationattributes define the structure . - The Prisma Client is generated from the schema. The client is type-safe, and the query API is tailored to the models. The client should be instantiated once per application, and the
globalThispattern prevents the duplicate instances in the development . - Prisma 6.16 removed the Rust query engine. The TypeScript query compiler replaced it, which reduced the bundle size by 90% and made the queries up to three times faster. Prisma 7 made the driver adapter mandatory and moved the generated client out of
node_modules. - The CRUD operations are the
create,findMany,findUnique,update, anddelete. Theselectlimits the returned fields, and theincludefetches the relations. The nested writes create and link the related records in the same query . - The migration workflow is
migrate devin development andmigrate deployin production. Themigrate devgenerates and applies the migration, and themigrate deployapplies the pending migrations. The migration history is in theprisma/migrationsfolder . - The transactions are scoped to the
$transactioncallback. Thetxclient must be used inside the callback, and the outerprismaclient bypasses the transaction. The transaction commits when the callback resolves and rolls back when it throws . - The error handling uses the
PrismaClientKnownRequestError. TheP2002code is the unique constraint violation, theP2025is the record not found, and theP2003is the foreign key violation .
Remember: Prisma is the schema-first ORM that brings the type safety and the productivity to the database access in Node.js and TypeScript. The schema is the single source of truth, and the client and the migrations are generated from it. The recent releases removed the Rust engine, made the driver adapter mandatory, and moved the generated client into the project source. The CRUD operations are type-safe, the nested writes create and link the related records, and the transactions scope the multi-step operations. The cursor pagination is the pattern for the large datasets, and the error handling uses the known request codes. The result is a data access layer that is type-safe, productive, and modern.
Stop using slow, ad-bloated tool sites! 🤮
🔎 Search “KandZ Tools” on Google to use many professional utilities for free.
KandZ.me is the ultimate minimalist hub for:
✅ Finance (Mortgage, Interest, Inflation)
✅ Tech (Base64, JSON, Dev Suite, IP)
✅ Health (BMI, BMR, TDEE)
✅ Productivity (Timer, Workspace, QR)
⚡️ Fast & Private
🔒 No data leaves your device
💎 100% Free
🔗 Use it now: https://tools.kandz.me
🔖 Bookmark it—you’ll need it later!