BB

backenly/backenly

Developer tools
83 stars 19 forks Quality 76 Trend 53Apache-2.0

Autonomous backend platform. Your coding agent builds it over MCP, Backenly keeps it running: Postgres, REST APIs, auth, storage, realtime, and functions, with every change governed, verified, and...

Overview

The autonomous backend platform for agentic coding. PostgreSQL, REST APIs, auth, storage, realtime, and functions, driven by your coding agent over MCP, with every change planned, verified, and reversible. https://github.com/user-attachments/assets/2b215d72-4b1b-4a4c-8290-a94553be192d ⭐ to follow releases and help other builders find Backenly. Most backend platforms hand you primitives and leave you owning schema design, API wiring, RLS policies, monitoring, and recovery. Newer agent-native backends hand an agent raw SQL and no safety net. Backenly does neither. You describe the product you're building through the coding agent you already use (Claude Code, Cursor, Codex) over an MCP server. Backenly derives the data model, generates the endpoints, writes the policies, applies the change, and then . A continuous autonomy loop keeps watching after that, repairing drift, missing indexes, broken triggers, and RLS gaps on its own.

README

⭐ Star the repo to follow releases and help other builders find Backenly.


What this is

Most backend platforms hand you primitives and leave you owning schema design, API wiring, RLS policies, monitoring, and recovery. Newer agent-native backends hand an agent raw SQL and no safety net.

Backenly does neither. You describe the product you’re building through the coding agent you already use (Claude Code, Cursor, Codex) over an MCP server. Backenly derives the data model, generates the endpoints, writes the policies, applies the change, and then verifies it against the live runtime. A continuous autonomy loop keeps watching after that, repairing drift, missing indexes, broken triggers, and RLS gaps on its own.

The distinction that matters: Backenly does not just generate backend resources, it manages backend change safely. Every mutation, whether it comes from an agent, from the dashboard, or from an automated repair, goes through one typed action kernel with dry-run, audit, and rollback. There is deliberately no raw-SQL path for mutating structure. Reads are standard SQL, and your data is never locked in: direct Postgres connection strings and full pg_dump exports are one command away.

How it works

  • Intent-first. An LLM planner derives entities, relations, and actions from natural language. No table designer, no hand-written migrations.
  • One governed kernel. Every mutation flows through executeAction, so the agent’s model of the database and the database itself cannot silently diverge.
  • Closed-loop autonomy. The loop heals the reversible safe band by itself. Anything risky, such as auth, external credentials, or destructive and irreversible changes, always waits for a human.
  • Multi-tenant by construction. Each project gets its own PostgreSQL schema (workspace_{projectId}). Isolation is enforced by Postgres grants and RLS, never by application-level string filtering.
  • PostgREST data plane. Tables are served through PostgREST, so the query grammar you already know works unchanged.

Core capabilities

Capability What you get
Database PostgreSQL with a schema per project, served through PostgREST
Auth & Users Email/password and social sign-in, JWT sessions, RLS-forced user tables
Storage Public and private buckets with per-file access control
Realtime Shared LISTEN/NOTIFY hub for table change subscriptions
Functions Serverless route modules, validated before they ship and self-healed if they break
Integrations One ctx.integrations..request() surface for third-party APIs
Autonomy MAPE-K loop that observes, detects, proposes, applies, and verifies repairs
Monitoring Request logs with stability and reliability scoring
Branches Preview branches with their own sequences, plus diff and merge
Deploy Governed rollout with restore points and an audit ledger

What your agent can actually do

The MCP server advertises 20 tools. The catalog is capped deliberately, because tool-selection accuracy degrades as it grows, while the dispatcher stays wider so an agent pinned to an older manifest never gets a 404.

Group Tools
Understand read_backend_state · get_table_schema · run_query · fetch_docs
Build apply_migration · enable_auth · set_rls · create_bucket · generate_function · enable_realtime
Data db_insert · db_update · db_delete
Operate branch · create_api_key · set_env_var · get_database_credentials · check_approval · generate_types
Natural language backend_chat, the fall-through for anything not named above

