A full-stack room reservation system built with Svelte + SvelteKit, Prisma 7, and MariaDB. Handles user authentication, role-based access control, space management, recurring reservations, and admin operations.
src/hooks.server.ts on protected routeshasPermission(user.role, requiredRole) in src/lib/permissions/auth.tssrc/routes/(authenticated)/ require valid JWTsrc/routes/(authenticated)/(admin)/ check for Admin/Owner rolesrc/routes/(authenticated)/(admin)/api/{action}/+server.ts
├── POST handler accepts request
├── Validates user permissions
├── Calls database model (e.g., user.model.js, rooms.model.js)
└── Returns JSON response
src/lib/server/user.model.js - User queries (findUser, createUser, updateRole)src/lib/server/rooms.model.js - Room queries (findRooms, createRoom, deleteRoom)src/lib/server/db.ts with MariaDB adapterSvelteKit Route (+page.server.ts)
→ Server Load Function or Form Action
→ Calls Model (user.model.js, rooms.model.js)
→ Uses PrismaClient
→ Returns data/result to component
| Layer | Technology | Version | Notes |
|---|---|---|---|
| Frontend Framework | Svelte | 4.2.7 | Reactive components, minimal overhead |
| Meta Framework | SvelteKit | 2.50.1 | SSR, routing, server routes |
| Build Tool | Vite | 5.4.6 | ESM-based, zero-config |
| ORM | Prisma | 7.3.0 | ESM client (90% smaller bundle) |
| DB Adapter | @prisma/adapter-mariadb | 7.3.0 | Native MariaDB connection pooling |
| DB Driver | mysql2 | 3.16.1 | MySQL protocol implementation |
| Authentication | jsonwebtoken + bcrypt | 6.0.0 | JWT sessions, password hashing |
| Nodemailer | 7.0.12 | SMTP integration | |
| Utilities | Day.js + rrule | - | Date handling, recurring patterns |
| Styling | Bootstrap 5 + SCSS | 5.3.3 | Component library, modern CSS API |
| Testing | Vitest | 4.0.18 | Fast unit tests, ESM-native |
| Type Safety | TypeScript | 5.0.0 | Full codebase type checking |
| Node | - | 20.x | Required version |
| Deployment | AWS ECS Fargate | - | Containerized, serverless |
| Container Registry | AWS ECR | - | Private Docker image registry |
| IaC | Terraform | 1.5+ | AWS infrastructure as code |
This project uses a reusable multi-environment GitHub Actions pipeline deploying to AWS ECS Fargate via Docker and ECR.
feature/* ──► dev ──► qa ──► main
│ │ │
dev qa prod
cluster cluster cluster
feature/* — cut from dev, opened as PR back to devdev — auto-deploys to dev ECS cluster on every mergeqa — auto-deploys to qa ECS cluster on every mergemain — deploys to prod ECS cluster after required reviewer approval1. Developer cuts feature branch off dev
│
2. PR opened → dev (code review)
│
3. Merge to dev triggers build.yml
- Authenticates to AWS via access token
- Runs: npm ci → npx prisma generate → npm run build
- Builds Docker image with build-time env vars
- Tags image as dev-<git-sha> and dev-latest
- Pushes to ECR
│
4. deploy-dev.yml triggers automatically
- Downloads current ECS task definition
- Swaps image tag to dev-<git-sha>
- Registers new task definition revision
- Updates ECS dev service
- Waits for stability (ECS circuit breaker auto-rolls back on failure)
│
5. PR: dev → qa (tested in dev first)
- Merge triggers deploy-qa.yml automatically
- Same image SHA promoted — no rebuild
│
6. PR: qa → main
- GitHub Environment approval gate pauses workflow
- Required reviewer approves in GitHub UI
- Deploys to prod — zero-downtime (new task starts before old stops)
Manual trigger via rollback.yml in the Actions tab:
| File | Trigger | Purpose |
|---|---|---|
build.yml |
Push to dev |
Build Docker image, push to ECR |
_deploy.yml |
Called by others | Reusable deploy template |
deploy-dev.yml |
Push to dev |
Deploy to dev ECS cluster |
deploy-qa.yml |
Push to qa |
Deploy to qa ECS cluster |
deploy-prod.yml |
Push to main |
Deploy to prod with approval gate |
rollback.yml |
Manual | Roll back any environment |
Managed via main.tf at the repo root. Provisions:
To provision infrastructure:
terraform init
terraform plan
terraform apply
Key Terraform considerations:
ecsTaskExecutionRolesvelte-rooms-tf-state bucket)use_lockfile = false — avoids s3:DeleteObject permission requirementterraform import <resource> <id>SvelteKit has two categories of private env vars — understanding this is critical for the Docker build:
| Type | Import | Resolved | Where to set |
|---|---|---|---|
$env/static/private |
Build time | During npm run build |
GitHub Actions secrets (build args) |
$env/dynamic/private |
Runtime | When container starts | ECS task definition |
These must exist as GitHub repository secrets AND be passed as Docker build args. The build fails without them because SvelteKit resolves them statically during npm run build.
| Secret | Purpose |
|---|---|
AWS_ACCESS_KEY_ID |
AWS authentication |
AWS_SECRET_ACCESS_KEY |
AWS authentication |
AWS_REGION |
AWS region (us-east-1) |
ECR_REPOSITORY |
Full ECR URL (from terraform output ecr_repository_url) |
JWT_ACCESS_SECRET |
JWT signing secret |
CONTACT_EMAIL |
Contact email for notifications |
To find all build-time variables in the codebase:
grep -r "from \"\$env/static/private\"" src/ | grep -o '"[A-Z_]*"' | sort -u
These are injected into the container at runtime via the ECS task definition environment block. They do not need to be in GitHub secrets.
| Variable | Purpose |
|---|---|
DATABASE_URL |
MariaDB connection string |
SMTP_HOST |
Email server host |
SMTP_PORT |
Email server port |
SMTP_USER |
Email credentials |
SMTP_PASS |
Email credentials |
To find all runtime variables:
grep -r "from \"\$env/dynamic/private\"" src/ | grep -o '"[A-Z_]*"' | sort -u
Create a .env file at the repo root (never commit this):
JWT_ACCESS_SECRET=your-secret
[email protected]
DATABASE_URL=mysql://user:pass@localhost:3306/svelte_rooms
# Install dependencies
npm install
# Generate Prisma client
npx prisma generate
# Run database migrations
npx prisma migrate dev
# Start dev server
npm run dev
Always test the Docker build locally before pushing to avoid waiting for GitHub Actions to catch errors:
docker build \
--build-arg JWT_ACCESS_SECRET=test-secret \
--build-arg [email protected] \
-t svelte-rooms-test .
If this passes locally it will pass in GitHub Actions.
Admin Routes - All under src/routes/(authenticated)/(admin)/api/
| Endpoint | Method | Purpose |
|---|---|---|
/api/newReservation |
POST | Create reservation |
/api/deleteReservation |
POST | Delete reservation |
/api/reservationData |
POST | Fetch reservation details |
/api/deleteRoom |
POST | Delete space/room |
/api/editUserRole |
POST | Update user role |
All routes validate user permissions before executing.
See prisma/schema.prisma for full schema.
| Role | User Mgmt | Space Mgmt | Reservations | Admin Access |
|---|---|---|---|---|
| Owner | ✅ Full | ✅ Full | ✅ Full | ✅ Yes |
| Admin | ✅ Assign roles | ✅ CRUD | ✅ View all, delete | ✅ Yes |
| User | ❌ | ❌ | ✅ Own only | ❌ |
| Guest | ❌ | ❌ | ✅ View own | ❌ |
| Restricted | No access |
Check src/lib/permissions/auth.ts for enforcement.
MIT License. See LICENSE file for details.