【TypeScript × Prisma】型安全なDBアクセス完全ガイド|自動生成型・CRUD・リレーション・トランザクション・エラー処理まで徹底解説

【TypeScript × Prisma】型安全なDBアクセス完全ガイド|自動生成型・CRUD・リレーション・トランザクション・エラー処理まで徹底解説 TypeScript

Prisma は TypeScript との親和性が抜群に高いORMです。schema.prisma にテーブル定義を書くだけで、型安全なクライアントコードが自動生成され、クエリの引数・戻り値・リレーションのデータすべてに正確な型が付きます。

この記事では Prisma が生成する型の構造を理解した上で、CRUD・リレーション・トランザクション・エラー処理まで、実務で即使えるパターンを TypeScript の型定義を中心に解説します。

この記事でわかること

  • Prisma が schema.prisma から自動生成する型の種類と使い方
  • 型安全な CRUD 操作(findUnique・findMany・create・update・delete)
  • include / select によるリレーションデータの型推論
  • 型安全なフィルタリング・ソート・ページネーション
  • トランザクション($transaction)の型定義
  • Prisma エラー(PrismaClientKnownRequestError)の型安全な処理
  • PrismaClient 拡張(Extensions)と型定義
  • Next.js / Express でのシングルトンパターン
スポンサーリンク

セットアップ

# Prisma のインストール
npm install prisma --save-dev
npm install @prisma/client

# Prisma の初期化(schema.prisma が生成される)
npx prisma init

# または SQLite でローカル開発
npx prisma init --datasource-provider sqlite
// .env
DATABASE_URL="postgresql://user:password@localhost:5432/mydb"
// SQLite の場合
// DATABASE_URL="file:./dev.db"

PrismaClient のシングルトン初期化

Next.js 等のホットリロード環境では PrismaClient が複数インスタンス生成されることがあります。シングルトンパターンで防ぎましょう。

// lib/prisma.ts
import { PrismaClient } from "@prisma/client";

const globalForPrisma = globalThis as unknown as {
  prisma: PrismaClient | undefined;
};

export const prisma =
  globalForPrisma.prisma ??
  new PrismaClient({
    log: process.env.NODE_ENV === "development"
      ? ["query", "error", "warn"]
      : ["error"],
  });

if (process.env.NODE_ENV !== "production") {
  globalForPrisma.prisma = prisma;
}
globalThis を使う理由
global(Node.js固有)ではなく globalThis(ECMAScript標準)を使うことで、TypeScript の型チェックが通り、Edge Runtime(Vercel Edge / Cloudflare Workers)でも動作します。as unknown as { prisma: ... } のダブルアサーションはglobalThis の型定義に prisma が含まれていないための必要な回避策です。

スキーマ定義と自動生成型

schema.prisma の定義例

// prisma/schema.prisma
generator client {
  provider = "prisma-client-js"
}

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

model User {
  id        Int       @id @default(autoincrement())
  email     String    @unique
  name      String?
  role      Role      @default(USER)
  createdAt DateTime  @default(now())
  updatedAt DateTime  @updatedAt
  deletedAt DateTime?              // ソフトデリート用
  posts     Post[]
  profile   Profile?
}

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

model Profile {
  id     Int    @id @default(autoincrement())
  bio    String?
  userId Int    @unique
  user   User   @relation(fields: [userId], references: [id])
}

model Tag {
  id    Int    @id @default(autoincrement())
  name  String @unique
  posts Post[] @relation("PostTags")
}

enum Role {
  USER
  ADMIN
  MODERATOR
}
# スキーマを変更したら必ず実行
npx prisma generate         # TypeScript 型を再生成
npx prisma migrate dev      # DBにマイグレーションを適用
npx prisma db push          # マイグレーションなしで同期(プロトタイプ向け)

自動生成される型の種類

npx prisma generate を実行すると @prisma/client に以下の型が生成されます。

