Your database just became a git repository.
What if your database was also your version control? Every change tracked. Every value recoverable. Free hosting. Zero setup.
That's keyv-github - a storage adapter that uses GitHub repositories as key-value stores.
Consider what GitHub already provides:
For certain use cases, this is better than any managed database.
Require servers, maintenance, backups, security updates. Too much overhead for a simple script or prototype.
Monthly fees add up. Cold start latency. Vendor lock-in. Configuration complexity.
Not accessible remotely. No collaboration. No version history.
import Keyv from "keyv";
import KeyvGithub from "keyv-github";
const store = new KeyvGithub("owner/repo/tree/main", {
client: new Octokit({ auth: process.env.GITHUB_TOKEN })
});
const kv = new Keyv({ store });
// Your GitHub repo is now a database
await kv.set("config/settings.json", { theme: "dark" });
await kv.get("config/settings.json"); // { theme: "dark" }
Each key becomes a file. Each value becomes file content. Your repo structure is your data structure.
your-repo/
├── users/
│ ├── alice.json
│ └── bob.json
├── config/
│ └── settings.json
└── cache/
└── api-response.json
Keys map directly to file paths:
await kv.set("users/alice.json", { name: "Alice", role: "admin" });
// Creates: your-repo/users/alice.json
| Operation | API Calls | Description |
|---|---|---|
get |
1 | Fetch file contents |
set |
2 | Read SHA + create/update file |
delete |
2 | Read SHA + delete file |
has |
1 | Check file existence |
Every write creates a commit:
chore: update users/alice.json
chore: delete cache/old-data.json
Full audit trail. Rollback any time. Blame for every change.
GitHub API has limits:
For a simple CRUD app doing 10 ops/minute, that's 600/hour. Well within limits.
But for high-frequency access, you need caching.
The real power comes from combining stores:
import KeyvNest from "keyv-nest";
import { KeyvDirStore } from "keyv-dir-store";
import KeyvGithub from "keyv-github";
const store = KeyvNest(
memoryStore, // L1: Memory (instant)
new KeyvDirStore("./cache"), // L2: Local disk (fast)
new KeyvGithub("owner/repo") // L3: GitHub (persistent)
);
With this setup, GitHub is only touched on cold starts or when data changes.
// Centralized config that's version-controlled
const config = await kv.get("services/api/config.json");
// Update config (creates commit with history)
await kv.set("services/api/config.json", {
...config,
maxRetries: 5
});
// Blog CMS without a database
await kv.set("posts/hello-world.json", {
title: "Hello World",
content: "...",
publishedAt: new Date().toISOString()
});
// Build script reads directly from GitHub
const posts = await kv.get("posts/index.json");
// Serverless API backed by GitHub
export async function GET(request: Request) {
const { id } = parseParams(request);
const user = await kv.get(`users/${id}.json`);
return Response.json(user);
}
// Share cache across CI runners
const cache = new KeyvGithub("org/ci-cache");
// Save build artifacts
await cache.set("builds/latest.json", buildMetadata);
// Other runners read cached data
const cached = await cache.get("builds/latest.json");
new KeyvGithub(repoUrl, {
branch: "main", // Target branch
client: new Octokit(), // Authenticated client
prefix: "data/", // Path prefix for all keys
suffix: ".json", // File extension
msg: (key, val) => "...", // Custom commit messages
});
Keep your data organized:
const store = new KeyvGithub("owner/repo", {
prefix: "data/v1/",
suffix: ".json"
});
await store.set("users/alice", data);
// Writes to: data/v1/users/alice.json
const store = new KeyvGithub("owner/repo", {
msg: (key, value) =>
value === null
? `feat: remove ${key}`
: `feat: update ${key}`
});
Keys must be valid file paths:
// ✓ Valid
await store.set("config/settings.json", data);
await store.set("users/alice/profile.json", data);
// ✗ Invalid - throws error
await store.set("/absolute/path", data); // leading slash
await store.set("../escape/attempt", data); // directory traversal
await store.set("path//double", data); // double slashes
| Feature | keyv-github | Redis | SQLite | MongoDB Atlas |
|---|---|---|---|---|
| Cost | Free | $$$ | Local only | $$ |
| Setup | Zero | Config needed | File setup | Cloud setup |
| Version control | Built-in | None | None | None |
| Remote access | Yes | Yes | No | Yes |
| Query language | Key-based | Key-based | SQL | MongoDB QL |
| Best for | Config, small data | High-speed cache | Local apps | Complex queries |
npm install keyv-github
import Keyv from "keyv";
import KeyvGithub from "keyv-github";
const kv = new Keyv({
store: new KeyvGithub("you/your-repo", {
client: new Octokit({ auth: process.env.GITHUB_TOKEN })
})
});
// You now have a free, version-controlled database
await kv.set("hello", "world");
Install: npm install keyv-github
GitHub: github.com/snomiao/keyv-github
Related: keyv-nest for multi-layer caching
Snowstar Miao builds infrastructure tools that question conventional architecture.