A FastAPI backend serving live data about my AI/data learning journey, persisted in a real SQLite database via SQLAlchemy. The project list still syncs live from my public GitHub repos (sorted by most recently pushed) — but now that data survives a server restart, and every resource supports full CRUD.
An interactive demo frontend (index.html) is included alongside the API — see below.
pip install -r requirements.txtpython main.pyAPI runs at http://localhost:8000. On first run, app.db (a SQLite file) is created
automatically in this folder — you don't need to set anything up by hand.
Running this locally only makes it reachable while your own machine is on, awake, and not asleep — fine for development, not for a real "always on" demo. This API is deployed at:
https://personal-projects-api-xt21.onrender.com
render.yaml in this repo configures the deploy. If you ever need to redeploy from scratch:
- Push this repo to GitHub (already done if you're reading this from the repo).
- On Render: New + → Blueprint → connect this GitHub repo. Render reads
render.yamlautomatically and sets up the build/start commands and a realADMIN_API_KEYfor you (auto-generated, not the local-dev fallback). If you instead connect it as a plain Web Service (not Blueprint), Render won't readrender.yaml— you'd need to manually set the Start Command touvicorn main:app --host 0.0.0.0 --port $PORT. scripts/main.jsalready pointsDEPLOYED_API_BASEat the URL above. The frontend tries your local dev server first (fast timeout), then this deployed URL (longer timeout, to tolerate Render's free-tier cold starts), then finally falls back to calling GitHub directly — so it degrades gracefully no matter what's running where.
Trade-off to know about: Render's free tier uses ephemeral disk and spins the service
down after periods of inactivity. That means the SQLite database (manually added/edited
projects, skills, and feedback) can reset on a cold restart — the GitHub project sync
self-heals automatically on the next request, but feedback submitted between restarts won't
survive indefinitely. For a learning project this is a reasonable trade-off for "always
reachable, mostly persistent." If durable feedback storage matters to you, the next step
up is swapping SQLite for Render's free PostgreSQL (a small change in database.py's
DATABASE_URL, since SQLAlchemy abstracts most of the rest).
database.py defines three SQLAlchemy tables — projects, skills, feedback — and
creates them (and the app.db file) the moment the app starts, if they don't already exist.
From then on, every request reads from and writes to that same file, so:
- Restarting the server does not reset anything.
- Projects/skills you add, edit, or delete via the API stay changed.
- Feedback submitted through the form is saved for good, not just printed to the console.
You can inspect the database directly with the sqlite3 CLI (already on macOS/Linux):
sqlite3 app.db
sqlite> .tables
sqlite> SELECT * FROM projects;
sqlite> SELECT * FROM skills;
sqlite> SELECT * FROM feedback;
sqlite> .quit| Method | Path | Notes |
|---|---|---|
| GET | / |
Welcome message |
| GET | /api/projects |
Live-synced from GitHub + any manually-added projects |
| GET | /api/projects/{id} |
A single project |
| POST | /api/projects |
Add a manual project (not from GitHub) |
| PUT | /api/projects/{id} |
Edit any project (GitHub-synced or manual) |
| DELETE | /api/projects/{id} |
Remove a project |
| GET | /api/skills |
Curated skills list |
| POST | /api/skills |
Add a skill |
| PUT | /api/skills/{id} |
Edit a skill |
| DELETE | /api/skills/{id} |
Remove a skill |
| POST | /api/feedback |
Submit feedback (public, validated) |
| GET | /api/feedback |
List all feedback — requires X-API-Key header |
| DELETE | /api/feedback/{id} |
Delete one feedback entry — requires X-API-Key header |
Visit http://localhost:8000/docs for interactive, auto-generated API documentation
(Swagger UI) — try every endpoint directly from the browser (the two admin routes need
you to paste the X-API-Key header in via the "Authorize" — actually just use curl or
the header field on each request, Swagger's built-in auth UI isn't wired up for this).
Feedback submissions include real names and emails, so listing or deleting them requires
an X-API-Key header:
curl http://localhost:8000/api/feedback -H "X-API-Key: your-key-here"By default (no env var set) it uses a fallback key hardcoded in security.py
(local-dev-only-change-me) — fine for local development, and the server prints a loud
warning on startup to remind you it's active. Before deploying this anywhere public,
set a real secret:
export ADMIN_API_KEY="something-long-and-random"
python main.pyPOST /api/feedback (submitting feedback) stays public with no key needed — that's the
whole point of a feedback form.
routes/projects.py calls GitHub's public REST API (api.github.com/users/sankara226/repos)
sorted by pushed date, and upserts the top 6 into the projects table (matched by name).
This sync is rate-limited to once per 5 minutes in-memory, well under GitHub's unauthenticated
limit (60 requests/hour/IP). If GitHub is unreachable and there's no prior synced data yet,
it bootstraps from the static snapshot in data/projects.json just once, so the demo never
starts up empty. The response includes a "source" field ("github", "cached",
"stale-cache", or "fallback") so you can always tell what happened.
Note: the Project model has updated_at and stars (pulled straight from GitHub) instead
of a week number — a made-up week count wouldn't mean anything for arbitrary real repos.
index.html (+ styles/, scripts/) is a small animated dashboard that visualizes this API:
live project cards, a skills section, a "try it live" API explorer, and a feedback form. It
tries the local backend first (http://localhost:8000) and, if that's not running, calls
GitHub's API directly from the browser instead — so the live "6 most recent repos" view works
for any visitor, whether or not you have main.py running. Just open index.html, no server
required.
fetch('http://localhost:8000/api/projects')
.then(r => r.json())
.then(data => console.log(data))- ✅ Database file (
app.db) created automatically on first run - ✅ Data persists across server restarts
- ✅ Full CRUD on projects and skills; create/read/delete on feedback
- ✅ Proper data models (Pydantic validation, including real email format checking)
- ✅ Organized routing (separate files under
routes/) - ✅ CORS enabled (callable from any frontend)
- ✅ Auto-documentation (
/docs) - ✅ Sensitive data (feedback) gated behind an API key, not left publicly scrapeable
- ✅ README with clear setup, database, and usage instructions