型名 用途
User モデルのデータ型(全フィールド) { id: number; email: string; name: string | null; ... }
Prisma.UserCreateInput 作成時の入力型 { email: string; name?: string; role?: Role }
Prisma.UserUpdateInput 更新時の入力型(全フィールドオプション) { email?: string; name?: string | null; ... }
Prisma.UserWhereInput フィルタ条件型 { email?: StringFilter; role?: Role; ... }
Prisma.UserWhereUniqueInput ユニーク条件型 { id?: number; email?: string }
Prisma.UserOrderByWithRelationInput ソート条件型 { createdAt?: SortOrder }
Prisma.UserSelect 取得フィールド指定型 { id?: boolean; email?: boolean; ... }
Prisma.UserInclude リレーション読み込み型 { posts?: boolean | PostFindManyArgs; ... }
Prisma.UserGetPayload<T> select/include を反映した戻り値型 Prisma.UserGetPayload<{ include: { posts: true } }>
Role enum 型 "USER" | "ADMIN" | "MODERATOR"

CRUD 操作と型定義

Read:findUnique / findFirst / findMany

import { prisma } from "@/lib/prisma";
import type { User } from "@prisma/client";

// ─── findUnique ─── 戻り値: User | null
async function getUserById(id: number): Promise<User | null> {
  return prisma.user.findUnique({
    where: { id }, // Prisma.UserWhereUniqueInput
  });
}

// ─── findFirstOrThrow ─── 見つからない場合は PrismaClientKnownRequestError
async function getUserByEmail(email: string): Promise<User> {
  return prisma.user.findFirstOrThrow({
    where: { email },
  });
}

// ─── findMany ─── 戻り値: User[]
async function getAdminUsers(): Promise<User[]> {
  return prisma.user.findMany({
    where: { role: "ADMIN" },
    orderBy: { createdAt: "desc" },
    take: 20,
    skip: 0,
  });
}

// ─── count ─── 戻り値: number
async function countUsers(): Promise<number> {
  return prisma.user.count({
    where: { role: "USER" },
  });
}

Create:create / createMany

import type { Prisma, User } from "@prisma/client";

// Prisma.UserCreateInput を使って入力型を明示
async function createUser(data: Prisma.UserCreateInput): Promise<User> {
  return prisma.user.create({ data });
}

// 利用例
const newUser = await createUser({
  email: "alice@example.com",
  name: "Alice",
  role: "USER", // Role enum の値
  // posts は undefined でも OK(optional)
  // createdAt は @default(now()) なので不要
});
// newUser の型: User(全フィールドが含まれる)

// ─── createMany ─── 戻り値: Prisma.BatchPayload({ count: number })
async function seedUsers(): Promise<Prisma.BatchPayload> {
  return prisma.user.createMany({
    data: [
      { email: "bob@example.com", name: "Bob" },
      { email: "carol@example.com", name: "Carol", role: "ADMIN" },
    ],
    skipDuplicates: true,
  });
}

Update:update / upsert / updateMany

import type { Prisma, User } from "@prisma/client";

// ─── update ─── where + data が必須
async function updateUserName(
  id: number,
  name: string
): Promise<User> {
  return prisma.user.update({
    where: { id },
    data: { name }, // Prisma.UserUpdateInput
  });
}

// ─── upsert ─── 存在すれば update、なければ create
async function upsertUser(
  email: string,
  name: string
): Promise<User> {
  return prisma.user.upsert({
    where:  { email },             // UserWhereUniqueInput
    update: { name },              // UserUpdateInput
    create: { email, name },       // UserCreateInput
  });
}

// ─── updateMany ─── 複数件更新、戻り値は BatchPayload
async function deactivateOldUsers(before: Date): Promise<Prisma.BatchPayload> {
  return prisma.user.updateMany({
    where:  { createdAt: { lt: before } },
    data:   { role: "USER" },
  });
}

Delete:delete / deleteMany

// ─── delete ─── 戻り値: User(削除されたレコード)
async function deleteUser(id: number): Promise<User> {
  return prisma.user.delete({
    where: { id },
  });
}

// ─── deleteMany ─── 戻り値: Prisma.BatchPayload
async function deleteUnverifiedUsers(): Promise<Prisma.BatchPayload> {
  return prisma.user.deleteMany({
    where: { email: { endsWith: "@temp.com" } },
  });
}

リレーションデータの型推論(include / select)

Prisma の最大の型安全機能が include / select の型推論です。どのフィールドを取得するかに応じて、戻り値の型が自動的に変化します。

include で関連データを取得する

import type { Prisma } from "@prisma/client";

// include: { posts: true } で User + Post[] が戻り値型に含まれる
const userWithPosts = await prisma.user.findUnique({
  where: { id: 1 },
  include: { posts: true },
});
// userWithPosts の型:
// (User & { posts: Post[] }) | null
// userWithPosts?.posts は Post[] 型

