CodingNic

Associations and Relationships

One-to-Many Relationships

Associations and Relationships 15 min read

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:

text
Author (1) โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€< (many) Book

Modeling It in Prisma

text
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-written author_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 related Author using authorId.
  • Author.books Book[], the reverse side, also not a real column, it lets code navigate from an author to their books.

The Generated Migration

bash
npx prisma migrate dev --name add_author_book
sql
-- 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.

javascript
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

  1. Model Author and Book as shown, and migrate the relationship.
  2. Read the generated migration, and identify the exact line that creates the foreign key constraint.
  3. Create an author with two books in one prisma.author.create() call, as shown above.
  4. Explain, in your own words, the difference between Book.authorId (a real column) and Book.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 @relation field describing it, and a Book[] array field on the “one” side for reverse navigation.
  • Only the foreign key field itself becomes a real column, the @relation and array fields are Prisma-level conveniences for navigating the relationship in code.
  • Migrating a relationship generates a real FOREIGN KEY constraint, 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.