Backend
cd api
npm install
npm run start:dev
# API available at http://localhost:3001Frontend
cd web
npm install
npm run dev
# UI available at http://localhost:3000
# Vite proxies /api/* → http://localhost:3001/* (strips the /api prefix)docker compose up --build
# UI at http://localhost:3002
# API at http://localhost:3001Both containers run dev servers with live reloading. The web container's Vite proxy is pointed at the api service via the BACKEND_URL environment variable set in docker-compose.yml.
Backend (Jest + ts-jest, 26 tests)
cd api
npm test
# or with coverage:
npm run test:covFrontend (Vitest + Testing Library, 19 tests)
cd web
npm test
# or in watch mode:
npm run test:watch- The dataset is small and static (in-memory), so all filtering and sorting is done server-side on every request rather than introducing a database.
ItemsService.fetchItems()is intentionally async so the interface matches what anHttpServicecall to a real upstream would look like — swapping the stub for a live HTTP request requires no changes outside that method. - The
activefilter uses string values"true"/"false"/"all"over HTTP query params rather than a boolean, which is idiomatic for query strings and matches the original baseline intent. - Drag-and-drop reordering is a local UI concern only — it does not persist to the backend. A real implementation would need a PATCH endpoint to save the new order.
- Category values (
core,ops,growth) are treated as a known, finite set in the frontend dropdown. A production app would fetch distinct categories from the API.
- Feature modules (
ItemsModule,HealthModule) rather than registering everything directly onAppModule. Each module is self-contained and independently testable. - DTO with
class-validator(ListItemsQueryDto) replaces the original plain object type. TheValidationPipewithwhitelist: truerejects invalid values and strips unknown properties at the framework boundary, so the service never receives bad input. - Typed
sortBy—SORTABLE_FIELDSis aconsttuple used to derive theSortFieldunion type and the@IsInvalidator. This makes the whitelist the single source of truth shared across runtime validation and TypeScript types. getSortableValueremoved — the original method used an unsafeas unknown as Record<string, ...>cast to allow arbitrary string keys. WithSortFieldtyped tokeyof ItemRecord,item[sortField]is fully type-safe.- Two test levels: unit tests on
ItemsService(fast, no framework overhead) and integration tests onItemsControllerusing@nestjs/testing+supertestto verify the full HTTP layer including validation responses.
- @dnd-kit for drag-and-drop. It is pointer- and keyboard-accessible, works well with React 19, and has no DOM manipulation side effects that complicate testing — unlike react-beautiful-dnd which requires a working
getBoundingClientRect. - Vitest instead of Jest for frontend tests. It shares the same Vite config and transform pipeline, has native ESM support, and is significantly faster for a Vite-based project.
- Server-side filtering and sorting on every fetch, debounced search input (300 ms). This keeps the frontend stateless with respect to data — the displayed list is always what the API returns, except for drag-and-drop reordering which is purely local.
- Tailwind CSS for styling. Utility-first keeps component files self-contained and responsive layout (
flex-col sm:flex-row) straightforward without a separate stylesheet.
- No persistence for drag order. The reordered list resets on the next fetch (filter/sort change). A PATCH
/items/orderendpoint with optimistic UI would be the right fix but is out of scope here. - Categories hardcoded in the frontend. Fetching distinct categories from the API would be more robust but adds a second request on load for marginal benefit given the fixed dataset.
- No pagination. Acceptable for 6 items; would need a
limit/offsetor cursor strategy at scale. ts-node-devis used for the API dev server (--transpile-onlyskips type-checking on reload for speed; the TypeScript compiler still catches errors at build time and in-editor). Source directories are mounted as volumes so changes on the host reflect immediately without rebuilding the image.
- Persist drag order — add a
PATCH /items/orderendpoint and an optimistic update in the frontend with rollback on error. - Type-checking on reload —
ts-node-dev --transpile-onlyskips full type checking for speed. A separatetsc --watchsidecar would surface type errors faster during development. - Dynamic category list — fetch categories from
GET /items/categoriesor derive them from the items response. - E2E tests — add Playwright tests covering the full filter + sort + drag workflow against the running app.
- OpenAPI / Swagger — add
@nestjs/swaggerdecorators to generate API documentation automatically. - Error boundary in the frontend — wrap the app in a React error boundary for unexpected render errors separate from fetch errors.