gridcn1.2.2

Prisma

The grid's server fetch contract with Prisma. The same shape as a Prisma query, operator mapping included.

Recipe

Recipe: not a shadcn registry example. It requires a server.

Prisma's where delegates line up almost one-to-one with the grid's filter operators, so the mapping is a table, not a switch:

// app/api/grid/route.ts
import { Prisma } from "@prisma/client";
import { prisma } from "~/lib/prisma";

// grid operator → Prisma delegate; the "" pair is the empty/notEmpty values
const OPS: Record<string, string> = {
  contains: "contains",
  notContains: "notContains",
  equals: "equals",
  notEquals: "notEquals",
  startsWith: "startsWith",
  endsWith: "endsWith",
  gt: "gt",
  gte: "gte",
  lt: "lt",
  lte: "lte",
  empty: "equals",
  notEmpty: "notEquals",
};

const ALLOWED = new Set(["name", "email", "role", "score"]);

export async function POST(req: Request) {
  const { page, pageSize, sorts, filters, search } = await req.json();

  const where: Prisma.UserWhereInput = {
    ...(search
      ? { OR: [{ name: { contains: search, mode: "insensitive" } }, { email: { contains: search, mode: "insensitive" } }] }
      : {}),
    ...(filters.length
      ? {
          AND: filters.map((f) => {
            if (!ALLOWED.has(f.columnId)) throw new Error("unknown column");
            const value = f.operator === "empty" ? "" : f.operator === "notEmpty" ? { not: "" } : f.value;
            return { [f.columnId]: { [OPS[f.operator]]: value } };
          }),
        }
      : {}),
  };

  const [total, rows] = await Promise.all([
    prisma.user.count({ where }),
    prisma.user.findMany({
      where,
      orderBy: sorts.length
        ? sorts.map((s) => ({ [s.columnId]: s.direction }))
        : [{ id: "asc" }],
      skip: (page - 1) * pageSize,
      take: pageSize,
    }),
  ]);

  return Response.json({ rows, total });
}

Notes:

  • mode: "insensitive" gives the case-insensitive contains the grid's quick search implies. Drop it for exact semantics.
  • isAnyOf maps to in: f.value, and isNoneOf maps to notIn: f.value. Add them to the table above when you need them.
  • The client is the server-side example unchanged. Edits become prisma.user.update in the same write-through handler.

Shareable state

Same as the Drizzle recipe: DataGridUrlState moves the view state to the URL, and this route reads it from the query string instead of a JSON body.

On this page