© TOOLS BOX — Next.js / React / TypeScript コードサンプル集

サンプルガイド
←サンプル一覧
nextjscrud

Prisma でリレーションを含むクエリを書く(include / select / ネスト)

Prisma の include / select を使った 1対多・多対多のリレーション取得パターンと、ネストしたフィルタ・ソート・集計(_count)の例。

難易度: 中級·更新: 2026-04-18·
prisma

対応バージョン

nextjs 15react 19prisma 5

前提環境

Prisma の基本 CRUD(prisma-crud-api)とスキーマのリレーション定義を理解していること

概要

Prisma の include / select を使ったリレーションクエリパターン。1対多(User → Post)と多対多(Post ↔ Tag)の取得・ネストフィルタ・ソート・_count 集計を示す。

インストール

npm install prisma @prisma/client
npx prisma init

実装

スキーマ定義(1対多 + 多対多)

// prisma/schema.prisma
datasource db {
  provider = "sqlite"
  url      = "file:./dev.db"
}

generator client {
  provider = "prisma-client-js"
}

model User {
  id    Int    @id @default(autoincrement())
  name  String
  email String @unique
  posts Post[] // 1対多: User → Post
}

model Post {
  id          Int       @id @default(autoincrement())
  title       String
  published   Boolean   @default(false)
  authorId    Int
  author      User      @relation(fields: [authorId], references: [id])
  tags        Tag[]     // 多対多: Post ↔ Tag(暗黙の中間テーブル)
  createdAt   DateTime  @default(now())
}

model Tag {
  id    Int    @id @default(autoincrement())
  name  String @unique
  posts Post[] // 多対多の逆側
}

Prisma クライアント(シングルトン)

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

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

export const prisma =
  globalForPrisma.prisma ?? new PrismaClient({ log: ["query"] });

if (process.env.NODE_ENV !== "production") globalForPrisma.prisma = prisma;

include: 関連データをまとめて取得

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

// 1対多: ユーザーとその投稿を一括取得
export async function getUserWithPosts(userId: number) {
  return prisma.user.findUnique({
    where: { id: userId },
    include: {
      posts: {
        where: { published: true },        // 公開済みのみ
        orderBy: { createdAt: "desc" },    // 新しい順
        take: 10,                          // 最大10件
      },
    },
  });
}

// 多対多: 投稿とタグを一括取得
export async function getPostWithTags(postId: number) {
  return prisma.post.findUnique({
    where: { id: postId },
    include: {
      author: true,  // 著者情報も含める
      tags: true,    // 全タグを含める
    },
  });
}

select: 必要なフィールドだけ取得

// lib/queries.ts(続き)

// select: id / title / author.name だけを取得(over-fetching を避ける)
export async function getPostSummaries() {
  return prisma.post.findMany({
    where: { published: true },
    select: {
      id: true,
      title: true,
      createdAt: true,
      author: {
        select: { name: true }, // 著者名だけ
      },
      tags: {
        select: { name: true }, // タグ名だけ
      },
    },
    orderBy: { createdAt: "desc" },
  });
}

_count: 集計カウントをクエリに含める

// lib/queries.ts(続き)

// 投稿数を含むユーザー一覧
export async function getUsersWithPostCount() {
  return prisma.user.findMany({
    include: {
      _count: {
        select: { posts: true }, // postsの件数
      },
    },
    orderBy: {
      posts: { _count: "desc" }, // 投稿数降順
    },
  });
}

// タグ付き投稿の件数でソート
export async function getTagsWithPostCount() {
  return prisma.tag.findMany({
    include: {
      _count: {
        select: { posts: true },
      },
    },
    orderBy: {
      posts: { _count: "desc" },
    },
  });
}

ネストフィルタ: 条件を関連モデルにかける

// lib/queries.ts(続き)

