DEV Community

Malloc72P
Malloc72P

Posted on

Building a Full-Stack Data Grid App with Next.js, Prisma, and AG Grid

TL;DR

  • Build a complete CRUD data grid app using Next.js (Server Components + Server Actions), Prisma ORM (type-safe DB access), and AG Grid (inline editing, sorting, virtual scroll).
  • Server Components fetch data on the server; Server Actions let clients call server-side functions like regular async functions — no REST API needed.
  • Prisma provides auto-generated TypeScript types from your schema, making DB queries fully type-safe.
  • AG Grid's applyTransaction API enables optimistic updates — the UI updates instantly, and rolls back on failure.

Key Concepts

Next.js: Server Components & Server Actions

Server Components run on the server and can call business logic directly:

// app/page.tsx — Server Component
export default async function Home() {
  const orders = await getOrders(); // Direct DB call
  return <OrderGrid orders={orders} />;
}
Enter fullscreen mode Exit fullscreen mode

Server Actions let client components call server functions with a simple 'use server' directive:

'use server';

export async function createOrderAction(input: CreateOrderInput) {
  return createOrder(input);
}
Enter fullscreen mode Exit fullscreen mode

The client calls it like a normal function — Next.js handles the HTTP layer internally.

Prisma ORM: Type-Safe Database Access

Define your schema in Prisma's DSL, then generate a fully typed client:

model Order {
  id    Int @id @default(autoincrement())
  price Int
  qty   Int
}
Enter fullscreen mode Exit fullscreen mode

CRUD operations are concise and type-safe:

// Create
await prisma.order.create({ data: { price, qty } });

// Read with filters
await prisma.order.findMany({
  where: { price: { gt: 1000 } },
  skip: 20,
  take: 20,
});

// Update
await prisma.order.update({ where: { id }, data: { price, qty } });

// Delete
await prisma.order.delete({ where: { id } });
Enter fullscreen mode Exit fullscreen mode

AG Grid: Optimistic Updates with applyTransaction

The optimistic update pattern — update the UI first, then sync with the server:

async function handleCreate() {
  const tempId = -Date.now();
  const tempRow: Order = { id: tempId, price: 0, qty: 0 };

  // Optimistic: add temp row immediately
  gridRef.current?.api.applyTransaction({ add: [tempRow] });

  try {
    const created = await createOrderAction({ price: 0, qty: 0 });
    // Replace temp row with real data
    gridRef.current?.api.applyTransaction({
      remove: [tempRow],
      add: [created],
    });
  } catch {
    // Rollback on failure
    gridRef.current?.api.applyTransaction({ remove: [tempRow] });
  }
}
Enter fullscreen mode Exit fullscreen mode

The same pattern applies to update (rollback cell value on failure) and delete (re-add the row on failure).

Code Examples

Setting up the AG Grid component with inline editing and a delete button:

const colDefs: ColDef<Order>[] = useMemo(() => [
  { field: 'id', editable: false, sort: 'asc' },
  { field: 'price', editable: true },
  { field: 'qty', editable: true },
  {
    headerName: '',
    width: 100,
    cellRenderer: (p: ICellRendererParams<Order>) => (
      <button onClick={() => handleDelete(p.data!)}>Delete</button>
    ),
  },
], []);

<AgGridReact
  ref={gridRef}
  rowData={orders}
  columnDefs={colDefs}
  getRowId={(p) => String(p.data.id)}
  onCellValueChanged={handleCellValueChanged}
/>
Enter fullscreen mode Exit fullscreen mode

This is a summary of my original 3-part series written in Korean.
For the full articles with step-by-step setup instructions, check out the originals:

👉 Part 1: Next.js Basics
👉 Part 2: Prisma ORM Setup & CRUD
👉 Part 3: AG Grid & Optimistic Updates

Top comments (0)