Chapters of From Frontend to Full-Stack Backend

Week 2 - Database DepthChapter 13 of 282 min read

Prisma - schema, migration, relations and the N+1 problem

Writing the schema in one place, migration discipline, relation queries, catching and fixing N+1

Writing raw SQL has one pain - there is no type safety in the result. If you mistype a column name or a field's type changes, it gets caught at runtime, in front of the user. Prisma brings relief here: you write the schema once, and Prisma builds a type-safe client from it - write a wrong query and you see the red line in the editor itself, before the server even starts.

// prisma/schema.prisma
model User {
  id     Int     @id @default(autoincrement())
  email  String  @unique
  name   String
  orders Order[]
}

model Order {
  id     Int  @id @default(autoincrement())
  total  Int
  userId Int
  user   User @relation(fields: [userId], references: [id])
}

Migration - the ledger of database changes

When the schema changes, the database has to change too. Migration is the step-by-step written history of that change - it lives in Git like code, and everyone on the team reaches the same state in the same steps. Changing the database by hand is what causes the nightmare (one person's machine has the column, another's does not); migration ends that nightmare.

npx prisma migrate dev --name add_delivery_zone

// Fetching data with relations - in a single query
const orders = await prisma.order.findMany({
  include: { user: true, items: true },
});

The N+1 problem - this week's most expensive phrase

The scene goes like this: first you fetch 100 orders in one query. Then, to fetch each order's user, you run 100 more separate queries. Total 101 queries - when the job could have been done in 1 or 2 queries. That is called the N+1 problem: 1 main query + N extra ones. In Prisma the solution is using include like above - Prisma then joins things internally and brings them, without separate queries. You can catch the problem by turning on the query log - if calling an endpoint once makes queries shoot out in a rush, you know N+1 is hiding there.

The mistake everyone makes

Running queries inside a loop - that is the most common source of N+1. Another mistake: changing the schema by hand directly on the production database, without a migration - then staging and production structures no longer match.

Today's practice

Put Prisma into your week 1 API - write User and Order models, run a migration, rewrite the old raw queries with Prisma. Then turn on the query log and check whether any endpoint is sending needless extra queries.

How to say it in an interview

I use Prisma for type safety - the schema is in one place, and the client built from it catches wrong queries at compile time. I keep database changes with history through migrations. And I avoid N+1 by fetching relations together with include - I never run queries inside a loop.

All chapters of From Frontend to Full-Stack Backend