T-CREATOR

Prisma スキーマ定義チートシート:model/enum/@id/@unique/@index の最短リファレンス

Prisma スキーマ定義チートシート:model/enum/@id/@unique/@index の最短リファレンス

Prisma を使ったアプリケーション開発では、データベースのテーブル構造を schema.prisma ファイルで定義します。この記事では、Prisma スキーマの中でも特に頻繁に使う modelenum@id@unique@index の 5 つの構文に焦点を絞り、実務ですぐに使えるチートシートとしてまとめました。

初めて Prisma に触れる方でも、この記事を見ればスキーマ定義の基本が理解でき、すぐにコードが書けるようになります。

早見表

model 基本構文

#項目構文説明
1モデル定義model ModelName { }データベースのテーブルに対応するモデルを定義します
2フィールド定義fieldName Typeフィールド名と型を指定します
3オプショナルfieldName Type?NULL を許可するフィールドとして定義されます
4配列fieldName Type[]1 対多のリレーションで使用します
5デフォルト値@default(value)フィールドの初期値を設定できます

enum 構文

#項目構文説明
1enum 定義enum EnumName { }固定値の選択肢を定義します
2値の指定VALUE_NAME大文字スネークケースで記述するのが慣例です
3フィールド利用status EnumNameモデル内で enum 型として参照できます

@id(主キー)

#項目構文説明
1単一主キー@id1 つのフィールドを主キーに設定します
2自動生成 ID@id @default(autoincrement())整数型の自動採番 ID として設定されます
3UUID 主キー@id @default(uuid())UUID を主キーとして自動生成します
4複合主キー@@id([field1, field2])複数フィールドの組み合わせを主キーにします

@unique(一意制約)

#項目構文説明
1単一フィールド@uniqueそのフィールドの値を一意にします
2複合 unique@@unique([field1, field2])複数フィールドの組み合わせを一意にします
3名前付き制約@@unique([fields], name: "constraint_name")制約に名前を付けて管理しやすくできます

@index(インデックス)

#項目構文説明
1単一インデックス@@index([fieldName])検索パフォーマンスを向上させます
2複合インデックス@@index([field1, field2])複数フィールドでの検索を高速化します
3名前付き@@index([fields], name: "index_name")インデックスに名前を付けて識別しやすくします
4並び順指定@@index([field(sort: Desc)])降順インデックスを作成できます(DB によります)

背景

Prisma は TypeScript や JavaScript で利用できる次世代 ORM(Object-Relational Mapping)ツールです。従来の ORM と異なり、スキーマファイル(schema.prisma)を中心にした宣言的なデータモデル定義が特徴で、型安全なデータベースアクセスを実現してくれます。

Prisma を導入することで、以下のようなメリットが得られます。

  • 型安全: TypeScript の型推論がデータベースクエリにも効くため、実行時エラーを防げます
  • 自動マイグレーション: スキーマ定義から SQL マイグレーションを自動生成できます
  • 直感的な API: クエリビルダーが読みやすく、学習コストが低いです

以下の図は、Prisma がアプリケーションとデータベースの間でどのように機能するかを示しています。

mermaidflowchart LR
  app["アプリケーション<br/>(TypeScript/JavaScript)"] -->|Prisma Client 呼び出し| client["Prisma Client"]
  client -->|SQL 実行| db[("データベース<br/>(MySQL/PostgreSQL等)")]
  schema["schema.prisma"] -.->|型生成| client
  schema -.->|マイグレーション生成| db

Prisma Client はスキーマファイルから自動生成され、型安全なクエリを提供します。マイグレーションもスキーマから生成されるため、開発者はスキーマファイルだけを管理すればよいのです。

スキーマファイルの役割

schema.prisma ファイルは、以下の 3 つの役割を担っています。

  • データソース定義: どのデータベースに接続するか
  • ジェネレータ定義: どの言語向けのクライアントを生成するか
  • データモデル定義: テーブル構造やリレーションの定義

この記事では、3 つ目の「データモデル定義」で使う構文に特化して解説していきます。

課題

Prisma のスキーマ定義は公式ドキュメントが充実していますが、以下のような課題があります。

  • 情報が分散している: modelenum、各アトリビュート(@id@unique など)の説明が別々のページに分かれており、必要な情報をすぐに見つけにくいです
  • サンプルが多すぎる: 公式ドキュメントには多くのユースケースが掲載されていますが、初心者には「最低限これだけ知っていればいい」という情報が欲しいところです
  • 比較しづらい: 似た機能(@id@@id@unique@@unique など)の違いが一目で分かりません

