A full-stack CRUD application for managing tasks with advanced search, filtering, and pagination. Built with Next.js, PostgreSQL, and Prisma with multi-tenant data isolation.
| Category | Stack |
|---|---|
| Frontend | Next.js 16, React 19, Tailwind CSS, Shadcn UI |
| Backend | Next.js Server Actions, API Routes |
| Database | Neon PostgreSQL, Prisma ORM |
| Authentication | Clerk (Multi-tenant) |
| CMS | Payload CMS (Admin Dashboard) |
| Language | TypeScript |
✅ Full CRUD Operations - Create, read, update, delete tasks
✅ Multi-Tenant Isolation - Each user sees only their own tasks
✅ Database Relations - Todo-to-Category one-to-many relationships
✅ Search & Filter API - Text search + category filtering via /api/todos
✅ URL Query State - Persistent filtering (?search=X&category=Y&page=Z)
✅ Pagination - 5 tasks per page for optimized performance
✅ Data Access Layer - Separated query logic in lib/data/todos.ts for reusability
✅ FormData Server Actions - Native form submission to server actions
✅ Admin Dashboard - Payload CMS for collection management
✅ Type Safety - Full TypeScript + auto-generated types
Currently no keyboard shortcuts implemented. Add custom shortcuts for:
Ctrl + KorCmd + K- Quick task searchCtrl + NorCmd + N- New taskEsc- Close modals/dropdowns
Architecture Decisions:
- Next.js App Router - For file-based routing and server components
- Server Actions - For secure CRUD operations with built-in auth checks
- FormData API - Server actions accept native FormData from HTML forms
- Prisma ORM - For type-safe database queries and relations
- Data Access Layer - Separated query logic in
lib/data/for reusability - URL-based State - Query parameters (
searchParams) instead of context for filtering - Pagination - Prisma
take+skipfor chunked data loading - Payload CMS - Separate admin panel with GraphQL + REST APIs
Data Flow:
HTML Form (native FormData)
↓
Server Action (createTodo)
↓
Data Access Layer (lib/data/todos.ts)
↓
Prisma ORM Query
↓
PostgreSQL Database
↓
revalidatePath() → UI Re-renders
Component Flow:
SearchInput (client) → Updates URL params
↓
page.tsx (server) → Reads searchParams
↓
Hero.tsx (server) → Imports data layer functions
↓
getFilteredTodos() → Builds Prisma query
↓
Database → Returns paginated filtered results
Key Implementation:
- Data Access:
getFilteredTodos()andgetCategories()inlib/data/todos.ts - FormData:
createTodo(formData: FormData)extracts fields using.get() - Relations: Todo ↔ Category via foreign key (optional)
- Filtering: Dynamic Prisma where clauses (search + category)
- Pagination: Calculate
skip = (page - 1) * 5 - Security: Clerk auth + userId filtering on every query
git clone <repo-url> && cd <project>
npm installCreate .env.local:
DATABASE_URL=postgresql://...
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=pk_test_...
CLERK_SECRET_KEY=sk_test_...
PAYLOAD_SECRET=random-key
PAYLOAD_PUBLIC_SERVER_URL=http://localhost:3000npx payload generate:types
~If this gives error, set type to module in package.json
npx prisma db push
npx prisma generatenpm run dev| URL | Purpose |
|---|---|
| localhost:3000 | Main dashboard |
| localhost:3000/admin | Admin panel |
| localhost:3000/api/todos | Search API |
See PROJECT_EXP.md for detailed file structure, architecture, and feature explanations.