// ネストした include(Post の author も含める)
const postWithAll = await prisma.post.findUnique({
  where: { id: 1 },
  include: {
    author: true,           // User 型
    tags:   true,           // Tag[] 型
  },
});
// postWithAll?.author は User 型
// postWithAll?.tags は Tag[] 型

// ─── Prisma.UserGetPayload で型を事前定義 ───
// 戻り値型を関数の引数・戻り値として共有したい場合
type UserWithPosts = Prisma.UserGetPayload<{
  include: { posts: true };
}>;

function displayUser(user: UserWithPosts): string {
  // user.posts は Post[] 型として使える
  return `${user.name}: ${user.posts.length}件の投稿`;
}

select で取得フィールドを絞る

// select で取得するフィールドを指定すると、型が自動的に絞られる
const userSummary = await prisma.user.findMany({
  select: {
    id:    true,
    email: true,
    name:  true,
    // createdAt・updatedAt・role は取得しない
  },
});
// userSummary の型: { id: number; email: string; name: string | null }[]
// role や createdAt にアクセスしようとするとコンパイルエラー

// select とリレーションの組み合わせ
const usersWithPostCount = await prisma.user.findMany({
  select: {
    id:    true,
    name:  true,
    _count: { select: { posts: true } }, // 集計フィールド
    posts: {
      select: {
        id:    true,
        title: true,
        // content は除外
      },
      where: { published: true }, // リレーション内フィルタも可能
    },
  },
});
// usersWithPostCount の型:
// { id: number; name: string | null; _count: { posts: number };
//   posts: { id: number; title: string }[] }[]

// ─── Prisma.UserGetPayload で型を定義 ───
type UserSummary = Prisma.UserGetPayload<{
  select: {
    id: true;
    email: true;
    name: true;
    posts: { select: { id: true; title: true } };
  };
}>;
include と select は同時使用不可
includeselect を同一クエリで併用するとエラーになります。リレーションを含む特定フィールドだけを取得したい場合は select の中にネストした select を使ってください。(上記コード例の posts: { select: { ... } } のパターン)

型安全なフィルタリング・ソート・ページネーション

import type { Prisma } from "@prisma/client";

// ─── 検索条件を型安全に組み立てる ───
interface PostFilterParams {
  keyword?:   string;
  authorId?:  number;
  published?: boolean;
  tags?:      string[];
  page?:      number;
  pageSize?:  number;
}

async function findPosts(params: PostFilterParams) {
  const { keyword, authorId, published, tags, page = 1, pageSize = 20 } = params;

  // Prisma.PostWhereInput 型で where 条件を組み立てる
  const where: Prisma.PostWhereInput = {
    ...(published !== undefined && { published }),
    ...(authorId  !== undefined && { authorId }),
    ...(keyword && {
      OR: [
        { title:   { contains: keyword, mode: "insensitive" } },
        { content: { contains: keyword, mode: "insensitive" } },
      ],
    }),
    ...(tags?.length && {
      tags: { some: { name: { in: tags } } },
    }),
  };

  const [posts, total] = await prisma.$transaction([
    prisma.post.findMany({
      where,
      orderBy: { createdAt: "desc" },
      take:    pageSize,
      skip:    (page - 1) * pageSize,
      include: { author: { select: { name: true, email: true } } },
    }),
    prisma.post.count({ where }),
  ]);

  return {
    posts,
    total,
    totalPages: Math.ceil(total / pageSize),
    page,
  };
}
Prisma のフィルタ演算子

  • equalsnot:完全一致・不一致
  • innotIn:配列の中に含まれる・含まれない
  • ltltegtgte:数値・日付の大小比較
  • containsstartsWithendsWith:文字列検索
  • mode: "insensitive":大文字小文字を無視した検索(PostgreSQL)
  • ANDORNOT:論理演算子(配列で複数条件)
  • someeverynone:リレーション配列に対する条件

トランザクション

Sequential Transactions(配列スタイル)

// 複数クエリをまとめてトランザクション実行
// すべて成功すれば commit、一つでも失敗すれば rollback
// ※ User モデルに points: Int @default(0) フィールドがある前提
async function transferPoints(
  fromUserId: number,
  toUserId: number,
  points: number
): Promise<void> {
  // $transaction の引数は配列: PrismaPromise<T>[] を渡す
  const [deductResult, addResult] = await prisma.$transaction([
    prisma.user.update({
      where: { id: fromUserId },
      data:  { points: { decrement: points } },
    }),
    prisma.user.update({
      where: { id: toUserId },
      data:  { points: { increment: points } },
    }),
  ]);
  // deductResult・addResult は User 型
}