Agents can also browse live project state as MCP resources (backenly://state, tables, apis, buckets, triggers) instead of spending a tool call to ask.

Quick start

Cloud

Create a project at backenly.com, then point your agent at it. Nothing to install or operate.

Self-hosted

One deployment is one project.

git clone https://github.com/backenly/backenly.git
cd backenly
npm install
npm run selfhost

That is the whole install. It generates the secrets, starts the Compose stack, creates the tables, runs the superuser steps, issues the PostgREST credential and reconciles until the deployment reports ready. Then:

npm run dev                   # dashboard :3000 · runtime :3001

Then claim the deployment. The installer prints a claim link carrying a setup token. Open it and create your account: the first account to present the token becomes the administrator and takes ownership of this deployment’s single project in the same step. There is no second command to run. The signup page also has a Setup token field while the deployment is unclaimed, if you would rather paste the token than follow the link.

The token is in .env as BACKENLY_SETUP_TOKEN. It gates the claim because a deployment is often reachable before its operator gets to it — an open port on a VPS, a preview environment, a colleague pointed at the wrong host — and without it the single administrator slot would go to whoever loaded the page first. Once the deployment is claimed the token stops working, whatever it is set to.

Email

Claiming the deployment needs no email. Two things do: password-reset codes, and the code a new account must enter before it exists if you open registration with BACKENLY_ALLOW_PUBLIC_SIGNUP=true. Set any SMTP provider in .env:

SMTP_HOST=smtp.resend.com     # or SES, Mailgun, SendGrid, Gmail
SMTP_PORT=587                 # STARTTLS; 465 is rewritten to 587
SMTP_USER=resend
SMTP_PASS=...
SMTP_FROM=Backenly    # a domain your provider has verified

Without it, the reset page says email is unavailable rather than pretending a code was sent, and later signups are refused rather than created unverified. To reset a password without email, run this on the server:

npm run auth:reset-password -- --email [email protected]              # prompts
npm run auth:reset-password -- --email [email protected] --generate   # prints one

It ends every session for that account. Access to the machine is the authority, as it is for the setup token.

The four credentials

The install creates a separate PostgreSQL role for each job, rather than one that does everything:

Role Used by Properties
backenly_user the install scripts only SUPERUSER. Creates roles, installs event triggers. Never in DATABASE_URL.
backenly_app web and runtime NOSUPERUSER NOBYPASSRLS. Owns the platform tables and workspace schemas, so governed typed actions keep their DDL rights.
backenly_authenticator PostgREST NOINHERIT; can only switch into anon / authenticated / service_role.
backenly_backup pg_dump only NOSUPERUSER BYPASSRLS, with CONNECT, USAGE, SELECT and nothing else.

backenly_app not being a superuser is the one that matters most. A superuser bypasses row-level security — including FORCE ROW LEVEL SECURITY — so an application running as one has its own policies applied only by convention. Ownership plus FORCE is what keeps DDL working while leaving RLS in force.

Restore runs over the application connection, not the backup one, so the application keeps ownership of the schema it restored.

npm run selfhost is safe to rerun. It fills in what is missing and never rotates a secret that already exists, so a rerun cannot sign out your sessions or break a running PostgREST.

It does not hide the steps or replace them: it runs the same sequence documented below, driven by the same module that npm run bootstrap prints its guidance from. Read that section if you are bringing your own database, if a step failed and you want to run it yourself, or if you would simply rather see what is being done to your machine.

The installer generates everything except one choice: which AI provider serves the natural-language tools (backend_chat, function generation). Set ANTHROPIC_API_KEY or OPENAI_API_KEY in .env, or AI_PROVIDER=bedrock with BEDROCK_REGION to use Claude through your AWS account. Without one, those tools say AI is unavailable; the typed tools and the self-healing loop need no model.

Doing it by hand

The same install, one step at a time. npm run bootstrap is the centre of it: it provisions the project, and it is a reconciler rather than an installer, so rerunning it repairs whatever is missing and changes nothing else. You will run it at least twice, and that is the intended path, not a failure.

You install on the host:

  • Node.js 20+ — the app, the scripts and the CLI all run here, not in a container
  • Docker, with Compose

The Compose stack provides:

  • PostgreSQL 15, with pg_stat_statements preloaded
  • Redis
  • PostgREST, pinned — this is the /db/* data plane

Docker does not provide Node. If you would rather run your own PostgreSQL or PostgREST, read Bringing your own database first; the steps below describe the Compose path.

Use a PostgreSQL cluster dedicated to this deployment.

PostgREST authenticates through cluster-global roles, and the list of schemas it serves is stored as a role setting with no IN DATABASE scope. Two Backenly databases on one cluster therefore overwrite each other’s served-schema registry, and the loser’s data plane answers PGRST106 on every table. This is a known limitation being redesigned, not something you can configure around. The Compose stack gives you a dedicated one.

1. Install

git clone https://github.com/backenly/backenly.git
cd backenly
npm install
cp .env.example .env

docker compose -f docker-compose.dev.yml up -d

postgrest will restart in a loop until step 4 issues its password. That is expected, and it is deliberate that it serves nothing until then.

Then set these in .env, adding any line the template does not already carry — today that is BACKENLY_PROJECT_ID:

BACKENLY_PROJECT_ID=       # any UUID, e.g. `uuidgen`
JWT_SECRET=
POSTGREST_JWT_SECRET=
STORAGE_SECRET=
ANTHROPIC_API_KEY=    # or OPENAI_API_KEY, or AI_PROVIDER=bedrock + BEDROCK_REGION

BACKENLY_EDITION is already single-tenant in the template and you can leave it there. Backenly resolves an unset edition to single-tenant too, so the line is not what makes this a self-host install — it is there because scripts/deploy.sh composes through scripts/compose-cloud.sh, which refuses to guess an edition rather than risk cloning a private repository you cannot read.

DATABASE_URL already matches the Compose stack, so leave it alone unless you changed POSTGRES_USER or POSTGRES_PASSWORD.

BACKENLY_PROJECT_ID is the identity of this deployment and must never change afterwards: it names the workspace_ schema your tables live in. Bootstrap refuses to run against a database that already holds a different one. You may leave it unset and pin the id bootstrap generates instead, but then step 4’s command has to be copied from bootstrap’s output rather than from here.

JWT_SECRET signs every platform session. POSTGREST_JWT_SECRET is separate and must match the value Compose passes to PostgREST, because the gateway mints short-lived internal tokens with it. STORAGE_SECRET signs the access tokens for stored files — without it every upload fails with a 500 whose only explanation is in the server log. None of the three has a default, deliberately: a fallback baked into a public repository is a key everybody already knows. The AI provider key powers the natural-language tools; .env.example lists the providers, per-purpose model overrides and monthly AI spending limits. See .env.example for the rest.

2. Tell the database which role Backenly connects as

Skip this on the Compose path. The stack already uses backenly_user, which is what the SQL defaults to.

It applies only if you brought your own database and the role in your DATABASE_URL is something else, in which case run, as a superuser:

psql -c "ALTER DATABASE  SET backenly.app_role = ''"

Skipping it there makes the next section fail with role "backenly_user" does not exist.

3. Create the tables and bootstrap

npm run db:generate && npm run db:push
npm run bootstrap

db:push belongs to this step only — do not rerun it later.

The PostgREST support objects are created by SQL in step 4, not by Prisma: a registry table and two event triggers that fire on schema DDL. prisma db push does not know about them, so on an installed deployment it treats them as objects to drop, which is exactly what --accept-data-loss authorises. The registry is what the data plane reads to decide which schemas PostgREST serves, so losing it takes /db/* down.

npm run bootstrap is the command that is safe to rerun. It is a reconciler and repairs whatever is missing.

This first run is expected to exit 3. Bootstrap creates the project row, its workspace schema and its signing secret, then reports what it cannot install itself. Its exit code is the state, and deployment automation should read it:

exit meaning
0 ready
2 refused. The database is in a state bootstrap will not touch, either more than one project or a BACKENLY_PROJECT_ID that does not match what is already there. Nothing was written.
3 core bootstrapped, prerequisites still unmet. Not ready: the /db/* data plane will answer PGRST106 until step 4 is done.

4. Install the prerequisites bootstrap cannot install itself

These reach the database as a superuser. Bootstrap prints this same list, with your real project id filled in; run what it prints.

# 1. support objects: registry functions, DDL event triggers, and the
#    anon / authenticated / service_role / backenly_authenticator roles.
bash scripts/postgrest-install.sh

# 2. passwords, role membership and the per-schema grants.
#    Prints the connection string PostgREST authenticates with. Store it: it is
#    not recoverable, and this command will not print it again.
npx tsx scripts/setup-postgrest-roles.ts --project  --apply

# 3. put that password in .env as POSTGREST_AUTHENTICATOR_PASSWORD, then start
#    the data plane. It has been restarting in a loop until now.
docker compose -f docker-compose.dev.yml up -d postgrest

# 4. optional. Only needed to hand out direct psql credentials to a project.
#    Backenly is fully operational without it.
bash scripts/install-sql.sh scripts/setup-direct-access.sql

Those scripts talk to PostgreSQL through one explicit mechanism, chosen by BACKENLY_DB_ADMIN, so the same command works wherever the database lives:

BACKENLY_DB_ADMIN how it connects
docker (default) psql inside the Compose postgres service
url psql -d "$BACKENLY_ADMIN_DATABASE_URL" — any reachable database
local sudo -u postgres psql — a cluster installed on this host

That order is not arbitrary and cannot be collapsed into one command. The first has to create the roles, because the event triggers it installs grant to them, and a CREATE SCHEMA that grants to a role nobody created aborts. The second grants per workspace schema, so it needs the schema npm run bootstrap already created above. This is why bootstrap runs, reports NOT ready, and is rerun: it is a reconciler, and rerunning is the mechanism.

Then rerun:

npm run bootstrap

Exit 0 means ready. Anything else, read what it printed and fix that; it is safe to rerun as many times as you need.

5. Run it

npm run dev                   # dashboard :3000 · runtime :3001

npm run dev starts both processes together. There is no account yet, so sign up in the dashboard, then run npm run bootstrap once more to issue the anon key your frontend embeds.

Autonomy

The self-healing loop runs by itself. There is nothing to schedule.

instrumentation.ts starts an in-process scheduler when the app boots, and it is active whenever VERCEL is unset — which is every self-hosted deployment. The reconciler ticks every minute. .env.example already sets the two flags it needs, so a .env copied from it has autonomy on:

ENABLE_AUTONOMY_RECONCILER=true      # the loop runs at all
ENABLE_AUTONOMY_LIVE_EXECUTION=true  # it may apply fixes, not just observe

Confirm it is ticking by watching the process output:

[AutonomyReconciler] Cron run complete — 1 projects, 0 fixes applied, 0 frozen

0 projects is normal on a new install, and does not mean it is broken. A project is only reconciled once something has been built in it (a table other than the built-in users table, a function, a bucket, a trigger, a webhook or enabled auth) and it shows recent activity. A project that has only been named has nothing to be right or wrong about, so the loop skips it and files nothing against it. Create a table through your agent and the count becomes 1 on the next tick.

CRON_SECRET is not required for any of the above. It guards one ad-hoc endpoint, for forcing a tick without waiting for the minute:

CRON_SECRET=   # add to .env, then restart
curl -H "x-cron-secret: $CRON_SECRET" http://127.0.0.1:3000/api/cron/autonomy

Nothing calls that URL on its own. The in-process scheduler is the mechanism; the endpoint is for operators.

Backups

Backups are a Backenly Cloud capability and the routes answer 404 on a self-hosted deployment. Nothing below is needed to run Backenly; it is here because the same code runs in both editions and the failure it describes is silent.

A backup is pg_dump of one project’s workspace_ schema, gzipped under BACKUP_DIR. It carries tables, rows, indexes, constraints, RLS policies, FORCE ROW LEVEL SECURITY and the end-user users table. It is not PITR and not deployment-level disaster recovery: project secrets, API keys, storage objects and function definitions all live outside that schema.

npm run selfhost creates the backup credential for you. It converges a dedicated backenly_backup role and records BACKUP_DATABASE_URL in .env, as the last step of the install — last because the objects it must be able to read are created at different times: the platform tables by the migration chain, the PostgREST registry and event triggers by the elevated SQL after it, and the workspace schema by bootstrap after that.

This used to be a manual step, and the instructions here were incomplete: they granted the workspace schema and not public, so an operator who followed them exactly still could not produce a Deployment Recovery bundle. pg_dump --schema public takes a LOCK TABLE on every table in the schema and stopped at the registry:

pg_dump: error: query failed:
ERROR: permission denied for table backenly_pgrst_schema_registry

The credential is needed because workspace tables are FORCE ROW LEVEL SECURITY, and under FORCE even the owner is subject to its policies, so pg_dump over the application role aborts on the first protected table:

pg_dump: error: query failed: ERROR: query would be affected by
         row-level security policy for table "users"

On production that meant every nightly backup failed for four days while the pruner deleted the last good ones, ending at zero on disk. The Compose stack hides it, because POSTGRES_USER there is the bootstrap superuser and superusers bypass RLS. Managed Postgres does not hide it.

Do not fix this by granting BYPASSRLS to the application role — that disables every RLS policy on every tenant at once. The installer creates a role with exactly this much and no more:

LOGIN NOSUPERUSER NOCREATEDB NOCREATEROLE BYPASSRLS NOINHERIT
CONNECT on the database
USAGE + SELECT on public and on every workspace schema
no CREATE, no INSERT/UPDATE/DELETE/TRUNCATE, member of nothing, owns nothing

To converge it by hand on an existing deployment, or after adding a schema:

npx tsx scripts/setup-backup-role.ts --apply

Restore deliberately writes back over DATABASE_URL instead, so the application keeps ownership of its own schema — restoring over the backup role would re-own every table to it, and FORCE ROW LEVEL SECURITY keys on the owner. tests/integration/backup-role-contract.spec.ts builds both roles for real and proves the whole contract, including that the application role is refused where the backup role succeeds.

Bringing your own database

The Compose stack is the supported path. If you already operate PostgreSQL, or run PostgREST under your own service manager, the differences are:

  • Point DATABASE_URL and DIRECT_URL at your database, and do step 2 above.
  • Run the step 4 scripts with BACKENLY_DB_ADMIN=url and BACKENLY_ADMIN_DATABASE_URL set to a superuser connection string, or with BACKENLY_DB_ADMIN=local if PostgreSQL is on this machine.
  • Start your own PostgREST against the connection string step 4.2 prints, with POSTGREST_URL in .env pointing at it and its JWT secret set to POSTGREST_JWT_SECRET. Use the version pinned in docker-compose.dev.yml: PostgREST reads its configuration out of the database, so a version difference changes what the data plane serves.
  • Enable pg_stat_statements yourself:
# postgresql.conf, then restart the server
shared_preload_libraries = 'pg_stat_statements'
CREATE EXTENSION IF NOT EXISTS pg_stat_statements;

It is how Backenly finds indexes that are missing by measurement — the columns Postgres is actually spending milliseconds filtering on — rather than only by schema shape. Without it that check reports itself as unchecked rather than passing, so nothing claims a guarantee it never evaluated. The Compose stack sets it already.

Connecting a coding agent

Backenly is built to be driven over MCP. Point your agent at the MCP server:

npx @backenly/mcp-server init

Works with Claude Code, Cursor, Codex, Cline, and Claude Desktop. Keys are scoped and revocable, and read-only keys serve a reduced tool set.

Restart your MCP host after installing. Tools stay absent until it reconnects, which looks like a broken install but is not one.

Verify the connection by asking your agent:

Call Backenly's read_backend_state tool and tell me what exists in this project.

Then describe what you want. The SDK is for the app you ship:

const backend = new BackenlyClient({ projectId, apiKey })
await backend.auth.signUp({ email, password })
await backend.posts.create({ title: 'Hello' })
await backend.posts.list({ filter: { published: true } })

Repository layout

Path What lives there
app/ Next.js routes: dashboard UI, platform APIs, and the public /api/v1/* runtime
lib/ai/ The Brain: planning, the tool loop, and executeAction, the governed mutation kernel
lib/orchestration/ The nine-phase pipeline from intent to verified change
lib/autonomy/ The MAPE-K loop that monitors and repairs running backends
lib/execution/ Schema writes, migrations, and rollback beneath the kernel
lib/tenant/ Schema isolation and the tenant boundary
packages/ Client libraries: SDK, CLI, MCP server (MIT)
prisma/ Platform schema: the public schema, not tenant data
server/ Express runtime serving the end-user API
scripts/ Operational tooling, probes, and the demo recording pipeline

AGENTS.md is the deeper architectural guide, and is written for coding agents working in this repo as much as for people.

Self-hosting vs Cloud

Self-hosting is free and complete. This repository is the whole platform: runtime, governance, and the full self-healing engine, not a stripped community edition. You bring the servers, the Postgres, and an AI provider key.

Backenly Cloud runs the same codebase, and handles infrastructure, backups, upgrades, and the autonomy tokens. pg_dump moves your data between the two in either direction.

Contributing

Pull requests are open and welcome. Bug reports, feature requests, questions, and code all help. Good first places to look are open issues, the probe and detector suite under lib/autonomy/, and client library ergonomics in packages/. If a change is large or moves an architectural boundary, open an issue first so we can agree the approach before you spend the time.

Before opening a PR:

npm run lint
npx tsc --noEmit
npm test
npx tsx scripts/preflight-oss.ts --tree    # no credentials in what you committed

Two things worth knowing before you write code here. Tests run against a real PostgreSQL instance, and the database is never mocked, because mocking it has caused production incidents here before. And every schema mutation goes through executeAction; a patch that writes DDL around the kernel will be sent back, no matter how correct the SQL is.

See CONTRIBUTING.md for the full guide.

Security

Please do not open a public issue for a security problem. See SECURITY.md for the private reporting route.

License

The platform is licensed under the Apache License 2.0.

The client libraries under packages/, the SDK, CLI, and MCP server, are MIT, so they impose nothing on the applications that embed them.

Trademark

Apache-2.0 §6 grants no rights to the Backenly name or logo, and this project does not grant them separately. You may fork, modify, self-host, and commercialise the software. Distributing it under the Backenly name requires written permission.

Describing your project as “built on Backenly” or “a fork of Backenly” is accurate and always welcome. See TRADEMARK.md for the full policy, including what you may do without asking.


View this README on GitHub

Install

npx tsx scripts/setup-postgrest-roles.ts --project <PROJECT_ID> --apply