// 特定タグを持つ投稿を取得
export async function getPostsByTag(tagName: string) {
  return prisma.post.findMany({
    where: {
      published: true,
      tags: {
        some: { name: tagName }, // 少なくとも1つのタグが一致
      },
    },
    include: { author: true, tags: true },
    orderBy: { createdAt: "desc" },
  });
}

// 投稿が1件以上あるユーザーのみ取得
export async function getActiveUsers() {
  return prisma.user.findMany({
    where: {
      posts: {
        some: { published: true }, // 公開済み投稿が1件以上
      },
    },
    include: {
      _count: { select: { posts: true } },
    },
  });
}

Route Handler での使用例

// app/api/users/[id]/posts/route.ts
import { NextResponse } from "next/server";
import { getUserWithPosts } from "@/lib/queries";

type Params = { params: Promise<{ id: string }> };

export async function GET(_req: Request, { params }: Params) {
  const { id } = await params;
  const user = await getUserWithPosts(Number(id));

  if (!user) {
    return NextResponse.json({ error: "Not found" }, { status: 404 });
  }

  return NextResponse.json(user);
}

ポイント

  • include は関連モデルのすべてのフィールドを取得する。select は必要なフィールドだけを指定する。大量データを扱う場合は select で over-fetching を防ぐ
  • include と select は同一クエリで同時に使えない。どちらかを選ぶ(関連モデルのフィールドを絞りたいなら select 内でネストして指定する)
  • ネストフィルタの演算子: some(1件以上一致)/ every(全件一致)/ none(0件一致)。関連モデルを条件にした絞り込みで使う
  • _count で関連モデルの件数を集計できる。orderBy にも使えるため「投稿数が多い順」などのソートが 1 クエリで完結する
  • 多対多の暗黙の中間テーブルは Prisma が自動管理する。明示的な中間テーブルが必要な場合(追加フィールドを持つ場合)は @relation でモデルを定義する

注意点

prisma-crud-api は単一モデルの基本 CRUD。prisma-soft-delete は論理削除パターン。これは include / select によるリレーション取得・ネストフィルタ・_count 集計に特化。1対多(User → Post)と多対多(Post ↔ Tag)の両方のパターンを示す。

関連サンプル

同じテーマや技術スタックを使った実装例

  • Prisma と Route Handler で CRUD API を実装する

    Prisma Client の findMany / create / update / delete を Route Handler から呼び出して CRUD API を構築する例。スキーマ定義からマイグレーション・型安全なクエリまでを示す。

  • Prisma で skip / take を使ったページネーションクエリを実装する

    Prisma の skip / take / orderBy を使ったオフセットページネーションと、cursor ベースのページネーションのパターン、Route Handler との組み合わせ例。

  • Prisma で論理削除(soft delete)を実装する

    deletedAt フィールドを使った論理削除パターンを Prisma で実装し、通常クエリから削除済みレコードを自動除外する例。

  • Prisma の orderBy で並び替えクエリを実装する

    Prisma の orderBy による単一フィールド・複数フィールド・リレーション先フィールドでの並び替えパターンと、null 値の扱い(nulls first / last)の例。

  • Next.js Route Handler で REST API(CRUD)を実装する

    app/api/ 配下の Route Handler を使い、GET / POST / PUT / DELETE を備えた REST API を実装する例。インメモリデータを使ってシンプルに CRUD を示す。

関連仕様

このサンプルを理解するのに役立つ仕様や概念

  • FrameworkNext.jsReact ベースのフルスタックフレームワーク。SSR・SSG・App Router・API Routes を提供する。
  • API / OptionincludePrisma クエリでリレーション先モデルを一緒に取得するオプション。JOIN 相当の操作をシンプルに書ける。
  • API / OptionselectPrisma クエリで取得するフィールドを明示的に指定するオプション。不要なフィールドを除外してover-fetching を防ぐ。
  • API / OptionwherePrisma クエリで絞り込み条件を指定するオプション。完全一致・部分一致・複数条件(AND / OR)など様々なフィルタを組み立てられる。
←サンプル一覧に戻る