Interactive Transactions(関数スタイル)

// コールバックスタイル: 途中で条件分岐・throw が可能
async function createPostWithNotification(
  authorId: number,
  data: Prisma.PostCreateInput
): Promise<Post> {
  return prisma.$transaction(async (tx) => {
    // tx は PrismaClient と同じ API を持つ
    const author = await tx.user.findUnique({ where: { id: authorId } });
    if (!author) throw new Error("作成者が見つかりません");

    const post = await tx.post.create({
      data: { ...data, authorId },
    });

    await tx.notification.create({
      data: {
        userId:  authorId,
        message: `投稿「${post.title}」を作成しました`,
      },
    });

    return post; // Promise が resolve されれば commit
  });
  // throw されれば自動的に rollback
}
Interactive Transaction のタイムアウト
Interactive Transaction はデフォルト 5 秒でタイムアウトします。重い処理や複数クエリを実行する場合は { timeout: 10000 }(ms)をオプションで指定してください:prisma.$transaction(async (tx) => { ... }, { timeout: 10000 })。また、トランザクション内ではシングルトンの prisma を使わず、引数の tx のみを使うこと(デッドロック防止)。

Prisma エラーの型安全な処理

Prisma は独自のエラークラスを提供しています。これらを型で絞り込むことで、エラーコードに応じた処理を型安全に書けます。

import { Prisma } from "@prisma/client";

// ─── エラークラスの種類 ───
// Prisma.PrismaClientKnownRequestError    → DB制約違反・レコード未検出など
// Prisma.PrismaClientUnknownRequestError  → 不明なDBエラー
// Prisma.PrismaClientRustPanicError       → エンジン内部エラー(まれ)
// Prisma.PrismaClientInitializationError  → 接続失敗・環境変数未設定
// Prisma.PrismaClientValidationError      → クエリ引数の型エラー

async function createUserSafe(
  data: Prisma.UserCreateInput
): Promise<{ ok: true; user: User } | { ok: false; error: string }> {
  try {
    const user = await prisma.user.create({ data });
    return { ok: true, user };
  } catch (e) {
    if (e instanceof Prisma.PrismaClientKnownRequestError) {
      // error.code はエラーコード文字列(例: "P2002")
      switch (e.code) {
        case "P2002": // Unique constraint failed
          // e.meta?.target は制約対象のフィールド名の配列
          const fields = (e.meta?.target as string[])?.join(", ") ?? "不明";
          return { ok: false, error: `${fields} はすでに使用されています` };
        case "P2025": // Record not found
          return { ok: false, error: "レコードが見つかりません" };
        case "P2003": // Foreign key constraint failed
          return { ok: false, error: "関連するレコードが存在しません" };
        default:
          return { ok: false, error: `DBエラー (${e.code}): ${e.message}` };
      }
    }
    if (e instanceof Prisma.PrismaClientValidationError) {
      return { ok: false, error: "クエリの引数が不正です" };
    }
    throw e; // 不明なエラーは再 throw
  }
}
エラーコード 意味 発生例
P2002 Unique constraint 違反 同じ email が既に存在する
P2003 Foreign key 制約違反 存在しない authorId を指定
P2025 レコードが見つからない findUniqueOrThrowupdatedelete でレコードが存在しない
P2016 クエリ実行エラー 型の不一致など
P1001 DB接続失敗 DATABASE_URL が誤っている
P1002 DBタイムアウト ネットワーク遅延・DB負荷

エラーハンドリングの設計パターンはTypeScript エラーハンドリング完全ガイドも参照してください。

Raw クエリの型安全な実行

import { Prisma } from "@prisma/client";

// ─── $queryRaw ─── 型引数で戻り値型を指定
interface UserStats {
  role:  string;
  count: bigint; // PostgreSQL の COUNT() は bigint
}

async function getUserStats(): Promise<UserStats[]> {
  return prisma.$queryRaw<UserStats[]>`
    SELECT role, COUNT(*) as count
    FROM "User"
    GROUP BY role
    ORDER BY count DESC
  `;
}

