Skip to content
Nsikak Akpan
All work
Placeholder content

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