以下の図は、Prisma 初学者が抱えがちな課題を整理したものです。

mermaidflowchart TD
  start["Prisma スキーマを<br/>書きたい"] --> problem1["情報が分散している"]
  start --> problem2["構文の違いが<br/>わかりにくい"]
  start --> problem3["最低限必要な知識が<br/>わからない"]

  problem1 --> solution["チートシート形式で<br/>まとめて解決"]
  problem2 --> solution
  problem3 --> solution

これらの課題を解決するために、本記事では 最もよく使う 5 つの構文だけ を厳選し、チートシート形式でまとめました。

解決策

この記事では、Prisma スキーマ定義における modelenum@id@unique@index の 5 つに絞り、以下の方針で解説します。

  • 構文と用途を対応させる: 各構文がどんな場面で使われるかを明確にします
  • 比較表で違いを示す: 似た機能の違いを表形式で整理します
  • すぐ使えるサンプルコード: コピーして使える実例を提供します

以下の図は、この記事で扱う 5 つの構文がスキーマ定義のどこで使われるかを示しています。

mermaidflowchart TD
  schema["schema.prisma"] --> model["model ブロック"]
  schema --> enum["enum ブロック"]

  model --> fields["フィールド定義"]
  fields --> id["@id / @@id<br/>(主キー)"]
  fields --> unique["@unique / @@unique<br/>(一意制約)"]
  fields --> index["@@index<br/>(インデックス)"]

  enum --> values["固定値の定義"]

それぞれの構文を順番に見ていきましょう。

具体例

ここからは、5 つの構文を具体的なコード例とともに解説していきます。

model(モデル定義)

model はデータベースのテーブルに対応する構造を定義するブロックです。モデル名は PascalCase(単語の先頭を大文字)で記述し、通常は単数形を使います。

基本構文

prismamodel User {
  id    Int    @id @default(autoincrement())
  name  String
  email String @unique
}

上記のコードは、以下の 3 つのフィールドを持つ User テーブルを定義しています。

  • id: 整数型の主キーで、自動採番されます
  • name: 文字列型の必須フィールドです
  • email: 文字列型で、一意制約が付いています

フィールドの型指定

Prisma では、以下のような基本型が使えます。

prismamodel Post {
  id        Int      @id @default(autoincrement())
  title     String   // 文字列
  content   String?  // NULL 許可の文字列
  published Boolean  @default(false)  // 真偽値
  views     Int      @default(0)      // 整数
  rating    Float?   // 浮動小数点(NULL 許可)
  createdAt DateTime @default(now())  // 日時
}

各型の意味は以下の通りです。

  • String: 文字列型(VARCHAR や TEXT に対応)
  • Int: 整数型(INTEGER に対応)
  • Boolean: 真偽値(BOOLEAN に対応)
  • Float: 浮動小数点数(FLOAT や DOUBLE に対応)
  • DateTime: 日時型(DATETIME や TIMESTAMP に対応)

オプショナルフィールド

型の後ろに ? を付けると、NULL を許可するフィールドになります。

prismamodel Profile {
  id        Int     @id @default(autoincrement())
  bio       String? // NULL 許可
  avatarUrl String? // NULL 許可
  userId    Int     @unique
}

bioavatarUrl は入力必須ではなく、データがない場合は NULL が入ります。

リレーションの定義

他のモデルとの関連を定義する場合は、参照先のモデル型を使います。

prismamodel User {
  id    Int     @id @default(autoincrement())
  name  String
  posts Post[]  // User は複数の Post を持つ
}
prismamodel Post {
  id       Int    @id @default(autoincrement())
  title    String
  authorId Int
  author   User   @relation(fields: [authorId], references: [id])
}

User は複数の Post を持ち(1 対多)、Post は 1 つの User に紐づきます。@relation で外部キーの関係を明示しています。

enum(列挙型)

enum は、フィールドが取りうる値を固定の選択肢として定義する構文です。ステータスや役割など、限られた値しか取らないフィールドに使います。

基本構文

prismaenum Role {
  ADMIN
  USER
  GUEST
}
prismamodel User {
  id   Int    @id @default(autoincrement())
  name String
  role Role   @default(USER)
}

Role という enum を定義し、User モデルの role フィールドで利用しています。デフォルト値は USER です。

enum の利点

enum を使うことで、以下のメリットが得られます。

  • 型安全: コード上で不正な値を指定するとコンパイルエラーになります
  • 可読性: 値の意味が明確になります
  • DB 制約: データベースレベルで値を制限できます(DB によります)

複数の enum を使う例

