概要
Prisma の orderBy はオブジェクト 1 つ(単一フィールド)、配列(複数フィールド・優先順位あり)、ネストオブジェクト(リレーション先フィールド)の 3 形式をサポートする。nulls: "first" | "last" で null 値の並び順も制御できる。
インストール
npm install prisma @prisma/client
npx prisma init
実装
Prisma スキーマ
// prisma/schema.prisma
model Post {
id Int @id @default(autoincrement())
title String
viewCount Int @default(0)
publishedAt DateTime?
createdAt DateTime @default(now())
author User @relation(fields: [authorId], references: [id])
authorId Int
}
model User {
id Int @id @default(autoincrement())
name String
email String @unique
createdAt DateTime @default(now())
posts Post[]
}
単一フィールドで並び替え
// lib/queries/posts.ts
import { prisma } from "@/lib/prisma";
// createdAt の新しい順
export async function getLatestPosts() {
return prisma.post.findMany({
orderBy: { createdAt: "desc" },
});
}
// viewCount の多い順
export async function getPopularPosts() {
return prisma.post.findMany({
orderBy: { viewCount: "desc" },
});
}
// title のアルファベット順
export async function getPostsAlphabetical() {
return prisma.post.findMany({
orderBy: { title: "asc" },
});
}
複数フィールドで並び替え(配列形式)
// 複数フィールドを指定する場合は配列で渡す(先頭のキーが優先)
export async function getPostsByViewThenDate() {
return prisma.post.findMany({
orderBy: [
{ viewCount: "desc" }, // 1番目: viewCount の多い順
{ createdAt: "desc" }, // 2番目: 同じ viewCount なら新しい順
],
});
}
リレーション先フィールドで並び替え
// 著者名のアルファベット順でソート(リレーション先)
export async function getPostsByAuthorName() {
return prisma.post.findMany({
orderBy: {
author: { name: "asc" },
},
include: { author: { select: { name: true } } },
});
}
// 著者の投稿数順でソート(集計フィールド)
export async function getPostsByAuthorPostCount() {
return prisma.post.findMany({
orderBy: {
author: {
posts: { _count: "desc" },
},
},
include: { author: { select: { name: true } } },
});
}
null 値の並び順を制御する
// publishedAt が null のものを最後に置く(デフォルトは DB によって異なる)
export async function getPostsByPublishedAt() {
return prisma.post.findMany({
orderBy: {
publishedAt: { sort: "desc", nulls: "last" },
},
});
}
// publishedAt が null のものを先頭に置く
export async function getDraftPostsFirst() {
return prisma.post.findMany({
orderBy: {
publishedAt: { sort: "asc", nulls: "first" },
},
});
}
Route Handler でソートパラメータを受け取る
// app/api/posts/route.ts
import { NextResponse } from "next/server";
import { prisma } from "@/lib/prisma";
import type { Prisma } from "@prisma/client";
type AllowedSortField = "createdAt" | "viewCount" | "title";
type SortOrder = "asc" | "desc";
const ALLOWED_FIELDS: AllowedSortField[] = ["createdAt", "viewCount", "title"];
export async function GET(request: Request) {
const { searchParams } = new URL(request.url);
const sortField = searchParams.get("sort") ?? "createdAt";
const sortOrder = searchParams.get("order") === "asc" ? "asc" : "desc";
const field: AllowedSortField = (ALLOWED_FIELDS as string[]).includes(sortField)
? (sortField as AllowedSortField)
: "createdAt";
const orderBy: Prisma.PostOrderByWithRelationInput = {
[field]: sortOrder as SortOrder,
};
const posts = await prisma.post.findMany({
orderBy,
select: { id: true, title: true, viewCount: true, createdAt: true },
});
return NextResponse.json({ posts });
}
ポイント
- 単一フィールドのソートはオブジェクト
{ field: "asc" | "desc" }で指定する。複数フィールドの優先順位付きソートは配列[{ field1: "desc" }, { field2: "asc" }]を使う nulls: "first" | "last"で null 値の並び順を制御できる。DB によってデフォルト挙動が異なるため、期待する順序がある場合は明示的に指定する- リレーション先フィールドのソートはネストオブジェクト
{ relation: { field: "asc" } }で表現する。_countを使うとリレーションの件数でソートできる - Route Handler でソートキーを受け取るときは allowlist で許可フィールドを制限する。ユーザー入力を直接
orderByに渡すと任意フィールドアクセスのリスクがある prisma-pagination-queryとの組み合わせ:findManyにorderByとskip/takeの両方を指定できる。ソート順を固定してページネーションすることで、ページをまたいで重複や欠落が起きないようにする