A small full-stack task management application built as a hiring take-home assignment.
- Frontend: Vue 3 + Vite (Composition API, Vue Router, Axios)
- Backend: .NET 8 Minimal API
- Database: SQLite via Entity Framework Core
- Validation: FluentValidation on the backend, form-level validation on the frontend
- Packaging: Docker + docker-compose for a one-command startup
- Login with hardcoded demo credentials (
admin/123456) - Session stored in
localStorage; route guards redirect unauthenticated users - Task CRUD: list, details, create, edit, delete (with confirm prompt)
- Filter by status, search by title keyword
- Server-side pagination with page / page size query params
- Request validation with structured 400 responses
- Global exception handling middleware
- Seed data so the app has content on first run
- Responsive layout that collapses gracefully on mobile
task-management-app/
├── backend/
│ ├── TaskManagement.Api/
│ │ ├── Data/ # AppDbContext + seeder
│ │ ├── Extensions/ # Service registration helpers
│ │ ├── Features/Tasks/ # Endpoints, DTOs, validators
│ │ ├── Middleware/ # Global exception handler
│ │ ├── Migrations/ # EF Core migrations
│ │ ├── Models/ # TaskItem entity
│ │ └── Program.cs
│ └── Dockerfile
├── frontend/
│ ├── src/
│ │ ├── components/ # Shared UI (header, etc.)
│ │ ├── modules/
│ │ │ ├── auth/ # Login page + session helpers
│ │ │ └── tasks/ # Pages, components, composables
│ │ ├── router/ # Vue Router + guards
│ │ ├── services/ # axios client + tasks service
│ │ └── assets/ # Global styles
│ ├── nginx.conf
│ └── Dockerfile
├── docker-compose.yml
├── .gitignore
└── README.md
- Node.js 20+
- .NET 8 SDK
- (Optional)
dotnet-efglobal tool for running migrations manually
cd backend/TaskManagement.Api
dotnet restore
dotnet runThe API starts on http://localhost:5080. On first run it will apply the EF migrations and seed sample tasks automatically.
Swagger UI is available at http://localhost:5080/swagger when running in Development.
cd frontend
cp .env.example .env # optional, defaults to http://localhost:5080
npm install
npm run devThe app runs at http://localhost:5173. Sign in with:
- Username:
admin - Password:
123456
A single command runs both services:
docker compose up --build- Frontend (nginx): http://localhost:8080
- Backend (API): http://localhost:5080
The SQLite database is persisted in a named volume (backend-data) so data survives container restarts.
Migrations are applied automatically on startup. To create or update them:
cd backend/TaskManagement.Api
dotnet ef migrations add <MigrationName>
dotnet ef database updateIf dotnet-ef isn't installed: dotnet tool install --global dotnet-ef.
Base URL: http://localhost:5080
| Method | Path | Description |
|---|---|---|
| GET | /tasks |
Paged list. Query params: page, pageSize, status, search |
| GET | /tasks/{id} |
Fetch a single task. 404 if not found. |
| POST | /tasks |
Create a task. Body: { title, description, status } |
| PUT | /tasks/{id} |
Update a task. Same body as create. |
| DELETE | /tasks/{id} |
Delete a task. Returns 204. |
{
"items": [ { "id": 1, "title": "...", "description": "...", "status": "New", "createdAt": "..." } ],
"totalCount": 42,
"page": 1,
"pageSize": 10,
"totalPages": 5
}New, InProgress, Completed
Invalid requests return a standard ValidationProblemDetails (HTTP 400) with per-field messages.
After extracting/cloning:
git init -b main
git add .
git commit -m "Initial project setup"
# ... make additional logical commits ...
git remote add origin https://github.com/<your-user>/task-management-app.git
git push -u origin mainSuggested commit sequence if redoing from scratch:
- Initial project setup (solution + .gitignore + README skeleton)
- Backend task API (entity, DbContext, endpoints)
- Database migrations and seed data
- Frontend authentication (login + guards + session)
- Task CRUD UI
- Filtering and pagination
- Validation improvements
- Docker support
- README polish
- A single hardcoded account (
admin/123456) is acceptable — no registration or JWT flow is required. - The app is intended for a single user at a time; no per-user task scoping.
- SQLite is sufficient for the scope of this exercise; the DbContext would swap to another provider with only a connection-string change.
- Times are stored and returned in UTC; the UI formats them in the browser's locale.
- Auth: Using
localStorageas the source of truth for "signed in" is simple and fits the spec. In a real app I'd use httpOnly cookies + a real token. - State management: The app is small enough that a Pinia store would add more than it gives. Composables + service modules keep the surface area minimal.
- Styling: I hand-wrote a small CSS design system instead of pulling in Tailwind or a UI kit. It keeps the bundle lean and the look consistent without introducing a framework-level dependency for a take-home.
- Testing: I prioritized a complete, well-structured solution over adding a test suite. Given more time I'd add the tests listed below.
- Real auth (JWT or cookie-based), user management, and per-user task scoping
- A small test suite: xUnit + WebApplicationFactory for the API, Vitest + Vue Test Utils for components
- Optimistic UI updates for delete/edit, with toast notifications for feedback
- Cursor-based pagination and more sort options
- CI pipeline (GitHub Actions) that builds both projects and runs tests on PRs
- Structured logging (Serilog) and health-check endpoints for container orchestration
- Patterns for composing Minimal API endpoints as extension methods keep
Program.csshort without over-structuring - FluentValidation integrates cleanly with Minimal API via the
IValidator<T>interface andResults.ValidationProblem - Vite + Vue 3 composables are an excellent fit for the "service + page" split that keeps components thin