// System Specification & Design
Little Lemon — Web Application Specification
Type: Full-stack restaurant management web application
Purpose: Portfolio deployment (Meta Backend Developer Capstone, cleaned up and hardened for public deployment)
Status: Deployed to Render (free tier)
1. Overview
Little Lemon is a Django-based restaurant web application combining server-rendered public pages with a REST API backend for menu management and table reservations. It's a hybrid monolith: Django serves both HTML templates (for browsing/booking) and JSON API endpoints (for programmatic access and the booking page's dynamic JS).
2. Tech Stack
| Layer | Technology |
|---|---|
| Backend framework | Django 6.0.x |
| API framework | Django REST Framework 3.17.x |
| Authentication | Djoser + DRF Token Authentication |
| Database | SQLite (file-based, ephemeral on host) |
| Media storage | Cloudinary (persistent image hosting) |
| Static file serving | WhiteNoise |
| WSGI server (production) | Gunicorn |
| Dependency management | Pipenv |
| Frontend | Django Templates + vanilla JavaScript (no frontend framework) |
| Hosting | Render (free tier, Python native runtime) |
| Python version | 3.13 |
3. Data Model
Booking
| Field | Type | Notes |
|---|---|---|
| id | AutoField | Primary key |
| first_name | CharField(255) | |
| reservation_date | DateField | |
| reservation_slot | IntegerField | Hour of day, default 10 (10:00–20:00 range used by frontend) |
Constraint: unique_together = ('reservation_slot', 'reservation_date') — prevents double-booking the same time slot on the same day.
Menu
| Field | Type | Notes |
|---|---|---|
| id | AutoField | Primary key |
| title | CharField(255) | |
| price | DecimalField(10,2) | |
| inventory | IntegerField | |
| image | ImageField | Optional; stored via Cloudinary in production |
4. API Endpoints
Base path: /restaurant/
| Endpoint | Methods | Auth Required | Description |
|---|---|---|---|
| /restaurant/api/menu/ | GET, POST | GET: none · POST: token | List menu items / create new item |
| /restaurant/api/menu/<id>/ | GET, PUT, PATCH, DELETE | GET: none · writes: token | Retrieve/update/delete a single menu item |
| /restaurant/api/bookings/ | GET, POST | Token (all methods) | List bookings (optionally filtered by ?date=YYYY-MM-DD) / create booking |
| /restaurant/api/bookings/<id>/ | GET, PUT, PATCH, DELETE | Token (all methods) | Retrieve/update/delete a single booking |
| /restaurant/api-token-auth/ | POST | — | Exchange username/password for an auth token |
| /restaurant/health/ | GET | None | Health check — returns {"status": "ok"} |
Auth endpoints (via Djoser, mounted at /auth/)
Standard Djoser-generated user management endpoints (registration, current-user detail, token login/logout) — not hand-built, provided by the djoser package.
Page routes (server-rendered HTML)
| Route | View | Template |
|---|---|---|
| /restaurant/ | home | index.html |
| /restaurant/about/ | about | about.html |
| /restaurant/menu/ | menu_page | menu.html |
| /restaurant/reservations/ | bookings | book.html |
Note: the bare root / is not currently routed (404s) — the app is namespaced entirely under /restaurant/.
5. Authorization Model
| Resource | Public (anonymous) | Authenticated |
|---|---|---|
| Menu — read | ✅ Allowed | ✅ Allowed |
| Menu — write (create/update/delete) | ❌ Blocked | ✅ Allowed |
| Bookings — read | ❌ Blocked | ✅ Allowed |
| Bookings — write | ❌ Blocked | ✅ Allowed |
Implemented via DRF's IsAuthenticatedOrReadOnly (menu) and IsAuthenticated (bookings) permission classes. Authentication is token-based (rest_framework.authentication.TokenAuthentication), obtained via /restaurant/api-token-auth/ or Djoser's endpoints.
6. Frontend Behavior
- Booking page (
book.html): vanilla JS handles the reservation flow client-side — fetches existing bookings for a selected date via the API, renders available/reserved time slots (10:00–20:00, hourly), and submits new bookings viafetch()with CSRF token handling. - Menu page (
menu.html): server-rendered list of all menu items with image (or placeholder) and price. - Static assets: served via WhiteNoise with compressed manifest storage in production.
- Media assets: menu item images served from Cloudinary CDN.
restaurant/tests/test_models.py— model string representations,unique_togetherconstraint enforcementrestaurant/tests/test_api.py— Menu API permission boundaries (anonymous read/blocked write, authenticated write), Booking API auth requirements, duplicate-slot rejection, date-filtered queries, serializer output validationrestaurant/tests/test_views.py— page rendering for all four template views, health check endpoint
python manage.py test restaurant
8. Environment Configuration
All sensitive/environment-specific values are externalized via django-environ, loaded from a .env file (local) or host-provided environment variables (production):
| Variable | Purpose |
|---|---|
| SECRET_KEY | Django cryptographic signing key |
| DEBUG | Debug mode toggle (False in production) |
| ALLOWED_HOSTS | Permitted request Host headers |
| CLOUDINARY_URL | Cloudinary account credentials for media storage |
| DATABASE_URL | Optional — defaults to local SQLite if unset |
9. Deployment Architecture
- Host: Render, free-tier web service
- Build process: custom
build.sh— installs dependencies via Pipenv, runscollectstatic, runsmigrate - Runtime: Gunicorn (
gunicorn Littlelemon.wsgi:application --bind 0.0.0.0:$PORT) - Python version pinned via
.python-version(3.13) to matchPipfile.lock - Database persistence: SQLite is file-based and not persistent across redeploys/restarts on Render's free tier (no attached disk). This is an accepted tradeoff — booking data is demo-only and expected to periodically reset; menu data is intended to be seeded via a data migration so the app never appears empty.
- Media persistence: solved independently via Cloudinary, which is unaffected by host filesystem resets.
- CORS: not configured — app is same-origin (API and frontend served from the same Django instance). Would need reconsideration if a separate frontend is ever built against this API.
- No persistent relational database — acceptable for a demo/portfolio deployment, not suitable as-is for a production restaurant system handling real bookings.
- Free-tier hosting spins down after 15 minutes of inactivity; first request after idle incurs a ~30–60s cold start.
- No CI/CD pipeline beyond Render's git-push auto-deploy.
- No rate limiting on public-read endpoints.
- Bare
/route unhandled (404) — app is scoped under/restaurant/.
- Models: 2 (
Booking,Menu) - Custom REST endpoints: 5 resource endpoints + 1 health check
- Auth-provided endpoints: Djoser's standard set (registration, token login/logout, user detail)
- Automated tests: 23, all passing
- Third-party services integrated: Cloudinary (media storage)