Parking Shark is a full-stack, peer-to-peer parking marketplace built for the University of Virginia community. Renters can find an available driveway or garage, register a vehicle, book by the hour, pay, and review a completed stay. Hosts can publish listings, manage availability, confirm reservations, and export booking data.
Try the live demo · Read the case study
We built this together as a four-person CS 4750 Database Systems project. A lot of the work happened side by side on shared computers. The application goes beyond CRUD: booking is transactional, overlap protection lives in both the database and application flow, and access to listings, vehicles, reservations, payments, and reviews is ownership-aware.
- Marketplace search by location, spot type, price, time window, and sort order
- Host and renter dashboards with reservation lifecycle management
- Atomic MySQL booking procedure with per-spot locking and overlap prevention
- 13-table normalized relational model, stored procedure, trigger, constraints, role-scoped grants, reviews, payments, and CSV exports
- Password hashing, session fixation defense, same-origin mutation checks, authentication rate limiting, security headers, and parameterized queries
- Responsive server-rendered UI built with Express, EJS, Bootstrap, and custom CSS
- Automated policy tests, dependency audit, and GitHub Actions CI
Browser
└── Express + EJS
├── authentication and MySQL-backed sessions
├── ownership and reservation-policy middleware
├── marketplace / host / renter routes
└── mysql2 connection pool
└── MySQL
├── normalized marketplace data (13 tables)
├── create_booking stored procedure
├── prevent_double_booking trigger
└── CHECK constraints and least-privilege grants
The executable schema is in sql/schema.sql, which is the
single source of truth for the database.
Two people hitting "Reserve" on the same driveway for the same hour is the interesting concurrency problem in this project, and it is handled in SQL rather than in application code.
The subtlety is that the prevent_double_booking trigger alone is not enough.
MySQL runs at REPEATABLE READ by default, and the trigger's overlap check is a
plain non-locking read. Two concurrent transactions each get a snapshot taken
before the other inserted, so both see zero conflicts and both commit.
create_booking closes that hole by taking an exclusive row lock on the spot
before it checks anything:
SELECT hourly_rate INTO v_hourly_rate
FROM spots
WHERE spot_id = p_spot_id AND is_active = TRUE
FOR UPDATE;That serializes booking attempts per listing. Because the locking read runs
before any consistent read, the transaction's read view is created after the
lock is granted, so the overlap check that follows sees the booking that just
committed and raises SQLSTATE 45000. The reservation and its payment row are
written in one transaction, and the extend path in
routes/reservations.js takes the same locks in the
same order so the two paths cannot deadlock against each other.
Measured on MySQL 9.0.1 with two connections issuing overlapping
CALL create_booking at the same instant, 25 trials each:
| Booking path | Both succeeded | Exactly one succeeded |
|---|---|---|
With the FOR UPDATE row lock |
0 | 25 |
| Same procedure, lock removed | 25 | 0 |
Requirements: Node.js 22 to 24 and MySQL 8 or newer.
git clone https://github.com/RohanSi4/parking-shark.git
cd parking-shark
npm ci
cp .env.example .envCreate a local database and seed it:
mysql -u root -p < sql/schema.sql
mysql -u root -p < sql/migration_auth.sqlUpdate .env with your local MySQL credentials and generate a session secret:
node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"
npm run devOpen http://localhost:3000. Seed accounts use the password password123:
fh@virginia.edu: renter with completed and upcoming reservationsac@virginia.edu: host with listings and reviews
On macOS with Homebrew MySQL, bash setup.sh performs the same setup and starts
the app. It never overwrites an existing .env or database.
npm test
npm run checknpm test runs the reservation-policy and request-origin regression suite.
npm run check also verifies that production dependencies have no known audit
findings. A fresh schema import should create 13 application tables, 15 seeded
spots, 20 seeded reservations, the booking procedure, and overlap trigger.
Once running, GET /healthz verifies both the web process and its database
connection without exposing credentials or internal errors.
The repository now includes a Vercel-ready interactive demo. It is not a static mockup. A visitor can request a spot as a renter, switch to the host view to approve and complete the stay, then switch back to record payment and leave a review.
The demo uses signed, isolated cookie state and sample listings. That keeps the complete marketplace workflow available without exposing student accounts, collecting payment details, or depending on the unavailable course database. The original Express and MySQL application remains the source of truth for the database-backed product.
Run the hosted experience locally:
npm run demoDeploy the repository root as an Other framework project on Vercel. The
checked-in vercel.json sends application routes to api/index.js, bundles the
demo views with that function, and lets Vercel serve the public assets as static
files. No database environment variables are needed for the demo.
DEMO_SESSION_SECRET can be set to a long random value to use a private signing
key instead of the public-demo fallback.
npm run check
vercel build
vercel deploy --prebuiltThe original course deployment used Google App Engine Standard and Cloud SQL.
The legacy URL, https://parkingsharkuva.ue.r.appspot.com, is not currently
a working public demo: as of July 2026, Google Frontend returns a service
unavailable response before the Express app serves a page. The local app and a
fresh database bootstrap are verified; the cloud deployment still needs an
owner with project IAM access to inspect its serving version/logs and redeploy.
Copy deploy/app.yaml.example to the ignored
deploy/app.yaml, set production environment variables, and deploy:
gcloud config set project YOUR_PROJECT_ID
gcloud app deploy deploy/app.yaml
curl https://YOUR_APP_URL/healthzProduction requires a strong SESSION_SECRET; startup fails fast if the value
is missing or left as the example default. Set APP_ORIGIN when the public
origin cannot be inferred from the incoming request. Real environment files and
deployment credentials are intentionally excluded from Git.
Built by Adithya Balasubramaniam, Rohan Singh, Angad Brar, and Visvajit Murali for CS 4750 Database Systems, January to April 2026.
The coursework phase was highly collaborative and frequently happened on shared machines, so commit authorship does not cleanly divide that work between the four of us. The schema, the Express application, and the original UI were a joint effort.
The hardening and portfolio pass in July 2026 was separate, and that work is
mine alone. It is attributable through git log and git blame:
- The concurrency fix described above. The
FOR UPDATErow lock and the availability enforcement insidecreate_booking, plus the matching lock ordering on the reservation extend path. middleware/security.js, the same-origin mutation check that rejects cross-site POSTs using Origin, Referer, and Fetch Metadata.lib/reservation-policy.js, the reservation state machine that decides which role may confirm, complete, cancel, extend, pay, or review, extracted so it could be unit tested away from the database.- The whole
test/suite and the GitHub Actions workflow, including the CI step that imports the schema from scratch and asserts the table and seed counts. - The
demo/application and its Vercel entrypoint, which is what the live demo link serves.

