One-to-Many Relationships
Objectives
By the end of this lesson, you should be able to:
- Model a one-to-many relationship in a Prisma schema
- Explain how a foreign key is represented on both sides of the relationship
- Migrate a relationship into real foreign key constraints
๐ก Why this matters: Real data almost never lives in a single, isolated table. A student has many enrollments, a customer has many orders. This lesson covers the most common relationship shape, one record connected to many others.
โ ๏ธ A note on verification: as throughout this module, the Prisma CLI can’t run inside this course’s own sandboxed tooling. The schema syntax and migration output below reflect Prisma’s stable, current, documented behavior. Try it yourself, on your own machine, against a real database.
The Shape of a One-to-Many Relationship
One Author can have many Books, but each Book has exactly one Author:
Author (1) โโโโโโโโ< (many) Book
Modeling It in Prisma
model Author {
id Int @id @default(autoincrement())
name String
books Book[]
}
model Book {
id Int @id @default(autoincrement())
title String
author Author @relation(fields: [authorId], references: [id])
authorId Int
}
Reading both sides:
Book.authorId Int, a plain integer field, this is the actual foreign key column, the same as a hand-writtenauthor_id INTEGER REFERENCES authors(id)in raw SQL.Book.author Author @relation(fields: [authorId], references: [id]), a relation field, not a real column, it tells Prisma how to look up the relatedAuthorusingauthorId.Author.books Book[], the reverse side, also not a real column, it lets code navigate from an author to their books.
The Generated Migration
npx prisma migrate dev --name add_author_book
-- CreateTable
CREATE TABLE "Author" (
"id" SERIAL NOT NULL,
"name" TEXT NOT NULL,
CONSTRAINT "Author_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "Book" (
"id" SERIAL NOT NULL,
"title" TEXT NOT NULL,
"authorId" INTEGER NOT NULL,
CONSTRAINT "Book_pkey" PRIMARY KEY ("id")
);
-- AddForeignKey
ALTER TABLE "Book" ADD CONSTRAINT "Book_authorId_fkey"
FOREIGN KEY ("authorId") REFERENCES "Author"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
This is exactly the foreign key relationship covered conceptually in this lesson’s model, translated into real SQL, Book.authorId referencing Author.id, with ON DELETE RESTRICT meaning PostgreSQL refuses to delete an author who still has books, protecting against orphaned rows.
Creating Related Records
const author = await prisma.author.create({
data: {
name: 'Robert Martin',
books: {
create: [
{ title: 'Clean Code' },
{ title: 'Clean Architecture' }
]
}
}
});
This creates the Author row and both Book rows in one call, authorId is filled in automatically for each book, no manual ID juggling needed.
Try It
- Model
AuthorandBookas shown, and migrate the relationship. - Read the generated migration, and identify the exact line that creates the foreign key constraint.
- Create an author with two books in one
prisma.author.create()call, as shown above. - Explain, in your own words, the difference between
Book.authorId(a real column) andBook.author(a relation field that isn’t a column at all).
Recap
- A one-to-many relationship needs a foreign key field (
authorId) on the “many” side, plus a@relationfield describing it, and aBook[]array field on the “one” side for reverse navigation. - Only the foreign key field itself becomes a real column, the
@relationand array fields are Prisma-level conveniences for navigating the relationship in code. - Migrating a relationship generates a real
FOREIGN KEYconstraint, enforced by PostgreSQL itself, not just by application code.
Next lesson: many-to-many relationships, where records on both sides can relate to multiple records on the other.