An MIT-licensed, database-first framework for long-lived business applications on Bun.
Zero runtime dependencies. Only dev dependencies.
Reepolee has one hard prerequisite - Bun. Use the official installer:
macOS / Linux / WSL
curl -fsSL https://bun.sh/install | bash Windows
powershell -c "irm bun.sh/install.ps1|iex" Verify the install:
bun --versionUpgrade to canary (already Rust-based) for the latest features:
bun upgrade --canaryIf bun: command not found, the installer's ~/.bun/bin directory isn't on your $PATH - open a new terminal or run source ~/.bashrc (or ~/.zshrc) to pick up the change.
Reepolee is in beta. APIs, generators, and project conventions may change before 1.0.
-
Create a project
bun create reepolee/reepolee my-reepolee-project cd my-reepolee-projectbun createfetches the starter as a tarball, so the template's Git history never arrives - nothing to clean up - and it initializes a fresh repository with a single initial commit. If you prefer to clone the repository instead:git clone https://github.com/reepolee/reepolee.git my-reepolee-project cd my-reepolee-project -
Run the install script
bun reepolee:install
This script installs:
- Project dependencies via
bun install tw- the Tailwind v4 Standalone Executablereettier- the Ree template formatterreesql- the SQL formattervendor/folder - vendored packageslibvips- VIPS CLI for image processing
The bootstrap runs once: a marker file in
.reepolee/records the initialization, so runningbun reepolee:installagain re-runs the dependency and database steps but leaves your files untouched. Withbun createthe fresh repository and its initial commit come frombun createitself; with agit cloneyour history stays exactly as it is. - Project dependencies via
-
Verify the setup
bun dev
If you see
Listening on http://localhost:2338and the page loads in a browser, your setup is complete.
If you cloned the repository instead of using the release archive:
cp .env.example .env # edit CONNECTION_STRING
bun install
bun get:pre # fetch prerequisites
bun dev # server with hot-reload (CSS rebuilt on change by the dev watcher)Reeman Quick Start reads the following values from .env as defaults for its
admin-user prompts:
ADMIN_USERNAME=admin
ADMIN_EMAIL=admin@example.com
ADMIN_PASSWORD=passwordQuick Start still asks you to review or change each value, shows all three resolved values, and asks for confirmation before creating the user.
The bun get:pre command fetches all globally installed tools and vendored files needed to run this project. Verify with:
bun -v
tailwindcss -h | grep v # or findstr v on Windows
reettier --versionIf some are already installed, pick individual scripts:
| Purpose | Script | What it does |
|---|---|---|
| Tailwind CSS | bun get:tw |
Downloads standalone Tailwind v4 binary to ~/bin/tw (avoids npm collision issues) |
| reettier | bun get:reettier |
Downloads & installs the reettier binary from GitHub releases |
| reesql | bun get:reesql |
Downloads & installs the reesql binary from GitHub releases |
| Zod (vendored) | bun get:zod |
Downloads vendor/zod.min.js from jsDelivr ESM |
| Zod types (vendored) | bun get:zod-types |
Fetches the matching zod npm tarball and copies only its .d.ts files into vendor/zod-types/, so vendor/zod.min.js type-checks against zod's real API |
| highlight.js (vendored) | bun get:hljs |
Downloads vendor/highlight.min.js from jsDelivr ESM |
| Temporal polyfill (vendored) | bun get:temporal |
Downloads vendor/temporal.min.js from esm.sh |
| Alien DeepSignals (vendored) | bun get:signals |
Downloads static/alien-deepsignals.min.js from esm.sh |
| DPU polyfill (vendored) | bun get:dpu |
Downloads static/dpu.min.js (HTML <template> setters polyfill from GoogleChromeLabs) for DPU streaming support |
| libvips (installed) | bun get:vips |
Downloads & installs libvips for image processing (crop, resize). Supports Windows (from GitHub releases), macOS (Homebrew), and Linux (apt/dnf/pacman). Auto-adds to PATH. |
We need vips from libvips for image manipulation. Yes, we know Bun.Image() exists, but we need the crop function.
The Ree Templates extension adds syntax highlighting and formatting for .ree files. Install from the marketplace or via CLI:
code --install-extension reepolee.ree-templatesTailwind IntelliSense - works inside .ree files if you tell it to. Add to VSCode settings:
"tailwindCSS.includeLanguages": { "ree": "html" }TypeScript - the language server picks up .ts files natively, no extra configuration needed.
Treat .ree as HTML. The HTML syntax highlighter handles most of Ree fine - the tag prefixes ({=, {~, {_, {-, {#, {:, {/, {{) are visually distinct enough that the HTML grammar ignores them cleanly.
| Tool | Command | Purpose |
|---|---|---|
| oxlint | bun add -g oxlint |
JS/TS linting (not required, nothing depends on it) |
| gh (GitHub CLI) | Per-platform install | Create pull requests, manage issues, run deploy workflows from CLI |
| jq | Per-platform install | Filter NDJSON SQL logs |
| certbot | Per-platform install | TLS certificates on the production server (reverse proxy) |
Install whichever fits your workflow. None are needed to run Reepolee.
The image editor / avatar pipeline relies on the native libvips library. Reepolee ships a small installer (bun get:vips) that fetches a prebuilt libvips for your platform so you don't have to install it through a system package manager. The installer supports Windows (prebuilt from GitHub releases), macOS (Homebrew), and Linux (apt/dnf/pacman), and adds libvips to your PATH automatically.
If you don't use the image editor or avatar uploads, you can skip this - the rest of the app runs without libvips.
bun dev # Server with --hot reload (CSS rebuilt on change by the dev watcher)
bun dev:worker # Same, plus the queue worker (bun --hot worker.ts)
bun dev:reeman # Reeman app only (server.reeman.ts, port from REEMAN_PORT)
bun dev:all # App server + reeman app side-by-side (everything in one command)
bun run worker # Start background worker separatelybun test # Full suite (--parallel)
bun run db:clone-test # Clone production DB → test DB (requires TEST_CONNECTION_STRING)Set TEST_CONNECTION_STRING in .env to a database with "test" in the name. The safety guard refuses non-test databases.
Install pre-commit hooks to run, we supply reettier:
bun git:hooksbun run css:build # Build minified CSS
bun start # bun server.ts --prodTo bump the version:
bun pm version patch # Bump package.json version
bun run release # Bump version, commit, and package the release archive via the sibling ../reelease projectYou can also start PM2 for long term running
pm2 start operations/ecosystem.config.cjsand then use pm2 logs or pm2 monit to check the runtime progress.
- Runtime: Bun only. No runtime dependencies.
- Entry:
server.ts-Bun.serve()with route table fromroutes.ts. - Templates:
.reefiles inroutes/, custom engine atlib/template_engine.ts. - Routes: Route handlers export named functions, registered in
routes.ts. - Database: Bun's
SQLAPI → MySQL or SQLite viaconfig/db.ts. - CSS: Tailwind v4 via standalone CLI.
- Auth: Cookie-based sessions, invite-only registration, profile management.
- Generators: CRUD/schema/resource generators in
generator/.
Translations are stored entirely in the translations table in the database. The DB is the single source of truth - no JSON files. Every translation is a row (locale, namespace, key_path, translation), where locale is a full BCP 47 identifier such as en-us or sl-si.
To change translations, use one of:
UPDATE translations SET translation = ...orINSERT INTO translations (...) VALUES (...)- direct DB editsbun reeman sync-translations --translate- AI-powered sync that scans the DB, translates missing keys across all namespaces, and writes results back to the DB/system/translationsadmin UI - manual editing through the app
Translation reload endpoint: The server exposes POST /__reload-translations so generators and the queue worker can push fresh translations to a running server without a restart. It is disabled by default. To enable it, set INTERNAL_ADMIN_ENDPOINTS=true and a generated RELOAD_SECRET of at least 32 characters in .env; callers pass that value in X-Reload-Secret.
See AGENTS.md Translations and internals/CONTEXT.md for the full merge model, root fallback semantics, the prune tool, and limitations.
See internals/CONTEXT.md for the auth plugin contract, or inspect routes/system/auth/ for the implementation.
| Route | Access | Description |
|---|---|---|
/login |
Public | Login form |
/logout |
Any session | Clear session and redirect |
/register/:username/:invitation_code |
Invite link | Register with invitation code |
/profile |
Authenticated | Edit name, nickname, avatar |
/password |
Authenticated | Change password |
/invite |
Admin | Generate invitation links |
/invite/confirm/:token |
Admin | View invitation details |
Generator actions run through reeman - either interactively (bun reeman) or as a non-interactive
subcommand (bun reeman <subcommand> [args]). Interactive actions are also available as scripted commands
and share their underlying library functions. The CLI-only marketplace installer is the exception: it runs
the unpacked platform installer as a subprocess. User creation remains available through generator/user.ts.
bun reeman # Interactive menu
bun reeman schema <table_name|all> [--prefix <dir>] [--parent <table>] # Introspect DB → schema/
bun reeman crud <table_name> [--force] [--prefix <dir>] [--parent <table>] # Full pipeline: schema + CRUD
bun reeman crud all [--force] [--translate] [--prefix <dir>] # Full pipeline for every table
bun reeman bulk <table...> [--prefix <dir>] # Full pipeline for a specific set of tables
bun reeman refresh-crud <table> [--mode fields|full] # Regenerate CRUD for an existing route
bun reeman install <archive.tar.gz> # Install a marketplace archive
bun reeman sync-translations [namespace...] [--translate] # Sync translation keys across configured locales
bun reeman insert-translations # Write reviewable INSERT SQL for template keys missing from the DB
bun reeman prune-translations # Write reviewable DELETE SQL for unused DB keys
bun reeman add-locale <locale_code> [--translate] # Add a BCP 47 locale
bun reeman add-locale-alias <alias_locale> <target_locale> # Point one locale at another's translations
bun reeman remove-locale <locale_code> [--force] [--new-default <code>] # Remove a locale and its translations
bun reeman sync-locale-tables [table|all] [--dry-run] # Reconcile the per-locale clone tables
bun reeman remove-examples [--force] [--delete-translations] # Delete the shipped demo routes (routes/examples/)
bun reeman remove-route <url> [--force] [--delete-translations] # Delete one registered route
bun reeman remove-prefix-folder <name> [--force] [--delete-translations] # Delete a prefixed route folder and its sub-routes
bun reeman run-sql-file <path> [--force] # Execute a .sql file against the configured DB
bun reeman json-to-sql <path> --table <name> [--slug <slug>] # Turn a JSON file into a seeded table
bun reeman set-db-type <mysql|sqlite> # Switch DB type and rewrite CONNECTION_STRING
bun reeman set-session-driver <auto|redis> # Switch the session driver in .env
bun reeman check-domain-compliance [--verbose] [--fix] # Flag columns outside the canonical domain types
bun reeman --help # Full subcommand reference
bun generator/user.ts # Create a user with hashed passwordSee internals/GENERATOR_INTERNALS.md for the full documentation on common flags, reeman options, nested CRUD, and cursor & offset pagination.
Generated files per route:
routes/<table>/
├-- schema/ # Schema generator output
│ ├-- table.generated.ts # Auto-generated field definitions + TS types
│ ├-- table.ts # User-editable: exports fields, v_fields, columns
│ └-- validation_server.ts # Zod validation schemas
├-- translations/ # DB translation keys (generated by sync_translations)
├-- index.ts # Route handlers (CRUD)
├-- sql.ts # SQL queries (CRUD)
├-- sql_view.ts # View-based queries (if view exists, CRUD)
├-- form.ree # Create/edit form (CRUD)
└-- index.ree # List/index page (CRUD)
The server exposes a POST /__reload-translations endpoint that generators and the queue worker call after writing translated values to the database. This triggers reload_all_translations() and reload_route_maps() on the running server so navigation labels, route names, and all in-memory translations update immediately without a server restart.
Callers:
bun reeman sync-translations- after syncing all namespacesgenerator/schema.ts- after writing schema/nav translationsgenerator/crud/main.ts- after writing CRUD translationsworker.ts- after eachtranslate_batchjob completes
To enable the endpoint, set INTERNAL_ADMIN_ENDPOINTS=true and a generated
RELOAD_SECRET of at least 32 characters in .env. Callers must pass
X-Reload-Secret: <value> as a header:
curl -X POST http://localhost:2338/__reload-translations \
-H "X-Reload-Secret: $RELOAD_SECRET"Without both settings, the endpoint is not registered and behaves as a normal 404.
Every generated schema/table.ts includes export const route_param = "id";. For tables with non-integer PKs (e.g. VARCHAR id), change this value to use a different column for URL routing. The CRUD generator adapts all layers:
- Links in
index.reeuse the route_param column - Delete form POSTs to the route_param URL
- SQL generates
delete_record_by_route_param(value) - Delete pipeline uses the route_param directly (no extra SELECT)
The CRUD generator runs reettier on the generated route directory automatically.
| Flag | Description | Supported by |
|---|---|---|
--force |
Overwrite existing generated files without prompting. | crud, all, bulk |
--translate |
Use the configured AI provider to auto-translate generated translation keys into configured locales. | crud, all, bulk, sync-translations |
--prefix |
Nest generated routes under a subdirectory (e.g. --prefix admin). |
schema, crud, all, bulk |
--parent |
Mark as nested child of <table>. Auto-detects FK, scopes routes/queries. |
schema, crud, refresh-crud |
--pagination |
Pagination strategy: cursor or offset (default: offset). |
schema, crud, all, bulk, refresh-crud |
--refresh-fields |
Regenerate only field sections in form.ree/index.ree using CRUD markers. | refresh-crud --mode fields |
bun reeman <subcommand> [table] [--force] [--translate] [--prefix <dir>] [--pagination <type>] [--parent <table>]
| Subcommand | Description |
|---|---|
schema all |
Generate schemas for all eligible tables (introspects DB). |
schema <name> |
Generate schema for a single table. |
crud <name> |
Full pipeline for a single table: schema + CRUD. |
crud all |
Full pipeline for every table in the database. |
bulk <t...> |
Full pipeline for a specific set of tables (e.g. ones without CRUD yet). Same as reeman's interactive Bulk CRUD flow. |
refresh-crud <name> |
Regenerate CRUD for a route that already has a schema folder. |
install <archive.tar.gz> |
Unpack and run a platform-specific marketplace installer. |
sync-translations [namespace...] [--translate] |
Sync translation structure across configured locales, optionally filling missing values with the configured AI provider. |
add-locale <locale_code> [--translate] |
Add a BCP 47 locale to config/supported_locales.ts and initialize its DB translation rows. |
insert-translations |
Write reviewable INSERT SQL for template translation keys missing from the database. |
prune-translations |
Write reviewable DELETE SQL for database translation keys no longer referenced in templates. |
Run bun reeman --help for the complete list, including route removal, DB/session config, and translation subcommands.
Every reeman action - interactive or scripted - is appended as a plain command line to both .reepolee/reeman.sh and .reepolee/reeman.ps1 in the project root. Replay a whole session later on any platform with bash .reepolee/reeman.sh or pwsh .reepolee/reeman.ps1. In the interactive menu, the "Press Enter to continue..." prompt after an action also offers [c] to copy the equivalent CLI command to your system clipboard (via OSC 52 - works locally and over SSH).
For .ts, .js, and .sql files, use reettier / reesql via the Emerald Walk run-on-save extension:
"emeraldwalk.runonsave": {
"commands": [
{
"match": "\\.(js|ts)$",
"cmd": "reettier \"${file}\""
},
{
"match": "\\.(sql)$",
"cmd": "reesql \"${file}\""
}
]
},Marketplace apps are distributed as .tar.gz archives. Each archive must contain exactly one top-level folder with:
mysql/*.sqlandsqlite/*.sql- seed data for the demo's tables, one file per dialect.install.ps1- the Windows installer.install.sh- the macOS and Linux installer.- Any images or other assets the seed data references.
Install an archive from the project root:
bun reeman install ./studio.tar.gzReeman warns you to back up the repository and database, then asks for confirmation before making changes. It unpacks the archive to marketplace/<folder>/, runs install.ps1 on Windows or install.sh on macOS and Linux from the project root, and finally asks whether to keep or remove the unpacked marketplace folder. The installer script is trusted project code and may modify routes, configuration, and database data.
We use Podman by default on our Mac Minis. The scripts can also run with Apple's native
container CLI on macOS 26+ by setting CONTAINER_ENGINE=container.
chmod +x ../containers/containers.sh
../containers/containers.sh
# Apple container runtime
container system start
CONTAINER_ENGINE=container ../containers/containers.sh
CONTAINER_ENGINE=container bun run db:clone-testThere is a SeaweedFS-only start file. It uses the same CONTAINER_ENGINE switch.
chmod +x ../containers/seaweed.sh
../containers/seaweed.sh
# Apple container runtime
CONTAINER_ENGINE=container ../containers/seaweed.shcmd mcp add --transport stdio reepolee -- bun run scripts/mcp/index.tsReepolee Framework is built to keep web development fast, simple, and free of node_modules bloat. It is 100% free and open source.
If Reepolee Framework saves you time, powers your projects, or helps you ship clean static sites faster, consider supporting its ongoing maintenance and development!
- 🛠️ Maintenance & Bun compatibility updates
- 🚀 New features (plugin architecture, recipes)
- 📚 Continuous documentation improvements