Scholardeck
Founder & Full-Stack Engineer
SaaS · Since 2025 · In development
Scholardeck is my own product. Institutions were running scholarship cycles across spreadsheets, email threads, and shared drives, which meant no audit trail and no way to compare applicants consistently. I designed the schema, built the API and the interface, and shipped it. The whole thing is mine, which is also why every architectural decision below has a reason attached rather than a convention.
The problem
A scholarship cycle involves applicants, reviewers, and administrators who each need a different view of the same record, with a hard deadline and a defensible decision trail at the end. Spreadsheets give you none of that.
Architecture
- Client
- Next.js App Router with React Server Components. Radix UI primitives for the dialogs, menus, and form controls so keyboard and screen-reader behaviour is correct by default.
- State
- Server state rendered on the server; client state kept to isolated islands: form drafts, filter panels, and the review queue.
- API
- Route handlers exposing a typed REST surface, with server actions for mutations that originate in the UI.
- Data
- PostgreSQL modelled with Prisma. Applications, reviews, awards, and organisations are separate entities so a scoring rubric can change between cycles without rewriting history.
- Auth
- Session-based authentication with role-based access control across applicant, reviewer, and administrator, enforced at the data layer rather than in the UI.
- Infra
- Applicant documents in S3 with infrastructure defined in AWS CDK; application deployed on Vercel.
Decisions
Scoring rubrics change between cycles
Storing scores as columns on the application row would have meant a migration every time a programme changed its criteria. Modelling rubric criteria as their own entity, with scores joined against them, means a new cycle is data rather than a schema change, and old awards still render with the rubric they were actually judged under.
Permissions belong next to the data, not the view
An early version filtered records in the components. That works right up until a second entry point exists. Moving access control into the query layer meant every read path inherits it, and a new route cannot accidentally leak a record it forgot to filter.
Reviewers work in bursts, near the deadline
Load is not uniform. It spikes hard in the final days of a cycle. Keeping the review queue server-rendered and paginating against indexed columns kept the interface responsive under that spike without adding a caching layer to operate.
Outcomes
- Full application-to-award cycle running on one system, with a complete decision trail
- Schema supports per-cycle rubric changes without migrations
- Access control enforced at the query layer across all three roles
Built with
- Next.js
- TypeScript
- React Server Components
- Radix UI
- Prisma
- PostgreSQL
- AWS CDK
- S3
- Tailwind CSS