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

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

Prisma で複数カラムをまたぐキーワード検索クエリを実装する

タイトル・本文・タグなど複数カラムをまたぐキーワード検索を Prisma の OR 条件と fulltext 検索で実装するパターン。単語分割・スコアリングなしの実用的な部分一致パターンを示す。

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

対応バージョン

nextjs 15react 19prisma 5

前提環境

Prisma の基本的な CRUD 操作と Next.js Server Component のデータフェッチを理解していること

概要

単一のキーワードでタイトル・サマリー・タグなど複数カラムを横断検索する Prisma クエリを実装する。OR 条件と contains / mode: "insensitive" の組み合わせで部分一致を実現し、空文字・undefined を where から除外する安全なパターンを示す。

インストール

npm install prisma @prisma/client
npx prisma init

実装

Prisma スキーマ(例)

// prisma/schema.prisma
model Sample {
  id         String   @id @default(cuid())
  title      String
  summary    String
  tags       String[]
  category   String
  difficulty String
  createdAt  DateTime @default(now())
  updatedAt  DateTime @updatedAt
}

キーワード検索クエリ関数

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

type SearchParams = {
  q?: string;
  category?: string;
  difficulty?: string;
};

export async function searchSamples(params: SearchParams) {
  const { q, category, difficulty } = params;

  const keywordCondition = q?.trim()
    ? {
        OR: [
          { title: { contains: q, mode: "insensitive" as const } },
          { summary: { contains: q, mode: "insensitive" as const } },
          { tags: { has: q } },
        ],
      }
    : undefined;

  return prisma.sample.findMany({
    where: {
      ...keywordCondition,
      category: category || undefined,
      difficulty: difficulty || undefined,
    },
    orderBy: { updatedAt: "desc" },
  });
}

複数キーワード(スペース区切り AND 検索)

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

function buildKeywordConditions(q: string) {
  const keywords = q.trim().split(/\s+/).filter(Boolean);
  if (keywords.length === 0) return undefined;

  // 各キーワードについて OR を作り、それを AND(配列)で結合
  return keywords.map((kw) => ({
    OR: [
      { title: { contains: kw, mode: "insensitive" as const } },
      { summary: { contains: kw, mode: "insensitive" as const } },
    ],
  }));
}

export async function searchSamplesMultiKeyword(q?: string) {
  const AND = q ? buildKeywordConditions(q) : undefined;

  return prisma.sample.findMany({
    where: { AND },
    orderBy: { updatedAt: "desc" },
  });
}

Server Component での使用例

// app/samples/page.tsx(抜粋)
import { searchSamples } from "@/lib/db/searchSamples";

type Props = {
  searchParams: Promise<Record<string, string | string[] | undefined>>;
};

export default async function SamplesPage({ searchParams }: Props) {
  const params = await searchParams;
  const q = typeof params.q === "string" ? params.q : undefined;
  const category = typeof params.category === "string" ? params.category : undefined;
  const difficulty = typeof params.difficulty === "string" ? params.difficulty : undefined;

  const samples = await searchSamples({ q, category, difficulty });

  return (
    <div>
      <p className="text-sm text-gray-500">{samples.length} 件</p>
      {/* ...カード一覧 */}
    </div>
  );
}

count との並行取得

// lib/db/searchSamplesWithCount.ts
import { prisma } from "@/lib/prisma";
import type { Prisma } from "@prisma/client";

export async function searchSamplesWithCount(q?: string) {
  const where: Prisma.SampleWhereInput = q?.trim()
    ? {
        OR: [
          { title: { contains: q, mode: "insensitive" } },
          { summary: { contains: q, mode: "insensitive" } },
        ],
      }
    : {};

  const [total, items] = await Promise.all([
    prisma.sample.count({ where }),
    prisma.sample.findMany({ where, orderBy: { updatedAt: "desc" } }),
  ]);

  return { total, items };
}

ポイント

  • q?.trim() が空文字や undefined のとき keywordCondition を undefined にすることで、...spread 時に OR 条件が where に混入しない。Prisma は undefined フィールドをクエリ条件から除外する
  • mode: "insensitive" で大文字・小文字を区別しない検索になる。PostgreSQL では ILIKE に変換される。SQLite では未対応のため使用不可
  • tags: { has: q } は PostgreSQL の配列型カラムを対象とした完全一致検索。部分一致が必要な場合は hasSome や別テーブル設計を検討する
  • スペース区切りの複数キーワードは AND 配列(各キーワードの OR を AND 結合)で実装する。「全キーワードを含む」という自然な AND 検索になる
  • Promise.all で件数と一覧を並列取得することで、1件だけカウントするために全件フェッチするコストを回避できる
  • Prisma.SampleWhereInput 型を明示することで、where オブジェクトの型安全性を保証し、条件の組み立てミスをコンパイル時に検出できる

注意点

prisma-filter-query はカテゴリ・難易度などの絞り込み条件の動的組み立て。prisma-sort-query はソート条件の動的組み立て。これは単一キーワードで複数カラムを OR 検索するフルテキスト的なパターンに特化。

関連サンプル

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

  • Prisma でフィルタ条件を動的に組み立ててクエリを実行する

    URL クエリパラメータから受け取った検索条件を Prisma の where 句に動的に組み立て、キーワード・カテゴリ・難易度などの複合フィルタクエリを実行するパターン。

  • Next.js Route Handler でクエリパラメータを使ってソートを実装する

    sort キーと order(asc/desc)をクエリパラメータで受け取り、Route Handler でデータをソートして返すパターンと、クライアント側のソートボタン UI を示す例。

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

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

  • Next.js Route Handler でサーバーサイドキーワード検索を実装する

    クエリパラメータで受け取ったキーワードを Route Handler でサーバーサイドフィルタし、ページネーション付きで返す例。クライアントのデバウンス入力と合わせた構成も示す。

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

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

関連仕様

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

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