A modern task management application built with Next.js, TypeScript, and Effect. Features drag-and-drop task organization, multiple user profiles, filtering, and a clean, Linear-inspired design.
- Framework: Next.js 15 with App Router
- Language: TypeScript
- Database: PostgreSQL via Supabase
- ORM: Prisma
- Auth: Supabase Auth
- UI: Tailwind CSS + Headless UI
- State: TanStack Query
- Effect: Typed errors, composability
- Testing: Vitest for route handler integration tests
Before getting started, you'll need to install these tools.
- Node
- pnpm
- Docker Desktop
- Supabase CLI
brew install pnpm
brew install --cask docker
brew install supabase/tap/supabasepnpm installMake sure Docker Desktop is running, then:
supabase startpnpm env:setupThis will automatically copy .env.example to .env and populate it with your
Supabase credentials.
If the automated setup fails, you can manually copy and configure:
cp .env.example .env
supabase status # Shows your credentials
# Copy the anon key and service_role key into .envMigrate and seed your local development database:
pnpm db:migrate
pnpm db:seedpnpm devVisit http://localhost:3000. Create a new account or log in with:
Email: demo@example.com
Password: password123
The app includes integration tests that verify API endpoints, security, and database operations.
First, make sure Docker Desktop is running, then start up a separate Supabase instance for testing.
# Start test Supabase instance
pnpm test:supabasepnpm env:setup:testThis will automatically copy .env.test.example to .env.test and populate it
with your test Supabase credentials.
If the automated setup fails, you can manually copy and configure:
cp .env.test.example .env.test
supabase status --workdir supabase-test # Shows your test credentials
# Copy the anon key and service_role key into .env.test# Apply migrations to test database
pnpm test:db:migrate# Start test Supabase instance (if it's not already running)
pnpm test:supabase
# Start test server
pnpm test:server
# Run tests once
pnpm test:run
# Run tests in watch mode
pnpm test
# Run tests with Vitest UI
pnpm test:uiThis project uses Effect strategically in API route handlers for error handling and service composition. Each route handler follows a consistent pattern:
- Composes service layer Effects using Effect gen syntax
- Transforms errors into a unified format
- Converts the Effect into a NextResponse
This approach eliminates try-catch blocks, provides type-safe error handling, and ensures consistent API responses across all endpoints.
This project uses custom AsyncResult and Effect's Option types to explicitly model all possible states in the UI layer. This pattern prevents common bugs by making impossible states unrepresentable. We use these patterns sparingly where they make the code more clear and maintainable.
AsyncResult handles async operations with three explicit states:
Loading- Operation in progressOk(value)- Success with dataErr(error)- Failure with error details
Option (from Effect) explicitly handles potentially absent values:
Some(value)- Value existsNone- Value is absent
// Instead of: Session | null | undefined
// We use: AsyncResult<Option<Session>>
AsyncResult.match(sessionState, {
onLoading: () => <Spinner />,
onErr: (error) => <ErrorMessage error={error} />,
onOk: (maybeSession) =>
Option.match(maybeSession, {
onNone: () => <LoginPrompt />,
onSome: (session) => <AuthenticatedApp session={session} />,
}),
})This pattern makes managing asynchronous data and optional values easier to reason about and less error-prone.
βββ src/app/ # Next.js App Router pages
β βββ api/ # Route Handlers
β βββ app/ # Application UI
βββ core/ # Shared business logic
β βββ api/service/ # Effect-based service layer
β βββ lib/ # Utility functions
β βββ prisma/ # Database client
βββ prisma/ # Database schema and migrations
βββ test/ # Integration test suite
βββ src/ui/ # Reusable UI components
# Development
pnpm dev # Start development server
pnpm build # Build for production
pnpm start # Start production server
# Database
pnpm db:migrate # Run migrations
pnpm db:seed # Seed database
pnpm db:reset # Reset development database and seed data
# Environment Setup
pnpm env:setup # Setup .env with Supabase credentials
pnpm env:setup:test # Setup .env.test with test Supabase credentials
# Code Quality
pnpm lint # Run ESLint
pnpm typecheck # Run TypeScript checks
pnpm format # Format code with Prettier
pnpm format:md # Format README with markdownlint
pnpm prep # Run format, typecheck, and lint
# Testing
pnpm test # Run tests in watch mode
pnpm test:run # Run tests once
pnpm test:ui # Run tests with Vitest UI
pnpm test:server # Start test server on port 3001
pnpm test:supabase # Start test Supabase instance
pnpm test:supabase:stop # Stop test Supabase instance
pnpm test:supabase:status # Check test Supabase status
pnpm test:start # Start test Supabase and server together
pnpm test:db:push # Push schema to test database
pnpm test:db:migrate # Apply migrations to test database
pnpm test:db:reset # Reset test database
# Production
pnpm prod:prisma:migrate # Run migrations on production databaseThere are a few features I would have liked to build that I didn't have time for:
- CRUD operations for categories. Right now you just get "Creative", "Health", and "Shopping".
- CRUD operations for profiles. Right now you just get "Work" and "Personal".
- UI treatment for overdue or due soon tasks.
- e2e tests with Playwright.
- Keyboard interaction support for drag-and-drop.
- Investigate using localStorage to store the active profile id vs. cookies. Or perhaps managing the local state with TanStack Query instead of nanostores.
- Handle edge cases with task sorting, such as reordering tasks in a smarter way while filtering, mostly for moves to the top of the list.
- Implement proper race condition handling for drag-and-drop on slow network connections. Currently, rapid drag operations can cause UI inconsistencies.
- Enable filtering by mulitple task stasuses and categories at once.
- Improve API route payload validation with Effect Schema.
- Implement optimistic updates for task creation. I tried but ran into issues with UI jank.
- Dark mode.
- Use Suspense for loading states.
MIT License
Built with β€οΈ