← All workCase study / 04

Deployed portfolio project · synthetic data

Doctor Appointment

A full-stack application for finding doctors, choosing an available time and managing an authenticated appointment.

My Bachelor's final-year project, revisited with a more critical eye: an audit and hardening pass around authentication boundaries, data exposure and appointment ownership.

Live demo Source code Free-tier backend: the first load may take a moment

Film transcript: Silent 32-second recording of the deployed application: Home, search for Cardiologist, open Dr. Sarah Jenkins, choose 27 September 2026 and 2:00 PM, confirm the appointment, view both bookings under My Bookings, cancel the 2:00 PM appointment, and return to the booking modal where that slot is available again.

Platform
Web application
Status
Deployed portfolio project
Stack

01At a glance

The 30‑second version.

Problem

A familiar booking flow still has to stay coherent across authentication, availability, persistence, ownership and cancellation.

Product

Find a doctor, open a profile, choose a date and time, book, then manage or cancel it in My Bookings.

Outcome

Deployed with synthetic data, then hardened so appointments go through authenticated server routes that check who owns them.

0

Strapi tokens in the client bundle after hardening

5/5

Targeted frontend tests in the hardening pass

30min

Appointment slots, 10:00 AM to 6:30 PM

02Experience

Find. Book. Manage.

A signed-in user can find a doctor, open a profile, choose a time, book, then return to My Bookings to cancel.

  1. 01
    Find a doctor

    Search, or browse by specialty category.

  2. 02
    Choose a time

    Pick a date; slots that are already booked are shown as unavailable.

  3. 03
    Book

    The appointment is created for the signed-in user.

  4. 04
    Manage it

    My Bookings lists upcoming appointments, each with a cancel action.

Booking modal for 27 September 2026: 11:00 AM is unavailable, noon is formatted as PM, and 12:30 PM is selected.
Booked times are unavailable; noon is formatted as PM.
Top of the deployed Doctor Appointment homepage, showing its hero, specialty categories and first row of synthetic doctor cards.
Deployed homepage. All doctors, categories and appointments are synthetic demo data.
Signed-in doctor detail page for synthetic doctor Dr. Sarah Jenkins, with profile information, suggestions and a Book Appointment action.
Doctor profile with a Book Appointment action.
Genuine booking-success toast after creating a synthetic appointment through the authenticated application route.
The toast says a confirmation was sent; email is sandbox-limited.
My Bookings page showing a synthetic upcoming appointment for the signed-in user and its Cancel Appointment action.
My Bookings, scoped to the signed-in user.
Cancellation confirmation dialog for the synthetic upcoming appointment.
Cancelling asks for confirmation first.

03Architecture

Appointments belong to the signed-in identity.

Doctor and category data come from Strapi, while appointment operations go through authenticated Next.js server routes.

One booking · Next.js 15 + React · Tailwind CSSImages on Cloudinary · email via Resend (sandbox)

  1. Browser

    Next.js UISearch, profile, booking

  2. Sign-in

    KindeSession identity

  3. Boundary

    Server routesIdentity derived on the server

  4. Checks

    OwnershipList and cancel only your own

  5. CMS

    Strapi 4Hosted on Render

  6. Data

    PostgreSQLHosted on Supabase

Availability
For the chosen doctor and date, the browser asks only for booked time strings.
Credentials
A scoped, server-only credential replaced the token the browser used to hold.

04Challenges

Revisiting my own early work.

A later security audit found weaknesses around authentication boundaries, data exposure and appointment ownership.

Challenge 01 · Credentials

The browser held the keys.

0tokens in the client bundle
  • Browser-exposed Strapi token
  • Public appointment permissions
  • Server-only scoped credential
symptom
The browser could reach privileged Strapi appointment operations directly.
investigation
The audit found a Strapi token exposed to the browser and public permissions on appointments.
decision
Removed public appointment permissions, revoked the exposed token, and moved appointment operations behind authenticated Next.js server routes with a scoped, server-only credential.
result
The recorded checks show anonymous appointment access blocked and no Strapi token in the client bundle.

