I know “GitHub as a database” sounds like one of those ideas you should not say too loudly in front of backend engineers.
It feels wrong at first.
GitHub is for code, not contacts, notes, passwords, dashboards, or whatever tiny personal app you are hacking together at 2 am.
But then you build enough side projects and realize something funny. A lot of small apps do not need a real database on day one. They need persistence, history, private access, backups, and a simple way to edit or recover data when something goes wrong. GitHub gives you all of that almost for free.
That is why I used GitHub as the backend for my personal data vault.
The Context
Some context first. This vault is not a public app and it is not meant to be a polished SaaS product. It is a private personal tool running behind my own auth layer, built for one user: me. That changes the engineering tradeoffs a lot.
The app is simple. It stores contacts, notes, and password entries. The frontend is a Next.js app. The “database” is a private GitHub repo with a few JSON files inside it. Contacts live in contacts.json, notes live in notes.json, and passwords live in passwords.json. The app reads and writes those files using the GitHub Contents API.
So the backend looks almost stupid:
contacts.json
notes.json
passwords.json
No Postgres.
No Redis.
No migrations.
No admin panel.
No backup script.
Just files.
And for this kind of project, that simplicity is the feature.
Why It Felt Right
The best part is that every change becomes versioned automatically. If I accidentally delete a note, GitHub has the history. If the app writes something weird, I can inspect the diff. If I want to migrate data later, I already have plain JSON files instead of some mystery database state sitting inside a volume.
This is very underrated for personal tools. With normal databases, you always end up thinking about backups. Where is the volume. Did Docker mount it correctly. What happens if the VPS dies. How do I restore it. With GitHub, the data is already remote, versioned, and easy to clone.
The second win is portability.
JSON files are boring in the best way. I can open them in any editor, transform them with a script, export them, import them, or move them to a proper database later. There is no lock in, no hidden schema, and no magic.
The third win is operational laziness.
A private GitHub repo already has access control. The API already has auth. The storage is already hosted. For a small personal app, not having to operate another service is a massive quality of life improvement.
The Shape
The architecture is basically this:
Next.js app
-> API route
-> GitHub Contents API
-> private repo
-> JSON files
Reads are straightforward. Fetch the file from GitHub, decode the content, parse JSON, return it to the UI. Writes are also simple, but there is one important detail. GitHub needs the latest file SHA when you update a file, so the app first reads the file metadata, then sends the updated content with that SHA. That is how GitHub knows which version you are replacing.
This also gives you a soft concurrency check. If two writes happen against an old version, GitHub can reject the update instead of silently overwriting newer data. It is not the same as a real transaction system, but for a personal vault where one person is using the app, it is good enough.
The Real Design Rule
The biggest lesson from building it was that the data layer needs to stay boring. I kept GitHub logic in one connector style file. The UI does not know about GitHub. Contacts, notes, and passwords do not manually call random API endpoints everywhere. They go through a small generic read and write layer.
That matters because GitHub is a storage choice, not the identity of the app. If I later move the vault to Postgres, SQLite, S3, or even local encrypted files, I should not have to rewrite every screen. I should only replace the storage connector.
This is where many quick projects go wrong. The storage trick leaks into the whole app. Suddenly every component knows too much. The UI knows file names. The forms know API details. The business logic knows GitHub response shapes. That is fun for one evening and painful forever.
So the rule is simple:
Keep GitHub at the edge.
Where It Breaks
There are also real downsides. GitHub is not built for high write volume. It is not built for complex queries. It is not built for multi user collaboration with frequent updates. It is not built for low latency writes. If your app needs search across millions of records, relational joins, analytics, background jobs, or serious permissions, use a real database.
GitHub as a database works best when the data is:
- small
- personal
- file shaped
- valuable enough to version
- not written by many people at once
Config files. Personal notes. Small CMS content. Portfolio data. Feature flags for a side project. A tiny internal dashboard. Seed data. Static app content. These are good fits.
It is a bad fit when the app has many users writing at the same time, needs strict transactions, stores sensitive data without encryption, or depends on fast dynamic queries. Passwords are especially tricky. If you store anything sensitive this way, you should think carefully about encryption, token scope, and who can access the repo.
The Tradeoff
For my vault, the tradeoff was acceptable because the goal was not to build a startup scale database or a public product. The goal was to build a private personal data place that I control, understand, and can recover easily. GitHub made that possible without adding infrastructure.
The funny thing is that using GitHub this way made the app feel more trustworthy, not less. Not because GitHub is magically a database, but because the failure modes are visible. The data is plain. The history is there. The storage is not hiding inside a container volume that I will forget to back up.
So would I use GitHub as a database for a production SaaS app. No.
Would I use it again for a personal tool, portfolio CMS, config dashboard, or tiny admin app. Absolutely.
The Point
The real lesson is not “use GitHub instead of Postgres.” The lesson is to match the storage to the shape of the problem. Sometimes the right database is a managed relational system with indexes, migrations, backups, and monitoring.
And sometimes it is just three JSON files in a private repo.