prismaenum Status {
  DRAFT
  PUBLISHED
  ARCHIVED
}
prismaenum Priority {
  LOW
  MEDIUM
  HIGH
}
prismamodel Task {
  id       Int      @id @default(autoincrement())
  title    String
  status   Status   @default(DRAFT)
  priority Priority @default(MEDIUM)
}

Task モデルは StatusPriority の 2 つの enum を使い、タスクの状態と優先度を管理しています。

@id / @@id(主キー)

主キーは、テーブル内の各レコードを一意に識別するフィールドです。Prisma では @id(単一フィールド)と @@id(複合フィールド)の 2 種類があります。

@id(単一フィールドの主キー)

1 つのフィールドを主キーにする場合は @id を使います。

prismamodel User {
  id   Int    @id @default(autoincrement())
  name String
}

id フィールドが主キーとなり、自動採番されます。

UUID を主キーにする

整数の代わりに UUID を主キーにすることもできます。

prismamodel User {
  id   String @id @default(uuid())
  name String
}

uuid() 関数で自動的に UUID が生成されます。分散システムや外部公開 API では、連番 ID よりも UUID が好まれることが多いです。

@@id(複合主キー)

複数のフィールドの組み合わせを主キーにする場合は @@id を使います。

prismamodel UserRole {
  userId Int
  roleId Int

  @@id([userId, roleId])
}

userIdroleId の組み合わせが一意になります。中間テーブル(多対多リレーション)でよく使われます。

@id と @@id の使い分け

以下の表で、2 つの構文の違いを整理します。

#項目@id@@id
1対象単一フィールド複数フィールドの組み合わせ
2記述位置フィールドの横モデルブロック内の最後
3利用例通常のテーブル中間テーブル、複合キーが必要なテーブル
4自動生成@default(autoincrement())uuid() が使える使えない

@unique / @@unique(一意制約)

一意制約は、フィールドの値が重複しないようにする制約です。主キーではないが、ユニークであるべきフィールド(メールアドレスやユーザー名など)に使います。

@unique(単一フィールドの一意制約)

1 つのフィールドに一意制約を付ける場合は @unique を使います。

prismamodel User {
  id    Int    @id @default(autoincrement())
  email String @unique
}

email フィールドは重複が許されず、同じメールアドレスで複数のユーザーを登録できません。

複数フィールドに個別の一意制約

prismamodel User {
  id       Int    @id @default(autoincrement())
  email    String @unique
  username String @unique
}

emailusername の両方に一意制約が付き、それぞれが重複しないようになります。

@@unique(複合一意制約)

複数フィールドの組み合わせを一意にする場合は @@unique を使います。

prismamodel Post {
  id     Int    @id @default(autoincrement())
  slug   String
  locale String

  @@unique([slug, locale])
}

sluglocale の組み合わせが一意になります。たとえば、同じ slug でも locale が異なれば登録できます。

名前付き一意制約

一意制約に名前を付けることで、エラーメッセージやマイグレーションファイルでの識別が容易になります。

prismamodel Product {
  id   Int    @id @default(autoincrement())
  sku  String
  size String

  @@unique([sku, size], name: "product_sku_size_unique")
}

name パラメータで制約名を指定しています。

@unique と @@unique の比較

#項目@unique@@unique
1対象単一フィールド複数フィールドの組み合わせ
2記述位置フィールドの横モデルブロック内の最後
3利用例email、username など複合キーでの一意性
4名前付けできないname パラメータで可能

@@index(インデックス)

インデックスは、データベースの検索パフォーマンスを向上させるための仕組みです。頻繁に検索条件として使うフィールドにインデックスを張ることで、クエリが高速化されます。

基本構文

prismamodel Post {
  id        Int      @id @default(autoincrement())
  title     String
  authorId  Int
  createdAt DateTime @default(now())

  @@index([authorId])
}

authorId フィールドにインデックスが作成され、「特定の著者の投稿を検索する」クエリが速くなります。

複合インデックス

複数のフィールドを組み合わせたインデックスも作れます。

prismamodel Post {
  id        Int      @id @default(autoincrement())
  authorId  Int
  status    String
  createdAt DateTime @default(now())

  @@index([authorId, status])
}

「特定の著者かつ特定のステータス」で検索する場合に効果を発揮します。

名前付きインデックス

インデックスに名前を付けると、データベース管理ツールで識別しやすくなります。

prismamodel Post {
  id        Int      @id @default(autoincrement())
  authorId  Int
  createdAt DateTime @default(now())

  @@index([authorId], name: "author_id_index")
  @@index([createdAt], name: "created_at_index")
}

複数のインデックスを定義する場合、名前を付けることで管理しやすくなります。

