Technology handbook
Prisma
Schema modelling, generated client, relations, migrations, transactions and production operation.
- Topics
- 24
- Sections
- 4
Schema & modelling
Datasource and generator
Declare the database connection and the client the schema generates.
- One block names the database, another names the client to generate.
- The generated client is a build artifact, so it must be regenerated after a schema edit.
- The connection URL is read at generation and at runtime, which are different moments.
Models and fields
Map a model to a table with typed, explicitly nullable fields.
- A model maps to a table, and every field states its type and nullability.
- Optional in the schema is nullable in the database, not merely absent from a write.
- Renaming a field renames the column unless a mapping keeps the old name.
Attributes and defaults
Set primary keys, defaults, unique constraints and database-level mappings.
- Keys, defaults, unique constraints and column mappings are declared as attributes.
- A default declared here is applied by the database, not by the client.
- A mapping is what lets a friendly field name sit over an existing column name.
Relations
Express one-to-one, one-to-many and many-to-many with explicit relation fields.
- One-to-one, one-to-many and many-to-many are expressed with explicit relation fields.
- The scalar field holds the key; the relation field is how the client traverses it.
- An implicit many-to-many creates a join table you never named.
Enums and composite types
Model a constrained set of values and reuse a shared field shape.
- A constrained value set becomes a real database enum where the dialect has one.
- Adding a value is safe; removing one breaks rows that still hold it.
- A composite type reuses a field shape without creating another table.
Indexes
Add indexes for the queries a feature actually runs, not for every column.
- Indexes are added for the queries a feature runs, not for every column.
- A composite index only helps when the query filters on its leading columns.
- Every index is a cost on write, so an unused one is pure overhead.
Querying
findMany and filtering
Compose where conditions the client types check before the query runs.
- Conditions compose as objects the generated types check before the query runs.
- A typo in a field name fails at compile time rather than returning nothing.
- An empty filter object matches everything, which is rarely what was meant.
select and include
Return only needed columns and relations instead of whole rows by default.
- Only the needed columns and relations come back instead of whole rows.
- Without either, every scalar column is selected by default.
- select and include cannot both be used at the same level.
Nested writes
Create or connect related records in a single, consistent operation.
- Related records are created or connected in one consistent operation.
- It runs in a single transaction, so a partial write cannot survive.
- connect fails if the target does not exist, which is the check you want.
Aggregations and groupBy
Count, sum and group in the database rather than in application memory.
- Counting, summing and grouping happen in the database rather than in memory.
- Pulling rows to aggregate in the application moves data that is then thrown away.
- Grouped queries must name every non-aggregated field they select.
Raw queries
Drop to SQL safely with parameterized queries when the client cannot express it.
- SQL is available, parameterised, for what the client cannot express.
- Parameters are bound rather than interpolated, which is what keeps input out of the statement.
- The result is untyped, so its shape is asserted rather than inferred.
Migrations
migrate dev
Generate and apply a migration during development from a schema change.
- A schema change becomes a migration file and is applied locally.
- It can reset the database when history and schema have diverged, so it is development only.
- It uses transactions to read the current state, which pooled connections can break.
migrate deploy
Apply pending migrations in CI or production without generating new ones.
- Pending migrations are applied without generating anything new.
- It is the documented command for CI and production.
- Behind PgBouncer it must use the direct connection, since the Schema Engine needs a single connection.
Expand and contract
Ship a breaking column change as separate additive, backfill and cleanup steps.
- A breaking column change ships as additive, backfill and cleanup steps.
- Each step leaves the old and new code paths both working.
- Doing it in one step means the deploy and the migration must be simultaneous.
Baselining
Adopt Prisma on a database that already has tables without recreating them.
- An existing database is adopted without recreating its tables.
- The first migration is marked applied rather than actually run.
- Skipping this makes the tool try to create tables that already exist.
Drift detection
Recognize when the database no longer matches the migration history.
- The tool reports when the database no longer matches the migration history.
- Drift usually means someone changed the schema outside the migrations.
- Resolving it by resetting destroys data, so it is the wrong reflex in production.
Seeding
Populate reference data reproducibly and keep it separate from test fixtures.
- Reference data is populated reproducibly and kept apart from test fixtures.
- Seeds must be idempotent, because they will run more than once.
- Mixing fixtures into seeds puts test rows into a real environment.
Transactions & production
Interactive transactions
Run dependent reads and writes atomically with an explicit timeout.
$transaction(async tx => …) takes maxWait, timeout and isolationLevel.- The documented defaults are 2000ms to acquire and 5000ms to run.
- Every query must use the
tx handle, or it runs outside the transaction.
Batch transactions
Submit independent operations together when none depends on another's result.
- Independent operations are submitted together when none needs another's result.
- There is no round trip between them, which is where the speed comes from.
- A later operation cannot read what an earlier one in the batch wrote.
Isolation levels
Choose an isolation level deliberately when concurrent writes can interleave.
isolationLevel is set per transaction, defaulting to the database's own setting.Prisma.TransactionIsolationLevel.Serializable is the strictest option.- The docs note isolation levels are not supported on MongoDB.
Connection pooling
Size the pool for the runtime and pool externally on serverless platforms.
DATABASE_URL carries the pooled string for runtime queries.DIRECT_URL bypasses the pooler, which the CLI needs for migrations and introspection.- In Prisma 7 the runtime client is constructed with a driver adapter such as
@prisma/adapter-pg.
Client lifecycle
Reuse a single client across requests and shut it down cleanly.
- One client is reused across requests and shut down cleanly.
- A client per request exhausts connections under any real load.
- Hot reload in development creates clients repeatedly unless one is cached.
Query logging
Log slow queries and inspect the SQL the client actually emitted.
- Slow queries are logged and the emitted SQL inspected.
- Seeing the real SQL is what explains a query the types made look cheap.
- Logging every query in production is itself a performance cost.
Sources
- Prisma official documentation