Multi-tenant backend platform built with Hono, TypeScript, Bun, and PostgreSQL/SQLite. Monk API provides model-first data APIs, schema-isolated tenants, brokered human password authentication with Monk bearer tokens, ordered observer hooks, an HTTP filesystem API, optional app packages, and a cron surface for scheduled backend work.
The project is more than a CRUD service. It is a small programmable backend runtime: tenants define models and fields, the generic API operates on those models, observers enforce lifecycle behavior, and higher-level services can automate against the same tenant-scoped HTTP surface.
Read AGENTS.md before starting any task.
- Language: TypeScript with Hono framework
- Database: PostgreSQL (schema-per-tenant) or SQLite (file-per-tenant)
- Authentication: Brokered human username/password auth plus Monk-issued bearer tokens carrying Monk-owned access state
- Architecture: Ring-based observer system for model lifecycle behavior
- Runtime surfaces: HTTP API, dynamic
/app/*packages,/fs/*filesystem API, and cron scheduler - Distribution: Compiles to standalone executable with no external dependencies
Monk starts multiple surfaces from src/index.ts:
| Surface | Default | Purpose |
|---|---|---|
| HTTP API | PORT=9001 |
Public, auth, data, model, app, filesystem, and cron routes |
| Cron scheduler | PostgreSQL only | Tracks scheduled tenant jobs from the cron/process tables |
| Path | Purpose |
|---|---|
/health |
Health check |
/auth/register |
Register a new tenant and root user with tenant, username, email, and password |
/auth/login |
Verify tenant, username, and password and return a Monk bearer token |
/auth/refresh |
Refresh a Monk bearer token presented in Authorization |
/auth/tenants |
List registered tenants |
/docs/* |
Self-documenting API reference |
| Path | Purpose |
|---|---|
/api/data/:model[/:id] |
CRUD operations with relationship traversal |
/api/find/:model |
Advanced queries with 25+ filter operators |
/api/describe/:model |
Model and field definitions |
/api/aggregate/:model |
Aggregations ($sum, $avg, $count, $min, $max, $distinct) |
/api/bulk |
Multi-operation transactions |
/api/bulk/export |
Export tenant data to SQLite file |
/api/bulk/import |
Import data from SQLite file |
/api/acls/:model/:id |
Record-level access control lists |
/api/stat/:model/:id |
Record metadata (timestamps, etag) |
/api/tracked/:model/:id |
Field-level change history |
/api/trashed/:model |
Soft-deleted record management |
/api/user/* |
Self-service profile management |
/api/cron/* |
Scheduled process management |
/fs/* |
Tenant-scoped virtual filesystem access |
Operations on protected models require root/full authorization from the current Monk user row. Sudo and fake-token routes remain Monk-local token machinery layered on top of Monk bearer tokens.
Tenants define models and fields through /api/describe/*, then read and write records through /api/data/*. This lets the API serve many tenant-specific schemas without a new controller per resource.
Core model features include:
- Field types: text, integer, decimal, boolean, timestamp, date, uuid, jsonb, arrays
- Constraints: required, unique, default_value, minimum/maximum, pattern, enum_values
- Protection: sudo (requires elevated access), freeze (read-only), immutable (write-once)
- Indexing: btree indexes and full-text search support
- Change tracking: field-level audit trails with old/new values
- Relationships: traversal through
/api/data/:model/:id/:relationship
Monk includes higher-level APIs around the model runtime:
/api/find/:model- advanced filtering and ordering/api/aggregate/:model- count, sum, average, min, max, and distinct-style aggregation/api/bulk- multi-operation transactions/api/bulk/exportand/api/bulk/import- tenant data movement through SQLite files/api/stat/:model/:id- record metadata such as timestamps and etags/api/tracked/:model/:id- field-level change history/api/trashed/:model- soft-delete restore and purge workflows
Additional functionality is lazy-loaded under /app/:appName/* from workspace packages:
| Package | Path | Purpose |
|---|---|---|
| Grids | /app/grids/:id/:range |
Excel-style spreadsheet operations with cell ranges |
| Todos | /app/todos |
Example CRUD app demonstrating package pattern |
| OpenAPI | /app/openapi |
OpenAPI-related app package |
Apps can also install tenant models. When an app has tenant-backed models, the dynamic loader enforces authentication before installing or serving those model-backed routes.
The HTTP filesystem API is exposed at /fs/* and requires authentication. It lets authenticated clients read, write, and delete files in the virtual filesystem.
All endpoints support query parameters for response formatting:
Built-in:
json(default)yaml
Optional packages (install from packages/formatter-*):
toon- Compact format for LLMs (30-40% smaller)csv- Tabular exportmsgpack- Binary format (30-50% smaller)markdown- Markdown tablesgrid-compact- 60% smaller for Grid APIcbor,sqlite- Additional package-backed encodings
?unwrap- Remove{success, data}envelope?select=field1,field2- Return only specified fields
?encrypt=pgp- AES-256-GCM encryption using the presented bearer token material and Monk tenant/user salt
- PostgreSQL: Tenants share a regional database (e.g.,
us_east) with isolation via schema/namespace - SQLite: One file per tenant for portable, self-contained databases
- Monk derives a scoped external login identity from canonical
(tenant, username)values, verifies passwords through its upstream identity broker, then mints Monk bearer tokens for API access. - SHA256-based schema naming (enterprise mode) or human-readable (personal mode)
- Tenants evolve independently (different models, fields, data)
Monk's route handlers are intentionally thin. Much of the important model behavior lives in ordered observers under src/observers.
The observer system uses rings 0-9 for predictable lifecycle execution:
- Input validation
- Business logic hooks
- Database execution
- Audit/tracking
- External integrations
Observers attach to model operations such as create, update, and delete. Before changing data behavior, inspect the relevant observers as well as the route handler.
Four ACL arrays per record:
access_read- Read permissionaccess_edit- Edit permissionaccess_full- Full access (read/edit/delete)access_deny- Explicit deny (overrides other permissions)
User management lives under /api/user/*. Protected routes rely on Monk bearer tokens rather than client-presented upstream identity tokens.
Cron routes under /api/cron/* manage scheduled processes. The scheduler starts only in PostgreSQL mode because it depends on database-backed process tables, and new job creation is temporarily unavailable until the replacement execution backend lands.
On startup, the server initializes infrastructure, preloads observers, starts HTTP, and starts the cron scheduler when PostgreSQL is configured.
- Hono - Web framework
- TypeScript - Language
- PostgreSQL or SQLite - Database backends
- Bun - Runtime (compiles to standalone executable)
Use Docker only for the local PostgreSQL dependency. Railway deployment should use Railway's managed DATABASE_URL, not this compose file.
bun run db:local:upCreate .env for local development:
DATABASE_URL=postgresql://monk:monk@127.0.0.1:55432/monk
PORT=9001
NODE_ENV=development
JWT_SECRET=test
# Configure the auth broker variables required by your deploymentProduction auth is brokered through Monk: clients send username/password to Monk for login, and tenant + username + email + password for register. Monk verifies or provisions credentials through its upstream identity broker and returns Monk bearer tokens for API access.
Initialize the database after building:
bun run build
bun dist/index.js --no-startupReset the local database when you need a clean checkout state:
bun run db:local:reset
bun dist/index.js --no-startupProduction is hosted on Railway in the monk project.
| Resource | Link |
|---|---|
| Public API | https://monk-api-production.up.railway.app |
| Health check | https://monk-api-production.up.railway.app/health |
| Source repository | https://github.com/ianzepp/monk-api |
| Railway app service | monk-api |
| Railway database service | Postgres |
The Railway app service is linked to ianzepp/monk-api on main and uses Railway's managed Postgres DATABASE_URL. Do not use compose.local.yml for Railway.
DATABASE_URL,NODE_ENV,JWT_SECRET, and the auth broker credentials required by the current deployment are needed for production brokered auth.- Cron job definitions remain visible, but new job creation is temporarily unavailable until the replacement execution backend lands.
Prerequisites: Bun 1.0+, PostgreSQL 12+ (or SQLite for standalone)
# Clone and install
git clone https://github.com/ianzepp/monk-api.git
cd monk-api
bun install
# Configure environment
cp .env.example .env
# Edit .env with DATABASE_URL, JWT secret, and your auth broker settings
# Build and start
bun run build
bun run startStandalone (no PostgreSQL):
bun run build:standalone
./dist/monk-api # Single executable with SQLite# Register a tenant and root user
curl -X POST http://localhost:9001/auth/register \
-H "Content-Type: application/json" \
-d '{"tenant": "demo", "username": "root_user", "email": "root_user@example.com", "password": "secret-pass"}'
# Log in and use the Monk bearer token
MONK_TOKEN=$(curl -sS -X POST http://localhost:9001/auth/login \
-H "Content-Type: application/json" \
-d '{"tenant": "demo", "username": "root_user", "password": "secret-pass"}' | jq -r '.data.token')
curl http://localhost:9001/api/describe \
-H "Authorization: Bearer $MONK_TOKEN"- monk-cli - Command-line interface for Monk API
- monk-uix - Web browser admin interface
- monk-api-bindings-ts - TypeScript API bindings
| Document | Purpose |
|---|---|
| DEVELOPER.md | Architecture and development guide |
| TROUBLESHOOTING.md | Debugging guide |
| spec/README.md | Testing infrastructure |
| src/routes/docs/PUBLIC.md | Complete API reference |