// ─── Prisma.sql(テンプレートリテラル)で安全にパラメータを埋め込む ───
async function searchUsersByName(name: string): Promise<User[]> {
  // Prisma.sql を使うと SQL インジェクションを防げる
  return prisma.$queryRaw<User[]>(
    Prisma.sql`SELECT * FROM "User" WHERE name ILIKE ${`%${name}%`}`
  );
}

// ─── $executeRaw ─── 影響行数を返す(INSERT/UPDATE/DELETE)
async function archiveOldPosts(before: Date): Promise<number> {
  // 戻り値は number(bigint ではない)
  const result: number = await prisma.$executeRaw`
    UPDATE "Post" SET archived = true
    WHERE "createdAt" < ${before} AND published = false
  `;
  return result;
}
$queryRawUnsafe は SQL インジェクションに注意
$queryRawUnsafe("SELECT * FROM User WHERE id = " + id) のように文字列連結でパラメータを埋め込むと SQL インジェクションが発生します。必ず Prisma.sql`...`(タグ付きテンプレート)または $queryRaw`...` を使い、パラメータはテンプレート変数として渡してください。

PrismaClient 拡張(Extensions)

Prisma 4.7 以降で追加された Extensions を使うと、PrismaClient に独自のメソッドやミドルウェアを型安全に追加できます。

// lib/prisma.ts(Extensions 付き)
import { PrismaClient } from "@prisma/client";

const basePrisma = new PrismaClient();

// ─── $extends で独自メソッドを追加 ───
export const prisma = basePrisma.$extends({
  model: {
    user: {
      // ソフトデリート(deleted_at を設定するだけで実際には削除しない)
      async softDelete(id: number) {
        return basePrisma.user.update({
          where: { id },
          data:  { deletedAt: new Date() },
        });
      },
      // 公開中のユーザー一覧を取得するショートカット
      async findActive() {
        return basePrisma.user.findMany({
          where: { deletedAt: null },
        });
      },
    },
  },
  // ─── クエリミドルウェア(パフォーマンスログ)───
  query: {
    async $allOperations({ operation, model, args, query }) {
      const start  = performance.now();
      const result = await query(args);
      const end    = performance.now();
      if (end - start > 1000) {
        console.warn(`[Slow Query] ${model}.${operation}: ${Math.round(end - start)}ms`);
      }
      return result;
    },
  },
});

// 利用側では型補完が効く
// prisma.user.softDelete(1);   // OK
// prisma.user.findActive();    // OK
// prisma.post.softDelete(1);   // Error: post には softDelete が存在しない

Next.js / Express での実践パターン

リポジトリパターンで型を整理する

// repositories/userRepository.ts
import type { Prisma, User } from "@prisma/client";
import { prisma } from "@/lib/prisma";

// リポジトリが扱う型を一か所にまとめる
type UserWithPosts = Prisma.UserGetPayload<{
  include: { posts: { select: { id: true; title: true; published: true } } };
}>;

type CreateUserParams = Pick<Prisma.UserCreateInput, "email" | "name">;
type UpdateUserParams = Partial<Pick<Prisma.UserUpdateInput, "name" | "role">>;

export const userRepository = {
  // 一覧取得
  async findAll(): Promise<User[]> {
    return prisma.user.findMany({
      where:   { deletedAt: null },
      orderBy: { createdAt: "desc" },
    });
  },

  // ID で取得(投稿付き)
  async findByIdWithPosts(id: number): Promise<UserWithPosts | null> {
    return prisma.user.findUnique({
      where:   { id },
      include: { posts: { select: { id: true, title: true, published: true } } },
    });
  },

  // 作成
  async create(params: CreateUserParams): Promise<User> {
    return prisma.user.create({ data: params });
  },

  // 更新
  async update(id: number, params: UpdateUserParams): Promise<User> {
    return prisma.user.update({ where: { id }, data: params });
  },

  // 論理削除
  async softDelete(id: number): Promise<User> {
    return prisma.user.update({
      where: { id },
      data:  { deletedAt: new Date() },
    });
  },
};

Next.js Route Handler での利用

// app/api/users/route.ts
import { NextRequest, NextResponse } from "next/server";
import { userRepository } from "@/repositories/userRepository";
import { Prisma } from "@prisma/client";
import { z } from "zod";

const createUserSchema = z.object({
  email: z.string().email(),
  name:  z.string().min(1).optional(),
});

