Tech2 views

Part 3: Prisma Schema Deep Dive

In Part 2, we already set up a project using Prisma 7 + Next.js 16.2.2 + PostgreSQL + pnpm, and defined a minimal usable User model in prisma/schema.prisma.

The goal of this part is: to explain this schema from top to bottom — what datasource, generator, model, enum, and their attributes are, and how to use them. After reading, you should be able to:


1. Review our current schema.prisma

Let's first look at the current prisma/schema.prisma in the project (from Part 2):

text
generator client {
  provider = "prisma-client"
  output   = "../generated/prisma"
}

datasource db {
  provider = "postgresql"
  url      = env("DATABASE_URL")
}

model User {
  id        Int      @id @default(autoincrement())
  name      String
  email     String   @unique
  createdAt DateTime @default(now())
}

Prisma official docs tell you: The Prisma Schema has only three blocks:

This part expands on these three sections.


2. datasource: Tell Prisma "where to connect to the database"

2.1 What does our current datasource look like?

text
datasource db {
  provider = "postgresql"
  url      = env("DATABASE_URL")
}

The purpose of this configuration is:

Combined with the .env from Part 2:

text
DATABASE_URL="postgresql://prisma:prisma@localhost:5432/next_prisma_demo?schema=public"

The full meaning is:

2.2 What else can you configure in datasource?

In the Prisma Schema Reference, datasource has more advanced options besides provider and url, such as relation mode, etc., but if you're using Postgres, these two basic configurations are usually enough.

Remember this sentence:

Prisma only knows "which database you are connecting to" through datasource, and decides which types and features to support accordingly.


3. generator: Tell Prisma "which client to generate"

3.1 Recommended generator syntax for Prisma 7

In Prisma 7, we write the following in the schema:

text
generator client {
  provider = "prisma-client"
  output   = "../generated/prisma"
}

According to the official docs, for the prisma-client generator, output is required to tell Prisma where to place the generated client.

With the project structure from Part 2, this means:

That's why we import it like this in lib/prisma.ts:

text
import { PrismaClient } from '../generated/prisma'

3.2 Key points about generator

Based on Prisma 7 official docs, here are key points worth remembering about generator:

If you later use Prisma in a monorepo/multi-package structure, output becomes more important; for this current single-package project, the default generated/prisma path is already clear enough.


4. data model: Every part of the User model

Now we get to the most important part: data model. The official docs call this the Data Model Definition, including model, enum, attributes, etc.

Our current User model is:

text
model User {
  id        Int      @id @default(autoincrement())
  name      String
  email     String   @unique
  createdAt DateTime @default(now())
}

We can break it down into a few dimensions:

For now, we'll only look at "scalar + basic attributes"; relationships will be covered in the next part.

4.1 Field structure: name + type + optional modifier

Take the email line as an example:

text
email     String   @unique

Breaking it down:

Now look at the id line:

text
id        Int      @id @default(autoincrement())

This confirms the official definition of a model field: field = name + type + optional modifier (?/[]) + attributes.

4.2 Scalar Types

Prisma supports a set of scalar types. The official docs list these common ones:

And some provider-specific native type mappings (via the @db.* attribute), such as @db.VarChar(255), @db.Decimal(10,2), etc.

If we want to extend the User model a bit, for example add a balance field, we can write:

text
model User {
  id        Int      @id @default(autoincrement())
  name      String
  email     String   @unique
  balance   Decimal  @default(0)
  createdAt DateTime @default(now())
}

When mapping to Postgres, we can add native type attributes, for example:

text
balance   Decimal  @db.Decimal(10, 2) @default(0)

This tells Prisma to use DECIMAL(10,2) in the database.

4.3 Nullable and Arrays

In Prisma, ? and [] after a type are two very common modifiers:

For example, if we want to make name an optional field:

text
name String?

Or add a string array field tags to User:

text
tags String[]

Currently, our User is intentionally kept simple: all fields are required, no arrays for now; later when we do relationship modeling, you'll see array type fields like posts Post[].


5. Field attributes: Add "constraints and semantics" to fields with @xxx

Prisma uses so-called "attributes" to describe field constraints and semantics, written after the field, starting with @.

We have already used these in User:

5.1 @id: Primary Key

text
id Int @id @default(autoincrement())

Prisma docs state: An ID field uniquely identifies each record in a model. A model can have one @id or one @@id (composite primary key).

Here we use the most common pattern: "single-field integer primary key + auto-increment".

