
🔗 check the GitHub repository for source code.
Tech Stack
- Next.js 16 + React 19 — App Router with Server Actions
- Prisma 7 — ORM for PostgreSQL with custom output path and multi-file schemas
- @prisma/adapter-pg — native
pgdriver adapter for Prisma - Neon — serverless PostgreSQL hosted via Vercel integration
- @hello-pangea/dnd — accessible drag-and-drop for task reordering
- Tailwind CSS 4 — utility-first styling
- next-themes — dark / light mode switching
How It Works
$transaction that updates every affected row in one round-trip.
Database Schema (Prisma)
// prisma/models/todo.prisma
model Todo {
id String @id @default(cuid())
text String
completed Boolean @default(false)
order Int
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}Server Actions
// actions/todo.ts
export async function addTodo(formData: FormData) {
const text = formData.get('text') as string;
if (!text || text.trim() === '') return;
const minOrderTodo = await prisma.todo.findFirst({
orderBy: { order: 'asc' },
select: { order: true },
});
const newOrder = minOrderTodo ? minOrderTodo.order - 1 : 0;
await prisma.todo.create({ data: { text, order: newOrder } });
revalidatePath('/');
}
export async function reorderTodos(ids: string[]) {
const transactions = ids.map((id, index) =>
prisma.todo.update({ where: { id }, data: { order: index } })
);
await prisma.$transaction(transactions);
revalidatePath('/');
}Drag-and-Drop Reordering
// components/todo-list.tsx
const onDragEnd = async (result: DropResult) => {
if (!result.destination) return;
const items = Array.from(todos);
const [reorderedItem] = items.splice(result.source.index, 1);
items.splice(result.destination.index, 0, reorderedItem);
setTodos(items); // optimistic update
await reorderTodos(items.map((i) => i.id));
};Running the Project
npm install
# set DATABASE_URL in .env (get it from Neon dashboard or Vercel integration)
npx prisma migrate dev
npm run devConclusion
pg adapter keeps the database layer clean and type-safe, while @hello-pangea/dnd adds a polished UX touch — with optimistic UI updates and a single batched transaction for persistence.