並び順指定(データベース依存)

一部のデータベースでは、インデックスの並び順を指定できます。

prismamodel Post {
  id        Int      @id @default(autoincrement())
  createdAt DateTime @default(now())

  @@index([createdAt(sort: Desc)], name: "recent_posts_index")
}

降順インデックスを作ることで、「最新の投稿から取得する」クエリが最適化されます。ただし、この機能は MySQL や PostgreSQL など、データベースによって対応状況が異なります。

インデックスを使う場面

以下のような場合にインデックスを検討しましょう。

  • 頻繁に検索される: WHERE 句でよく使うフィールド
  • 外部キー: リレーション元のフィールド
  • ソート対象: ORDER BY でよく使うフィールド
  • ユニークではないが検索が多い: ステータスやカテゴリなど

インデックスの注意点

インデックスには以下のトレードオフがあります。

  • 読み込みは速くなる: SELECT クエリが高速化されます
  • 書き込みは遅くなる: INSERT や UPDATE のたびにインデックスも更新されるため、若干遅くなります
  • ストレージを消費する: インデックスはディスク容量を使います

必要な場所にだけインデックスを張り、むやみに増やさないことが大切です。

実践的なスキーマ例

ここまでの内容を組み合わせた、実務で使えるスキーマ例を示します。

prisma// ユーザーの役割を定義
enum Role {
  ADMIN
  EDITOR
  VIEWER
}
prisma// 投稿のステータスを定義
enum PostStatus {
  DRAFT
  PUBLISHED
  ARCHIVED
}
prisma// ユーザーモデル
model User {
  id        String   @id @default(uuid())
  email     String   @unique
  username  String   @unique
  role      Role     @default(VIEWER)
  createdAt DateTime @default(now())
  posts     Post[]

  @@index([email])
  @@index([createdAt])
}

このスキーマでは、以下の要素を使っています。

  • UUID 主キー: @id @default(uuid()) で外部に公開しても安全な ID
  • 一意制約: emailusername@unique
  • enum: Role でユーザーの役割を管理
  • リレーション: posts Post[] で 1 対多を定義
  • インデックス: emailcreatedAt で検索を高速化
prisma// 投稿モデル
model Post {
  id        Int        @id @default(autoincrement())
  title     String
  slug      String
  content   String?
  status    PostStatus @default(DRAFT)
  authorId  String
  author    User       @relation(fields: [authorId], references: [id])
  createdAt DateTime   @default(now())
  updatedAt DateTime   @updatedAt

  @@unique([slug, authorId])
  @@index([authorId])
  @@index([status])
  @@index([createdAt])
}

Post モデルでは、以下の工夫をしています。

  • 複合一意制約: @@unique([slug, authorId]) で同じ著者内で slug が重複しないようにする
  • 複数インデックス: authorIdstatuscreatedAt で検索を最適化
  • 自動更新: updatedAt フィールドは @updatedAt で自動的に更新日時が記録されます

以下の図は、UserPost のリレーションとインデックスの関係を示しています。

mermaiderDiagram
  User ||--o{ Post : "1対多"

  User {
    String id PK "UUID"
    String email UK "一意制約"
    String username UK "一意制約"
    Role role "enum"
    DateTime createdAt "インデックス"
  }

  Post {
    Int id PK "主キー"
    String slug "複合一意制約"
    String authorId FK "インデックス, 外部キー"
    PostStatus status "インデックス, enum"
    DateTime createdAt "インデックス"
  }

この図から、主キー(PK)、外部キー(FK)、一意制約(UK)、インデックスがどこに設定されているかが一目で分かります。

まとめ

この記事では、Prisma スキーマ定義の中でも特に重要な modelenum@id@unique@index の 5 つの構文に絞って解説しました。

それぞれのポイントを振り返りましょう。

  • model: データベースのテーブルに対応し、フィールドとリレーションを定義します
  • enum: 固定の選択肢を型安全に扱え、ステータスや役割の管理に便利です
  • @id / @@id: 主キーを定義し、単一フィールドか複合フィールドかで使い分けます
  • @unique / @@unique: 一意制約を設定し、重複を防ぎます
  • @@index: インデックスを張ることで、検索パフォーマンスを向上させます

これらの構文を使いこなせば、実務で必要な Prisma スキーマのほとんどを記述できるようになります。この記事をチートシートとして手元に置き、スキーマ定義の際に参考にしてください。

Prisma のスキーマ定義は、最初は覚えることが多く感じるかもしれませんが、一度パターンを理解すれば直感的に書けるようになります。ぜひ実際にコードを書きながら、体で覚えていってくださいね。

関連リンク