A personal backend platform — a self-hosted system that collects, stores, and serves your personal data through a unified API. Single-user only (yours). Think of it as your own private backend that powers a web UI, mobile apps, browser extensions, and an AI assistant that can query and modify your data.
Core idea: Instead of scattered scripts and one-off tools, you have one extensible system that owns all your data.
The system has three layers:
- Backend — API server handling all business logic and data persistence. Runs on its own port.
- Frontend — Web UI for interacting with your data. Runs independently, communicates with the backend via HTTP.
- Database — Stores all your data. Single file, zero config.
You can serve the frontend from the backend, run them as separate processes, or deploy them independently. Whatever works for you.
Background workers run on a schedule to fetch external data (prices, feeds, APIs) and write to the database.
- Minimal — Start simple, add only what's needed
- Iterative — Improve incrementally, don't build advanced features at once
- Simple code — Favor readability over cleverness. Plain functions over abstractions.
- Single-user — No auth, no roles, no multi-tenancy. It's your data.
- Own your data — Everything lives in your database. No SaaS lock-in.
Use a lightweight routing approach. Map "METHOD /path" strings to handler functions. Support :id style params. No heavy framework needed.
Example pattern:
GET /api/books → list all books
GET /api/books/:id → get single book
POST /api/books → create book
PUT /api/books/:id → update book
DELETE /api/books/:id → delete book
Group routes by domain. Each route file exports a map of "METHOD /path" → handler. A central router matches incoming requests to the right handler.
Use a simple cron scheduler. Workers run at scheduled times, fetch external data, and write to the database. On failure, send a notification (push notification, email, etc.).
- Use SQLite for simplicity (single file, zero config, backups are just file copies)
- Use an ORM with codegen (like Drizzle ORM) for type-safe queries
- Start with a few tables, add more as you need them
- No migration system needed initially — just update the schema and restart
book — Personal library (title, author, status, rating, genre, notes)
movie — Movie collection (title, year, director, actors, rating, metadata)
game — Game library (title, platform, playtime, tags, purchase_date)
subscription — Recurring costs (name, amount, billing_cycle, next_billing, active)
workout — Exercise log (type, duration, distance, notes, date)
Pick what matters to you. The point is: your data, your schema.
- Single-page application
- One layout component with a sidebar for navigation
- Each domain gets its own page (books, movies, games, etc.)
- Use a component library or build simple primitives (button, card, input, dialog)
- Clean and minimalist
- Consistent page wrappers with loading states
- Keyboard shortcuts for common actions
- Simple typed API client
- In-memory cache with invalidation on mutations
- No need for complex state management — keep it simple
Build an AI chat interface that can query and modify your database.
Use an AI SDK (like Vercel AI SDK) with function calling / tool use. Provide two tools:
-
queryDatabase— Read-only SELECT queries. Validates that the query only reads from known tables. Returns results as JSON. -
executeWrite— INSERT/UPDATE/DELETE queries. Requires explicit user confirmation in the UI before execution. Never auto-execute writes.
Give the AI assistant:
- The full database schema
- Guidelines for writing safe queries
- Context about what the data represents
- Instructions to be helpful and conversational
- Render tool calls inline (show the SQL being executed)
- Show Confirm/Cancel buttons for write operations
- Stream the AI response
- Keep conversation history
This lets you chat naturally: "Show me my unread books" or "Add a new movie: Inception, 2010" — and the AI handles the database operations.
The pattern for adding any new domain:
- Add a table to the database schema
- Create a route file with CRUD endpoints
- Build a page in the frontend
- (Optional) Add a worker if it fetches external data
- Update the AI system prompt so it knows about the new table
That's it. Each new feature follows the same pattern.
These are what I use, but choose whatever you're comfortable with:
| Layer | My Choice | Why |
|---|---|---|
| Runtime | Bun | Fast, built-in bundler, Bun.serve() for HTTP |
| Database | SQLite | Single file, zero config, easy backups |
| ORM | Drizzle ORM | Type-safe, codegen, lightweight |
| Frontend | React | Familiar, great ecosystem |
| Styling | TailwindCSS | Utility-first, fast |
| AI | Vercel AI SDK | Clean API, streaming, tool use |
| Scheduling | Croner | Simple cron expressions |
| Notifications | ntfy.sh | Self-hosted push notifications |
| Linting | Biome | Fast, single config for all code |
Start with these three things:
- One table — Pick something simple (books, movies, workouts)
- CRUD endpoints — GET, POST, PUT, DELETE for that table
- A page — List + create + edit UI
Once that works, add:
- A second table
- The AI chat endpoint with
queryDatabasetool - Background worker for one external data source
- More pages
Don't overthink it. Build the simplest thing that works, then iterate.