How Nimbus works
Nimbus is a source-available reactive document backend that ships as one binary. That binary is simultaneously the server, the protocol surfaces, the function runtime, the storage layer, and the CLI you operate it with. This page is the conceptual map: what lives inside the binary, how a request moves through it, and why “drop-in compatible” is an architectural property rather than a marketing one.
One binary, five layers
Section titled “One binary, five layers”Inside the single process, responsibilities are split into layers that mirror the codebase’s crate boundaries:
- Server / transport (
nimbus-server) owns all network I/O: the native HTTP and WebSocket API, plus the routes and listeners that carry each compatibility protocol. It binds to loopback by default and is the integration point that wires every other layer together. - Protocol adapters (
nimbus-convex,nimbus-firebase,nimbus-cloud-functions,nimbus-mongodb,nimbus-dynamodb) each own one foreign dialect — wire formats, auth handshakes, error shapes — and translate it into engine operations. Adapters are transport-agnostic libraries; the server decides how each one is exposed. - Engine (
nimbus-engine) is the central coordinator. Every read, write, subscription, and scheduled job — from any surface — flows through theEnginetype. It owns validation, authorization, query planning, the mutation path, and subscription fan-out. - Runtime (
nimbus-runtime) executes your functions in-process in V8 isolates. It is deliberately standalone: it knows nothing about the rest of Nimbus and declares what it needs from the host through a singleHostBridgecontract. Bundles are SHA-256 integrity-checked before invocation. - Storage (
nimbus-storage) is the persistence layer behind one engine-facing contract with multiple providers: embedded SQLite is the default, redb is a second embedded option, and Postgres, MySQL, and libSQL are external options. See storage backends.
A small shared-types crate (nimbus-core) defines the vocabulary —
documents, tables, mutations, schemas, queries — used across all of them,
and nimbus-bin wraps the whole stack in the CLI: nimbus start boots
the server; nimbus dev runs the local development loop.
How a request flows
Section titled “How a request flows”Every data operation, regardless of which protocol delivered it, ends up on the same path:
client → transport → adapter → engine → storageThe transport accepts the connection and admits the request — including resolving which tenant it belongs to. The adapter for that protocol decodes the wire format and authenticates the caller. The engine authorizes the operation, validates it against the table’s schema if one exists, executes it, and the storage layer makes it durable. Results travel back out through the same adapter, re-encoded in the dialect the client expects.
Function invocations add one loop to this picture, not a second path.
When a request invokes one of your functions, the server hands the bundle
to the runtime executor, which runs it in a V8 isolate. Every host call
the function makes — ctx.db.insert(...), a query, scheduling work —
crosses the HostBridge into a server-owned bridge that calls the same
engine methods a direct HTTP request would. There is no privileged side
channel for code running inside the database: a write from a function and
a write from an HTTP client converge on the identical engine-owned
mutation path, with the same validation, authorization, and subscription
fan-out. Data and mutations covers that
path in detail.
The runtime’s isolation from the rest of the system is structural. The
runtime crate has no dependency on the engine or storage; it only defines
the HostBridge trait, and the server supplies the implementation. Guest
code can reach exactly what the bridge exposes — nothing else. Functions
can also opt into Node.js compatibility targets while keeping the same
in-process invocation model; see
how the Node runtime works.
Tenancy is a first-class boundary
Section titled “Tenancy is a first-class boundary”A single Nimbus server hosts many tenants, and the tenant boundary is built into every layer rather than filtered in at the edges:
- The server admits each request to exactly one tenant before any lower layer sees it.
- Every engine operation is tenant-scoped — there is no engine API that reads or writes across tenants.
- Storage gives each tenant its own namespace: a separate database file on the embedded providers, a separate schema on Postgres, a separate database on MySQL, a separate namespace on libSQL. A cross-tenant query is not merely forbidden; it is inexpressible at the storage layer.
- Function invocations are bound to their tenant before any JavaScript runs; the host bridge they call through carries that binding.
This is also the scaling model: Nimbus scales by distributing tenants, not by sharding inside one tenant’s data. The full trust model — runtime tiers, sandbox boundaries, auth — is in tenant isolation, with operational procedures in the tenant isolation operator guide.
What “drop-in compatible” means
Section titled “What “drop-in compatible” means”Nimbus does not embed five databases. It runs one engine with one data model, one mutation path, and one subscription system — and each adapter teaches that engine to speak a foreign protocol natively enough that existing clients work unchanged:
- Convex: HTTP function routes and the Convex WebSocket sync protocol, executing Convex-style queries, mutations, and actions in the V8 runtime.
- Firestore: the Firestore v1 API — REST document and query
endpoints plus the gRPC surface, including
Listenstreams for realtime updates. - Cloud Functions: HTTP-triggered and callable function workloads served over the same HTTP surface.
- MongoDB: a dedicated listener speaking the MongoDB wire protocol with SCRAM authentication, so official drivers connect with a normal connection string.
- DynamoDB: a dedicated HTTP endpoint accepting SigV4-signed,
X-Amz-Target-dispatched requests from unmodified AWS SDK clients.
The architectural consequence is the interesting part: because adapters translate at the boundary and share the engine underneath, every surface inherits the same guarantees. A document written through the MongoDB adapter is durably journaled, indexed, and pushed to subscribers exactly like one written through Convex or the native API — the adapters own dialect, never data semantics. Adapters authenticate callers and normalize their identities; authorization happens once, in the engine, for all of them.
Each adapter has a front door under Developers and a compatibility matrix under Reference, so you can see precisely which operations each dialect supports today.
Where to go next
Section titled “Where to go next”- Data and mutations — the data model, the single mutation path, and the consistency guarantees it produces.
- Tenant isolation — the multi-tenant trust model in depth.
- How the Node runtime works — the function runtime’s compatibility contract.
- Developers — pick a protocol surface and build.
- Storage backends — choose and operate a persistence provider.