How the NDDev backend is built: Rust, one database and an agent instead of an admin panel
NDDev’s six websites in five languages, the inquiry forms, meeting requests, the assistant and content publishing all run on one core. Here are the decisions it is built on and why we made them.
A modular monolith in Rust
The core is one Cargo workspace with three layers. domain holds the rules with no I/O: what a revision, a publication or an inquiry is, and which state transitions are allowed. infrastructure deals with PostgreSQL and external services. api exposes HTTP, a CLI and MCP. SEO and observability are separate Rust services with their own databases: they change at a different pace and fail in different ways.
The stack is Rust 1.99, Tokio, axum, SQLx and PostgreSQL. Identifiers are UUIDv7: the application creates them, and they sort by creation time.
An agent instead of an admin panel
The platform has no visual CMS. The owner and the agents working for the company run it through commands: the CLI, the HTTP API and MCP all call the same command layer. Every command is typed, checks permissions and takes an idempotency key, so repeating a request with the same key returns the earlier result instead of creating a duplicate. An edit names the document version it expects: if the document has changed in the meantime, the command refuses rather than silently overwriting someone else’s work.
Even the sites’ contacts and labels are not constants in code but revisions in the database. They are changed by command and published together with the content.
One database and reliable background jobs
All state lives in PostgreSQL. Background work — SEO, translations, site builds, email — is modelled as jobs in the same database: an outbox and inbox, job leases with fencing tokens, short transactions. A separate message broker is not needed at this load. We do not promise exactly-once external effects: an external service’s result is accepted only if it answers work that was actually commissioned, against the same set of source data.
Revisions, and publishing as a snapshot
Every edit creates a new immutable revision. Publishing assembles a package of specific revisions and their SEO artifacts and gives it a generation number. The sites are built from that package’s manifest, so rebuilding produces the same pages. Restoring an earlier version of a page means publishing its earlier revision — it is still there.
SEO as a separate service
Every indexable revision commissions SEO work from a separate service. A model proposes the title and description, a separate editor request checks them against the source text, and Rust verifies the quotations, the snippet shape and the absence of invented prices or metrics. Canonical URLs, robots rules, hreflang and schema.org markup are built deterministically. If the model is unavailable, the result is honestly marked as a fallback and can only be published by an explicit decision.
A frontend under the publisher’s control
The frontend is a separate Astro repository. The core’s publisher builds it in a sandbox: isolated Linux namespaces, Landlock, no network and no access to secrets. The publisher measures the output itself and only then rolls it out to the sites; the previous frontend version comes back with a single command.
Data and latency
The core writes in Amsterdam, and a synchronous replica of the database runs in Kazakhstan. We measured the alternative: moving the writer to Kazakhstan while the API stays in Amsterdam makes every database statement cross a link of about 90 ms. Submitting an inquiry would grow from 119 ms to 2.58 s, opening a catalogue from 61 to 558 ms, search from 55 to 929 ms. A synchronous replica costs one round trip per commit.
Observability
Traces and metrics are sent as OpenTelemetry, but the core does not depend on the receiver: buffers are bounded, and if collection is down, requests keep being served.
What this means for client projects
We do not bring this whole architecture into every project — a small website does not need it. But a few rules apply everywhere: explicit contracts between parts of a system, changes that cannot be lost or applied twice, measurable behaviour in production, and a way back to the previous version without editing data by hand.