Challenge 02 · Data exposure

Doctor pages that knew too much.

Doctor queryProfile and categories

Patient appointmentsNo longer included

symptom
Broad doctor queries could expose patient appointment data.
investigation
Checked doctor and category payloads during the hardening pass.
decision
Restricted the broad doctor queries.
result
The recorded checks show no patient appointment data in doctor or category responses.

Challenge 03 · Ownership

Only your own bookings.

Server sessionIdentity from Kinde

Browser emailNo longer trusted

symptom
The app had to know whose appointments to list and cancel, without trusting an email sent by the browser.
investigation
Listing and cancelling went through privileged appointment operations reached from the browser.
decision
Derive the Kinde session identity on the server, scope listing to it, and check ownership before any cancellation.
result
Users can list and cancel only their own appointments.

Challenge 04 · Availability

Two people, one slot.

  • Server duplicate check
  • Race-proof booking
symptom
Two bookings for the same doctor and time must not both succeed.
investigation
Each doctor shares a 10:00 AM–6:30 PM schedule in half-hour slots, and the browser only marks booked times as unavailable.
decision
The server also checks for a duplicate slot and returns a conflict when it finds one.
result
A useful boundary, but not race-proof. The next step would be a database uniqueness or transaction strategy.

05Testing

Verified against the deployed app.

Production builds, targeted tests and manual checks of the full booking and cancellation lifecycle.

Deployed application

Booking, end to end.

Signed in, choosing a time slot for a synthetic doctor.

Doctor Appointment · booking

Booking modal for 27 September 2026: 11:00 AM is unavailable, noon is formatted as PM, and 12:30 PM is selected.

Recorded verification

Checked in production.

  • Create → list → cancel worked
  • Cancelled slot free again
  • Noon shown as 12:00 PM
  • Anonymous access blocked
  • No patient data in doctor responses
  • No Strapi token in the bundle

Automated checks

Builds and targeted tests.

Frontend and Strapi production builds passed. Results from the final test run; automated coverage remains limited.

5/5targeted frontend tests

My Bookings

Scoped to you.

The signed-in user's upcoming appointment, with its cancel action.

Doctor Appointment · My Bookings

My Bookings page showing a synthetic upcoming appointment for the signed-in user and its Cancel Appointment action.

06Status

A useful demo, honestly.

A portfolio application using synthetic data, not a clinic, a compliance product or evidence of real adoption.

Working in the deployed app

  • Doctor search
  • Specialty categories
  • Doctor profiles
  • Booking with availability
  • My Bookings
  • Cancellation
  • Authenticated server routes
  • Ownership checks

Known limits

  • Synthetic data only
  • One shared fixed schedule
  • Duplicate check not race-proof
  • Sandbox-limited email
  • Incomplete phone navigation
  • Empty-state loading issue
  • Free-tier backend cold starts
  • No doctor portal or rescheduling

This is not race-proof and not production-ready. Server-side duplicate checks help, but real booking safety needs a database uniqueness or transaction strategy.

07My role

What I did, and how.

Doctor Appointment started as my Bachelor's final-year project, where I used guided learning resources to build my first complete full-stack booking workflow. I implemented the application hands-on, connecting the frontend, authentication, CMS-backed data and appointment flow while learning how those pieces fit together. Revisiting the project later gave me a chance to evaluate that early work with a more critical engineering lens. I audited the application, identified weaknesses around authentication boundaries, data exposure and appointment ownership, and worked through a targeted hardening pass using AI-assisted development. I then verified the changes against the deployed application, including the complete booking and cancellation lifecycle.

✦ Hardened with AI-assisted development

AI-assisted implementation
Used AI-assisted development during the later audit and hardening pass; the original guided-learning project was implemented hands-on.

08Learned

The work beyond the screen.

Revisit early work

Revisiting it with a more critical engineering lens exposed weaknesses around authentication boundaries, data exposure and ownership.

Trust the server, not the browser

Identity, credentials and ownership checks belong behind authenticated server routes.

Know what's next

Move booking conflicts into the database and add real per-doctor availability.