export async function GET(): Promise<NextResponse> {
  const users = await userRepository.findAll();
  return NextResponse.json(users);
}

export async function POST(request: NextRequest): Promise<NextResponse> {
  const parseResult = createUserSchema.safeParse(await request.json());
  if (!parseResult.success) {
    return NextResponse.json({ error: parseResult.error.flatten() }, { status: 400 });
  }

  try {
    const user = await userRepository.create(parseResult.data);
    return NextResponse.json(user, { status: 201 });
  } catch (e) {
    if (e instanceof Prisma.PrismaClientKnownRequestError && e.code === "P2002") {
      return NextResponse.json({ error: "このメールアドレスは既に使用されています" }, { status: 409 });
    }
    throw e;
  }
}

Zod によるリクエストバリデーションの詳細はTypeScript Zod 完全ガイドを、Route Handler の型定義はTypeScript Next.js ガイドを参照してください。

よくある質問

QPrismaClient の型(PrismaClient型)を関数の引数として受け取れますか?

Aできます。引数に PrismaClient 型を直接使うか、より厳密には Omit<PrismaClient, symbol> を使います。ただし Interactive Transaction のコールバックで受け取る txOmit<PrismaClient, "$connect" | "$disconnect" | "$on" | "$transaction" | "$use" | "$extends"> 型です。リポジトリ関数がトランザクション内で呼べるよう、引数型を柔軟に設計してください:function createPost(tx: Omit<PrismaClient, symbol>, data: ...)

QPrisma.UserGetPayloadUser 型の違いは?

AUser 型はモデルの全フィールドを含む基本型です(リレーション含まず)。Prisma.UserGetPayload<T>select / include の指定を型引数に取り、クエリの戻り値に合わせた正確な型を計算します。リレーションを含むデータを関数間で受け渡す場合は UserGetPayload を使いましょう。

QPrisma と Zod を組み合わせて入力バリデーションを行う方法は?

Azod-prisma-types(コミュニティパッケージ)を使うと、schema.prisma から Zod スキーマを自動生成できます。手動で定義する場合は Prisma.UserCreateInput の型を参考に z.object({ email: z.string().email(), name: z.string().optional() }) のようなスキーマを作り、parse 後の値を Prisma クエリに渡してください。

QBigInt 型(COUNT の結果など)を JSON にシリアライズするにはどうすればよいですか?

AJSON.stringify はデフォルトで BigInt を処理できません。カスタムリプレイサーを使ってください:JSON.stringify(data, (_, v) => typeof v === "bigint" ? v.toString() : v)。または Number(bigIntValue) に変換する(9007199254740991 以下なら精度の問題なし)か、$queryRaw の型定義で count: numberbigint ではなく)とキャストして扱うのも実務上よく使われます。

Qソフトデリートを実装したとき、削除済みレコードを常に除外するにはどうすればよいですか?

APrisma の Middleware(旧)または Extensions の query フック(推奨)を使います。$extends({ query: { $allModels: { async findMany({ args, query }) { args.where = { ...args.where, deletedAt: null }; return query(args); } } } }) のようにwhere 条件を自動付与できます。ただしすべての findMany に適用されるため、管理画面など削除済みも表示したい箇所では別の PrismaClient インスタンスを使ってください。

まとめ

Prisma は schema.prisma を唯一の型の源泉(Single Source of Truth)として、すべてのクエリ操作に正確な型を自動生成します。

機能 型定義のポイント
CRUD 操作 Prisma.UserCreateInput / UpdateInput / WhereInput を引数型に使う
リレーション include: true で戻り値型に自動追加。UserGetPayload<T> で型を事前定義
select 取得フィールドに応じて戻り値型が自動的に絞られる
フィルタ Prisma.PostWhereInput で型安全な where 条件を組み立て
トランザクション 配列スタイル(並列 Promise)/ コールバックスタイル(条件分岐・throw 可能)
エラー処理 instanceof Prisma.PrismaClientKnownRequestError でエラーコードを型安全に処理
Extensions $extends で独自メソッドを型補完付きで追加

Prisma の型システムを活用することで、DB とアプリケーション間のデータ型の不一致をコンパイル時に検出できます。TypeScript Express ガイドTypeScript Next.js ガイドブランド型ガイドと組み合わせることで、フルスタックの型安全なアプリケーションを構築できます。