5.2 @default(...): Default Values

@default() can specify a default value or default function for a field. Common combinations:

In User, we use:

text
id        Int      @id @default(autoincrement())
createdAt DateTime @default(now())

At the database level, this means: id uses a sequence/auto-increment, and createdAt defaults to the insert time.

5.3 @unique: Unique Constraint

text
email String @unique

Indicates that User.email must be unique in the database. Prisma Migrate will create a unique index for this.

Later, when you try to insert a duplicate email in the business logic, you'll receive an error like "P2002 Unique constraint failed", which can be used for user registration conflict checks.

5.4 @updatedAt: Auto-updating time (optional addition)

Although the current User model doesn't use it yet, another common attribute you'll almost certainly use is @updatedAt:

text
updatedAt DateTime @updatedAt

You can consider upgrading the User model in the current project to:

text
model User {
  id        Int      @id @default(autoincrement())
  name      String
  email     String   @unique
  createdAt DateTime @default(now())
  updatedAt DateTime @updatedAt
}

6. Model attributes (@@xxx): Add rules at the model level

Model attributes are written at the bottom of the model block, do not belong to any field, start with @@, and common ones include:

6.1 @@map: Custom Table Name

You may not need this now, but understanding it is very useful. For example, if you want to call it User in code but have the table named users in the database:

text
model User {
  id        Int      @id @default(autoincrement())
  name      String
  email     String   @unique
  createdAt DateTime @default(now())

  @@map("users")
}

This means:

Useful for "old database integration + new code" scenarios.

6.2 @@index: Add indexes for common queries

Suppose you frequently query by createdAt for the latest users; you can add an index:

text
model User {
  id        Int      @id @default(autoincrement())
  name      String
  email     String   @unique
  createdAt DateTime @default(now())

  @@index([createdAt])
}

Or if you have a combined query requirement, such as often filtering by (email, createdAt) combination, you can write:

text
@@index([email, createdAt])

Prisma Migrate will generate the corresponding index SQL, improving query performance.


7. enum: A better choice for fixed value sets (preparing to enhance User)

When a field's values come from a finite set, such as user roles, order statuses, task priorities, etc., it's not recommended to use arbitrary strings; the official docs recommend using enum.

7.1 Adding a Role enum field to User

We can add a simple enum on the current schema:

text
enum Role {
  USER
  ADMIN
}

Then add a role field to the User model:

text
model User {
  id        Int      @id @default(autoincrement())
  name      String
  email     String   @unique
  role      Role     @default(USER)
  createdAt DateTime @default(now())
  updatedAt DateTime @updatedAt
}

The benefits are:

In code, it will look like this:

text
const user = await prisma.user.create({
  data: {
    name: 'Alice',
    email: 'alice@example.com',
    role: 'ADMIN', // has TS type hints
  },
})

The Prisma official Schema documentation recommends: whenever you have a "fixed set" string field, use enum if possible; this reduces spelling errors and magic strings.


8. About relationships (@relation): Will be explained in detail in the next part with a blog system

In Part 2 and this part, we deliberately used only a single-table User model to clearly explain the basics of the Schema language. Relationship modeling (such as User–Post–Comment) will be fully broken down in the next part using a complete "blog system" example.

Here is a conceptual diagram, similar to the models in the official docs:

text
model User {
  id      Int    @id @default(autoincrement())
  name    String
  email   String @unique
  posts   Post[]
}

model Post {
  id        Int      @id @default(autoincrement())
  title     String
  content   String?
  authorId  Int
  author    User     @relation(fields: [authorId], references: [id])
  createdAt DateTime @default(now())
}

You can remember these points for now:

The next part will focus on one-to-one, one-to-many, many-to-many, and nested writes using the "blog system User / Post / Comment".


9. Practical connection with the current project: What can you do now?

With Part 2 and this part, you can now make some safe enhancements to the current project's schema.prisma, for example:

After each schema modification, you can:

  1. Run pnpm prisma migrate dev --name something to generate a migration and sync the local database;

  2. Use pnpm prisma studio to open the UI and check if fields and data are as expected;

  3. In Next.js, use queries like prisma.user.findMany() to access the new fields.

Later, when you integrate Prisma MCP, most of the AI's work will be modifying this schema and running these commands, so understanding the semantics of the schema is the prerequisite for being able to "review AI output".


10. Summary: What you truly need to master in this part

Quick check: if you meet these criteria, you have mastered the basic syntax of Prisma Schema:

SHARE

Share

Share this article.