Skip to content

Latest commit

 

History

6 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 

Repository files navigation

Clinic Booking System

A full-stack appointment booking system for medical clinics. Patients can book a doctor's appointment without creating an account, or register to view and cancel their bookings later.

What this project does

For visitors

  • Search for doctors by name and see their clinic and speciality
  • Book an appointment as a guest, without registering
  • Register an account, then log in to view and cancel bookings

Behind the API

  • Full CRUD for doctors, clinics, specialities, appointment categories, patients, and appointments
  • JWT authentication with BCrypt-hashed passwords
  • Administrative endpoints are protected; public reference data and the guest booking path are not

Why I built it this way

Guests store less data than registered patients

The Patient model separates non-sensitive fields (name, email, gender, date of birth) from sensitive ones (SSN, tax number, driver's licence, insurance number, religion). An IsGuest flag drives the distinction, and the API discards sensitive fields entirely when creating a guest. Someone who books once without an account should not leave identifying data behind.

Cancelling keeps the row

DELETE /api/appointments/{id} does not remove the record. It sets the status to Cancelled, so the clinic keeps its booking history and can still see what was scheduled and dropped.

Request DTOs instead of entities

Controllers accept purpose-built DTOs rather than EF entities. This keeps navigation properties out of the API contract, so a client sending a booking is not offered a nested patient object with its own nested appointments. It also means the database model can change without silently changing what the API accepts.

Code-first migrations

The schema is defined in C# model classes and generated through EF Core migrations, so the database can be rebuilt from scratch on any machine with one command, and schema changes are versioned alongside the code.

Validation on both sides

Date constraints are enforced in the browser (appointments cannot be booked in the past, dates of birth cannot be in the future) and again in the controller, since client-side rules can be bypassed by calling the API directly.

Tech stack

Layer Technology
Database MySQL 8.0
Back-end ASP.NET Core 9 Web API, Entity Framework Core (code-first)
Front-end React with Vite
Auth JWT bearer tokens, BCrypt password hashing
Docs Swagger / OpenAPI

Setup

Requirements

  • .NET 9 SDK
  • MySQL Server 8.0
  • Node.js

Step 1 — Configure the database connection

Copy Backend/ClinicApi/appsettingsExample.json to appsettings.json and fill in your own values:

"ConnectionStrings": {
  "DefaultConnection": "Server=localhost;Database=ClinicDb;User=root;Password=YOUR_PASSWORD;"
},
"Jwt": {
  "Key": "YOUR_32_CHAR_SECRET_KEY",
  "Issuer": "ClinicApi",
  "Audience": "ClinicFrontend"
}

appsettings.json is gitignored so credentials never reach the repository. The JWT key must be at least 32 characters.

Step 2 — Create the database

cd Backend/ClinicApi
dotnet restore
dotnet ef migrations add InitialCreate
dotnet ef database update

This creates the ClinicDb database and all tables.

Step 3 — Run the back-end

dotnet run
  • API: http://localhost:5243
  • Swagger: http://localhost:5243/doc

Step 4 — Run the front-end

In a second terminal:

cd Frontend/clinic-frontend
npm install
npm run dev

The app opens at http://localhost:5173.

Testing protected endpoints in Swagger

  1. Register an account via POST /api/auth/register
  2. Call POST /api/auth/login and copy the token from the response
  3. Click Authorize at the top of the page
  4. Enter Bearer {your token} and confirm

Endpoints

Endpoints marked No are open because the guest booking flow and the public search depend on them. Everything else requires a valid JWT.

Auth

Method Endpoint Description Auth
POST /api/auth/register Register a new patient account No
POST /api/auth/login Log in and receive a JWT No

Doctors

Method Endpoint Description Auth
GET /api/doctors Get all doctors No
GET /api/doctors/{id} Get one doctor by ID No
GET /api/doctors/search?name={name} Search by first or last name No
POST /api/doctors Create a doctor Yes
PUT /api/doctors/{id} Update a doctor Yes
DELETE /api/doctors/{id} Delete a doctor Yes

Clinics

Method Endpoint Description Auth
GET /api/clinics Get all clinics No
GET /api/clinics/{id} Get one clinic by ID No
POST /api/clinics Create a clinic Yes
PUT /api/clinics/{id} Update a clinic Yes
DELETE /api/clinics/{id} Delete a clinic Yes

Specialities

Method Endpoint Description Auth
GET /api/specialities Get all specialities No
GET /api/specialities/{id} Get one speciality by ID No
POST /api/specialities Create a speciality Yes
PUT /api/specialities/{id} Update a speciality Yes
DELETE /api/specialities/{id} Delete a speciality Yes

Categories

Method Endpoint Description Auth
GET /api/categories Get all categories No
GET /api/categories/{id} Get one category by ID No
POST /api/categories Create a category Yes
PUT /api/categories/{id} Update a category Yes
DELETE /api/categories/{id} Delete a category Yes

Patients

Method Endpoint Description Auth
GET /api/patients Get all patients Yes
GET /api/patients/{id} Get one patient by ID Yes
POST /api/patients Create a patient (guest booking) No
PUT /api/patients/{id} Update a patient Yes
DELETE /api/patients/{id} Delete a patient Yes

Appointments

Method Endpoint Description Auth
GET /api/appointments Get all appointments Yes
GET /api/appointments/{id} Get one appointment by ID Yes
GET /api/appointments/patient/{patientId} Get a patient's appointments Yes
POST /api/appointments Book an appointment No
PUT /api/appointments/{id} Update an appointment Yes
DELETE /api/appointments/{id} Cancel an appointment Yes

Known limitations

No per-user authorization. Protected endpoints check that a caller is authenticated, but not which patient they are. A logged-in user could read another patient's appointments by changing the ID in the URL. The fix is to compare the patientId in the route against the claim in the token, and it is the first thing I would add.

No role separation. Every authenticated user has the same access, so a registered patient can reach endpoints intended for clinic staff. The User model already carries a Role, so the groundwork is there — the controllers would need [Authorize(Roles = "Admin")] on administrative actions.

Circular dependency between patients and users. User references Patient and Patient references User. It works, but a one-directional relationship with the foreign key on User alone would be cleaner.

Front-end token storage. The JWT is kept in localStorage, which is readable by any script on the page. An httpOnly cookie set by the server would be the stronger choice.

External libraries

Back-end

Package Version Purpose
Pomelo.EntityFrameworkCore.MySql 9.0.0 MySQL provider for EF Core
Microsoft.EntityFrameworkCore.Design 9.0.0 Required for EF migration commands
Microsoft.AspNetCore.Authentication.JwtBearer 9.0.0 JWT bearer authentication
Swashbuckle.AspNetCore 6.5.0 Swagger UI and API documentation
BCrypt.Net-Next 4.2.0 Password hashing

Front-end

Package Purpose
react-router-dom Client-side routing
axios HTTP requests to the API

References

Claude was used during development to help debug errors, discuss which approaches suited particular situations, and explain unfamiliar parts of the code.

About

Full-stack appointment booking for clinics. ASP.NET Core 9, EF Core, MySQL, React, JWT auth.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages