Join our FREE personalized newsletter for news, trends, and insights that matter to everyone in America

Newsletter
New

Building A Full-stack Data Grid App With Next.js, Prisma, And Ag Grid

Card image cap

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} />;  
}  

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);  
}  

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  
}  

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 } });  

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] });  
  }  
}  

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}  
/>  

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