Database-Agnostic by Default: Why a RESTful Backend Is the Portable Unit of Infrastructure
Every team that has migrated between databases knows the lie of "we use standard SQL, we can swap engines any time." SQL portability is real. Database portability is not. The application that was supposed to be agnostic turns out to be coupled to driver semantics, connection-pool ergonomics, authentication schemes, provisioning workflows, and networking assumptions that live far below the SQL layer. The seam is in the wrong place.
This article argues that a RESTful Backend-as-a-Service surface moves the database seam from the driver layer up to the HTTP boundary — a much better place to swap across. It walks through what HeliosDB BaaS actually exposes, shows a 60-second bootstrap, and lays out the deployment matrix that this architecture enables.
The Hidden Coupling of "Just Switch Databases"
When a team says "our app is database-agnostic," they almost always mean "our queries are standard SQL." The queries might be portable. The application usually is not, because every mainstream driver exposes more than SQL:
- Driver semantics. Prepared-statement caches, named-statement lifecycles,
DEALLOCATEbehaviour, pipelining rules, copy-protocol shape, notification channels. Swap the driver and these change. - Connection pooling. PgBouncer-in-transaction-mode forbids things session-mode allows. Whether you can hold a transaction across HTTP requests depends on pooler configuration the app can't see.
- Authentication. SCRAM-SHA-256, TLS client certs, cloud IAM tokens, rotating secrets injected by a sidecar, short-lived tokens from STS — the app's connection code embeds an opinion about all of this.
- Provisioning. Terraform for one provider, a managed console for another, a CLI for a third. The "database" is also a Kubernetes operator, or an RDS instance, or a Fly Postgres cluster. The shape of "create a database" is different everywhere.
- Networking. VPC peering, private endpoints, IPv6, DNS resolution inside the cluster vs outside. The connection string is a lie built on top of a pile of network config.
These are real costs. They are the reason a "portable" app takes three sprints to migrate. The SQL was never the hard part.
What a RESTful BaaS Changes
HeliosDB exposes three surfaces against the same engine: the PostgreSQL wire protocol (5432), a REST API under /api/v1, and a Model Context Protocol endpoint under /api/v1/mcp. Each caller picks the surface that fits. The REST surface is the one that delivers database-agnosticism, because it collapses all of the coupling above into a single idempotent boundary:
- Driver semantics disappear. Every call is an HTTP POST with a JSON body. There is no prepared-statement lifecycle to manage across requests.
- Connection pooling disappears. HTTP is stateless; the engine handles pooling internally. A serverless worker does not maintain a connection pool; it makes requests.
- Authentication becomes a header. A long-lived API key (
X-API-Key: hdb_…) for service-to-service calls, or a short-lived JWT inAuthorization: Bearer …for user-scoped calls. Rotation is a new header value, not a new connection. - Provisioning becomes a POST. One request creates an account, an organisation, a database, and an API key.
- Networking becomes egress to an HTTPS endpoint. No VPC peering, no DNS-inside-the-cluster dance. Any caller that can reach the internet can reach the database, under the auth you scope.
When every integration point is an HTTP call, the database stops being a service your app is bolted to and becomes a dependency your app holds at arm's length.
Bootstrapping in 60 Seconds
The full on-ramp is a single POST. It returns everything needed to run:
curl -X POST https://cloud.heliosdb.com/api/v1/quick-start \
-H "Content-Type: application/json" \
-d '{
"email": "agent@myapp.com",
"password": "<set-a-strong-one>",
"username": "agent",
"org_name": "MyApp",
"db_name": "main"
}'
# Response
{
"success": true,
"data": {
"token": "eyJ...",
"org_id": "uuid",
"db_id": "uuid",
"api_key": "hdb_...",
"endpoints": {
"execute": "/api/v1/organizations/{org_id}/databases/{db_id}/execute",
"branches": "/api/v1/organizations/{org_id}/databases/{db_id}/branches",
"query_at": "/api/v1/organizations/{org_id}/databases/{db_id}/query-at",
"api_keys": "/api/v1/organizations/{org_id}/databases/{db_id}/api-keys"
}
}
}
You now have a database. Execute SQL against it with the API key:
curl -X POST "$EXECUTE_URL" \
-H "X-API-Key: hdb_..." \
-H "Content-Type: application/json" \
-d '{"sql": "CREATE TABLE users (id SERIAL, name TEXT, embedding VECTOR(768))"}'
No schema migration tool. No connection pool config. No Terraform. The database is live and accepts arbitrary SQL, including VECTOR(n) columns with HNSW indexing, full-text indexes with BM25 scoring, materialised views, and the branching / time-travel extensions described below.
Deployment Flexibility: One Contract, Three Binaries
The REST surface is the same whether it is served by HeliosDB Nano embedded in a single container, HeliosDB Lite running as a managed instance, or HeliosDB Full in a multi-region cluster. That means an application that targets the REST contract has a deployment matrix rather than a dependency pin:
| Deployment | Engine | Footprint | When to pick |
|---|---|---|---|
| Embedded / edge | Nano | 32 MB binary, 30 MB RAM | Raspberry Pi, device-local, CI test harness, single-tenant dev loops |
| Single-node cloud | Lite | ~60 MB binary | SaaS production, edge + proxy, HTAP workloads |
| Managed BaaS | Cloud-hosted engine | Zero — no infra | Start here. Graduate to self-hosted if you need to. |
| Distributed cluster | Full | Server-grade, multi-region | Scale-out, multi-protocol, enterprise |
The code doesn't change across the four rows. Only the BASE_URL environment variable changes. An app that pushes BaaS to its limit on the managed free tier, then moves its most sensitive workload to self-hosted Nano in a Docker container, then eventually to a Lite instance behind its own load balancer, writes each of those migrations as a config flag — not a code rewrite.
Scalability: Stateless Frontline, Per-Database Auth
Serverless workers and edge runtimes struggle with traditional databases because every invocation wants a fresh connection, and databases charge for connection state. Reconnect storms on cold starts are a real operational cost; connection poolers are a patch, not a fix.
A stateless REST frontline dissolves the problem. Every invocation makes an HTTP request. The engine handles pooling internally. You can scale your workers linearly and the database will not notice.
Scaling the authorisation model matters just as much. The naive pattern — one database user shared across a fleet of workers — trades every security boundary for convenience. The HeliosDB pattern is the opposite: per-database API keys, issued programmatically, scoped to read, write, or admin, revocable without touching IAM.
# Issue a scoped key for a single worker pool
curl -X POST "$API_KEYS_URL" \
-H "X-API-Key: hdb_admin_..." \
-d '{"name": "ingest-worker-us-east", "scopes": ["write"]}'
# Response (shown once; store securely)
{ "api_key": "hdb_...", "name": "ingest-worker-us-east", "scopes": ["write"] }
A worker pool has a write-only key. A read-replica dashboard has a read-only key. A migration job has an admin key that lives for five minutes and is revoked. The key's blast radius is the database it belongs to — nothing higher, nothing sideways.
OpenAPI and MCP: Clients Come Free
The REST surface is described by an OpenAPI 3.1 spec at /api/v1/openapi.json. Point openapi-generator or openapi-typescript at it and you get a typed client in any language your team uses — TypeScript, Python, Go, Java, Rust, Kotlin. Versioning lives in the spec, not in hand-maintained SDKs that lag the API.
The same engine exposes a Model Context Protocol endpoint at /api/v1/mcp. MCP-aware agents (Claude, Cursor, and any tool that speaks the protocol) can discover and call database operations as tools without a custom adapter:
Updated Oct 2026: the MCP endpoint now requires an API key. Send the hdb_... key returned by POST /api/v1/quick-start (or generated per database in the console) as Authorization: Bearer hdb_... or X-API-Key: hdb_...; without it the endpoint answers 401. Account creation moved out of MCP: quick_start is now REST-only. Since 2026-10-02 the endpoint is a standard MCP server (Streamable HTTP, protocol 2026-07-28 and 2025-03-26 to 2025-11-25, stateless): Claude Code, Cursor, VS Code, the MCP Inspector and the official SDKs connect to it directly with the key as a header, and all 27 listed tools work over MCP, including list_databases, create_database and delete_database. A refused call comes back as an isError result with the reason.
curl -X POST https://cloud.heliosdb.com/api/v1/mcp \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $HDB_API_KEY" \
-d '{"jsonrpc":"2.0","method":"tools/list","id":1}'
The returned tools (27 at the time of the update) include execute_sql, create_branch, switch_branch, merge_branch, query_at, and create_api_key. An agent building a new feature can create a staging branch, run migrations on it, test, and merge — all without a human writing any HTTP glue.
When REST Isn't the Right Surface
Three workloads are still better served by the PostgreSQL wire protocol, and HeliosDB exposes that on the same engine for exactly this reason:
- Row-streaming analytics. Returning millions of rows over HTTP is a bad idea. Stream them over the wire protocol via
psql,COPY TO, or a server-side cursor. - Long-lived
LISTEN/NOTIFY. These are session-scoped and work best on a persistent connection. - High-throughput bulk ingest. The
COPYprotocol dominates row-by-row INSERTs for ingest pipelines by an order of magnitude. Use it.
The test is not "REST or wire protocol." It's "REST for anything stateless, wire protocol for anything streaming or long-lived." Same engine, same data, same auth model — just different protocols picked per caller.
The Composition
A RESTful BaaS is not a replacement for traditional database interfaces. It is a better default for the kinds of callers that dominate modern applications — serverless functions, mobile clients, edge workers, AI agents, CI jobs — and a cleaner seam for the cases where you need to move from managed to self-hosted, or from a single node to a cluster.
The argument is not "use a BaaS and you are portable." It is sharper than that: portability lives at the HTTP boundary. Put the seam there and everything downstream — drivers, pools, auth, provisioning, networking — stops being your application's problem.
Next in this series: a hands-on walkthrough building a semantic-search notes app against the same REST surface, and an architectural deep-dive on the three-protocol design. You can read about the BaaS features directly on the BaaS page or inspect the full OpenAPI spec.