Skip to content

Architecture

Packages, the query pipeline, storage mapping and conditional writes.

backd is a single stateless Go service. Apart from the registry built at startup, all state lives in MongoDB, so several identical instances can run side by side.

CONFIG_DIR ──load──▶ registry ──▶ provisioner ──▶ MongoDB
                        │                          ▲
HTTP ──▶ handlers ──────┴──▶ storage.Repository ───┘

Layers

PackageRole
internal/settingsEnvironment variables → Settings
internal/registryImmutable realm → database → collection tree, with realm settings (realm.yaml), compiled schemas and field index
internal/templatesEmbedded starter files written by backd template
internal/rulesCompiles the rules: of collection.yaml: expr-lang rules and runs the static checks (guards, roles, fields, now, read rules as filters)
internal/authStorage-neutral identity model (users, identities, sessions), password policy, argon2id hashing with a concurrency cap, sign-up, login and session handling; persistence behind auth.Store (in-memory fake in internal/auth/authtest)
internal/queryParses the list query language into storage.Conditions, checking every field and value against the schema
internal/storageBackend-neutral Repository interface, Document/Query/Page/Condition/Filter types, Fetch (single reads under a read rule), and the errors ErrNotFound, ErrVersionMismatch, ErrConflict and ErrUnavailable
internal/httpapiRouter, middleware, error envelope, document, /_auth and /_admin handlers, CORS
internal/jsonnumNormalizes JSON numbers to int64 or float64
internal/mongodbEverything MongoDB-specific: connection, validator translation, provisioner (including realm system databases), Repository and auth.Store implementations

No MongoDB driver types cross the storage interface. Another document store (for example Amazon DocumentDB) can be added as a new implementation without touching handlers or validation.

Query pipeline

Client queries never reach the database as-is:

  1. internal/query decodes where and accepts only the operators backd defines.
  2. It checks each field against the collection’s field index (plus id and the _meta fields: timestamps, version and the ownership fields) and type-checks each value against the schema. Read access rules are added as a separate storage.Filter tree.
  3. It produces a backend-neutral storage.Filter tree: storage.Condition leaves (field path, operator and normalized value) combined with And and Or for the object’s keys and its $or / $and groups.
  4. The repository translates the tree, and each operator, into its own query dialect, and ANDs it with the read rule’s filter.

The field index maps dot paths to their JSON Schema types. It includes properties of objects inside arrays under the array’s path (for example lines.sku), matching MongoDB’s dot notation. For array fields it also records the item types, so a scalar can be matched against array elements.

Stored documents

The repository maps between the API shape and the stored shape:

APIMongoDB
id (string, xid)_id (string)
_meta.created_at / _meta.updated_atBSON dates
_meta.version64-bit integer
_meta.owner, _meta.created_by, _meta.updated_bystrings (owner may be null), in realms with auth: enabled
user fieldsstored with keys sorted (recursively), after _id and _meta

Sorting keys makes stored documents deterministic. It also means that equality on a sub-document doesn’t depend on the key order the client sent.

Conditional writes

Repository.Replace and Delete take the version the handler read. The MongoDB implementation adds _meta.version to the write filter (null for documents stored before versioning). When nothing matches, it checks whether the document still exists, and reports ErrNotFound or ErrVersionMismatch. PUT and PATCH are implemented in the handler as get → (merge) → validate → rules → conditional replace, retried with backoff up to 3 times; DELETE under access rules is get → rules → conditional delete, retried the same way.

Driver timeouts and network errors are reported as storage.ErrUnavailable, which the HTTP layer maps to 503.

Edit this page · Last updated 08 Oct 2026