Browse all documentation

MongoDB

Limited

Create or configure a Ridu project for the supported MongoDB replica-set deployment.

Choose MongoDB when you can run the supported replica-set topology below. Collections, access rules, REST, the generated SDK, and the admin work the same way as they do with Ridu’s other database adapters; application code does not use MongoDB filters or driver types.

Production support is limited to:

  • an ordinary Ridu-generated starter or blank project;
  • Linux x86-64;
  • MongoDB Community 8.2.9, with release evidence built from digest-pinned images;
  • SCRAM-SHA-256 authentication and TLS with CA and hostname verification; and
  • a writable three-member replica set.

This does not include Atlas, Amazon DocumentDB, Azure Cosmos DB, a standalone mongod, another MongoDB version or topology, another operating system or architecture, arbitrary scale, network-partition/failover matrices, or point-in-time recovery. See Releases and compatibility before choosing this profile.

Create a project

npm create ridu@latest my-app

Choose Starter and MongoDB in the wizard, then run the exact install and dev commands it prints. Review the summary carefully: MongoDB is not the default database choice.

Preselect every scaffold choice

For a non-interactive npm setup, pass every choice as an argument:

terminal
npm create ridu@latest -- \
  --template starter \
  --database mongodb \
  --module github.com/acme/content \
  --scope @acme \
  --package-manager npm \
  --agent codex \
  content

Both generated templates include the same adapter wiring. The dev script starts the pinned local single-node replica set from compose.yaml and uses:

text
mongodb://127.0.0.1:27029/ridu?directConnection=true&replicaSet=ridu-rs0

This convenient plaintext, unauthenticated service is for local development only. It is still a replica set because Ridu operations require MongoDB transactions; a standalone mongod is rejected. To use another development replica set without putting its URL in process arguments, supply a command-scoped environment value from your shell’s secret binding:

DATABASE_URL="$MONGODB_DEVELOPMENT_URL" npm run dev -- --no-docker

Configuration discovery, generate, generate --check, and migrate create through the project-local CLI remain offline. They do not open MongoDB or serialize its URL into config, generated contracts, or migration artifacts.

Add MongoDB to an existing project

Use this path for a clean generated Ridu project before it has committed migration history or live data. Ridu has no generic PostgreSQL/SQLite-to-MongoDB data migration facility. Moving a live dataset is application-owned: export and transform every current document, localized value, version, auth/session record, relationship, task, preference, lock, and upload reference; preserve stable IDs; validate the destination; and rehearse backup, cutover, and rollback.

1. Select MongoDB in project metadata

Replace the existing adapter value. This example starts from PostgreSQL:

ridu.toml
version = 1
database = "postgres"
database = "mongodb"
entry = "./cmd/server"
admin = "./admin"

The value must match the CLI’s public adapter name. It controls development orchestration and migration routing; it does not contain a connection URL.

2. Use the generated adapter handshake

MongoDB development needs more than replacing one Open call. The internal candidate started by ridu dev must verify the resolved manifest’s exact indexes before the stable proxy promotes it. Copy the following generated pattern into cmd/server/main.go (retaining the project’s admin asset and handler options):

cmd/server/main.go
import (
  "context"
  "fmt"
  "log"
  "os"

  "github.com/acme/content/content"
  "github.com/acme/content/internal/adminassets"
  "github.com/riducms/ridu"
  "github.com/riducms/ridu/adapters/postgres"
  "github.com/riducms/ridu/adapters/mongodb"
  "github.com/riducms/ridu/store"
)

const internalDevelopmentServerArgument = "--ridu-internal-development-server"

func main() {
  applicationConfig := content.Config()
  internalDevelopmentServer := len(os.Args) == 2 && os.Args[1] == internalDevelopmentServerArgument
  if internalDevelopmentServer {
    os.Args = os.Args[:1]
  }

  var options []ridu.ExecuteOption
  if len(os.Args) == 1 {
    options = runtimeOptions(applicationConfig)
    options = runtimeOptions(applicationConfig, internalDevelopmentServer)
  }
  if err := ridu.Execute(applicationConfig, options...); err != nil {
    log.Fatal(err)
  }
}

func runtimeOptions(applicationConfig ridu.Config) []ridu.ExecuteOption {
func runtimeOptions(applicationConfig ridu.Config, internalDevelopmentServer bool) []ridu.ExecuteOption {
  return []ridu.ExecuteOption{
    ridu.WithStore(func(ctx context.Context) (store.Store, error) {
      return postgres.OpenWithConfig(ctx, postgres.PoolConfig{
        DatabaseURL:              os.Getenv("DATABASE_URL"),
        AllowInsecureTransport:   envBool("RIDU_ALLOW_INSECURE_DATABASE"),
        MaxUploadLockConnections: envInt32("RIDU_POSTGRES_UPLOAD_LOCK_CONNECTIONS"),
      })
      backend, err := mongodb.OpenWithConfig(ctx, mongodb.Config{
        DatabaseURL:            os.Getenv("DATABASE_URL"),
        AllowInsecureTransport: envBool("RIDU_ALLOW_INSECURE_DATABASE"),
        ApplicationName:        "ridu-server",
      })
      if err != nil {
        return nil, err
      }
      if internalDevelopmentServer {
        manifest, err := ridu.Resolve(applicationConfig)
        if err != nil {
          _ = backend.Close()
          return nil, fmt.Errorf("resolve MongoDB development manifest: %w", err)
        }
        if err := backend.VerifyIndexes(ctx, manifest); err != nil {
          _ = backend.Close()
          return nil, fmt.Errorf("verify MongoDB development indexes: %w", err)
        }
      }
      return backend, nil
    }),
    ridu.WithAddress(env("RIDU_ADDRESS", ":8080")),
    ridu.WithHandlerOptions(ridu.HandlerOptions{
      AdminAssets:             adminassets.FS(),
      AllowedOrigins:          envList("RIDU_ALLOWED_ORIGINS"),
      AllowedHosts:            envList("RIDU_ALLOWED_HOSTS"),
      TrustedProxyCIDRs:       envList("RIDU_TRUSTED_PROXY_CIDRS"),
      ReadinessTimeout:        envDuration("RIDU_READINESS_TIMEOUT"),
      StrictTransportSecurity: os.Getenv("RIDU_STRICT_TRANSPORT_SECURITY"),
    }),
    ridu.WithServerOptions(ridu.ServerOptions{
      ShutdownTimeout:            envDuration("RIDU_SHUTDOWN_TIMEOUT"),
      WorkerDrainTimeout:         envDuration("RIDU_WORKER_DRAIN_TIMEOUT"),
      ReadinessDrainDelay:        envDuration("RIDU_READINESS_DRAIN_DELAY"),
      AllowUnverifiableReadiness: envBool("RIDU_ALLOW_UNVERIFIABLE_READINESS"),
      SkipReadinessPreflight:     envBool("RIDU_SKIP_READINESS_PREFLIGHT"),
      AllowUnverifiableReadiness: internalDevelopmentServer,
      SkipReadinessPreflight:     internalDevelopmentServer,
    }),
  }
}

The red PostgreSQL lines are context from the existing generated server. Remove them after adding the MongoDB lines; the finished file has one adapter import, one runtimeOptions call and declaration, one store factory, and one value for each readiness option.

The readiness exceptions are only for the CLI’s internal development candidate, after the explicit index check. Do not expose them as production environment fallbacks. Keep the template’s envBool helper so invalid environment values fail clearly, and close the backend on every resolution/verification error as shown.

3. Add the local replica-set service

Copy this service into compose.yaml:

compose.yaml
services:
  mongodb:
    image: mongo:8.2.9-noble@sha256:007773db61cb1aa44e526fb7175fc582902e67d4e6cc5f13106445767d46c818
    command: ['mongod', '--replSet', 'ridu-rs0', '--bind_ip_all']
    ports:
      - '127.0.0.1:27029:27017'
    healthcheck:
      test:
        - CMD-SHELL
        - >-
          mongosh --quiet --eval 'try { const h=db.hello(); if (h.setName === "ridu-rs0" && h.isWritablePrimary) { quit(0) }; if (!h.setName) { try { rs.initiate({_id:"ridu-rs0",members:[{_id:0,host:"mongodb:27017"}]}) } catch (_) {} } } catch (_) {}; quit(1)'
      interval: 2s
      timeout: 5s
      retries: 30
      start_period: 5s

The health check initializes ridu-rs0 and succeeds only when the node is its writable primary. Ridu requires sessions and transactions, so a standalone MongoDB server is not sufficient. The local URL is:

text
mongodb://127.0.0.1:27029/ridu?directConnection=true&replicaSet=ridu-rs0

This single-node, unauthenticated, plaintext service is development-only and is not the three-member authenticated TLS production profile.

4. Start and verify development

go mod tidy
npm run dev

ridu dev starts the generated replica-set service, regenerates contracts, synchronizes safe additive changes, and starts the API and admin. Before deployment, create and verify the immutable migration history:

export DATABASE_URL='mongodb://127.0.0.1:27029/ridu?directConnection=true&replicaSet=ridu-rs0'
export RIDU_ALLOW_INSECURE_DATABASE=true
npm run ridu -- migrate create --name initial
npm run ridu -- migrate plan
npm run ridu -- migrate verify
npm run ridu -- migrate up
npm run ridu -- migrate status

For a separately managed development replica set, omit the Compose service and rerun the selected tab’s dev --no-docker command with DATABASE_URL="$MONGODB_DEVELOPMENT_URL". Keep TLS and authentication enabled unless the target is a disposable local development database. Before production, replace the local URL and insecure admission with the authenticated, CA- and hostname-verified three-member profile below and follow the full release cutover.

Development schema changes

During ridu dev, an additive manifest change creates only missing collections and indexes. A separate serving Store then non-mutatingly verifies the exact physical index plan and passes development readiness before the stable proxy promotes the candidate. A failed build, incompatible index, unhealthy candidate, or rejected rename leaves the last working process in place.

Development synchronization never drops state or guesses how stored content should move. For a rename, compiled transform, index replacement, or reviewed retirement, create and apply an immutable production migration instead of repairing MongoDB by hand.

--no-sync skips additive mutation, but it does not weaken verification: the candidate starts only when another owner has already prepared the selected database.

Production connection and credentials

The production DATABASE_URL must select the application database, authenticate with SCRAM-SHA-256, name the replica set, and enable certificate and hostname verification. For example:

text
mongodb://ridu-app:<secret>@mongo-1.example.internal,mongo-2.example.internal,mongo-3.example.internal/content?authSource=content&authMechanism=SCRAM-SHA-256&replicaSet=ridu-rs0&tls=true&tlsCAFile=/run/secrets/mongodb-ca.pem

Keep the running application credential scoped to its database. Use separate, short-lived credentials for verification and backups:

Credential Scope
Application Database-scoped access to the Ridu database. Use it for the running app and, when sufficient, plan, up, and status.
Verification Permission to create and drop the temporary database used by migrate verify.
Backup The database-scoped permissions required by mongodump and mongorestore.

Supply each credential only to its command. Ridu redacts credentials and topology details from normal connection failures, but operators must still keep URLs out of shell history, process arguments, logs, generated files, and image layers. A mode-0600 Database Tools configuration file is one way to keep backup credentials out of process arguments.

RIDU_ALLOW_INSECURE_DATABASE and --allow-insecure-database are for local development only. They do not expand the production support profile.

Immutable migration lifecycle

Artifacts created by ridu migrate create use MongoDB planner contract 2.0.0 inside shared artifact-envelope format 1. Authenticated planner-1.0.0 artifacts remain a supported immutable prefix: the runner validates and replays that committed history before applying v2 artifacts rather than rewriting or rejecting it. Create and inspect the plan before the cutover:

npm run ridu -- migrate create --name add-post-summary
npm run ridu -- migrate plan --json

Ridu binds each artifact to its manifest history and rejects altered or reordered migration files. up takes a fenced lease with a bounded wait, records durable step progress, and resumes the same immutable history after an interruption. It requires explicit maintenance admission only when the pending or incomplete history suffix contains a rename, transform, reference-index rebuild, or resource retirement. Run that work only after every old application process and worker is drained.

verify creates a random isolated database, replays the complete history with the adapter’s migration runner, checks the final ledger and index state, and drops that database. Because the temporary database starts empty, verify requires --allow-maintenance whenever any artifact in the complete history contains semantic work, including work already applied to the live database. status is non-mutating. ridu build embeds a fingerprint of the exact ordered migration filenames and artifact digests. Application readiness requires the live ledger to match that fingerprint, its head to match the executable manifest, and every required Ridu index to pass non-mutating verification. ridu check and ridu build remain offline and fail when committed history does not end at executable config. A direct go build has no fingerprint and fails closed in ordinary production startup. Deployment cutover additionally fails when the applied ledger or physical indexes do not exactly match the release history.

MongoDB does not expose down, reset, refresh, or fresh. Correct forward with another reviewed artifact, or restore the complete matched recovery point.

Deploy and recover

For a code-only replacement with the same manifest digest and migration-history fingerprint, start the candidate, require /readyz, then drain the old process. For any new migration artifact—even an additive or same-manifest data-only one—use the coordinated sequence:

  1. Rehearse the exact binary and history against a restored recovery point.
  2. Drain every old application process and worker.
  3. Run migrate verify through the project-local CLI with DATABASE_URL="$MONGODB_OPERATIONAL_URL" so the shadow-database authority exists only for that command. Append --allow-maintenance whenever the complete committed history contains semantic work.
  4. After verification succeeds, capture one recovery point containing a database-scoped mongodump and the upload store. Use a separately scoped $MONGODB_BACKUP_URL.
  5. Run migrate up through the project-local CLI with DATABASE_URL="$MONGODB_MIGRATION_URL", using the selected database-scoped application or controlled operator identity. Append --allow-maintenance only when the pending or incomplete history suffix contains semantic work.
  6. Run migrate status with the same migration identity and require exact complete history and Ridu-managed index state.
  7. Start only the target release binary carrying the expected manifest and migration-history fingerprints with $MONGODB_APP_URL, then wait for /readyz before admitting traffic.

Take the cutover recovery point only after draining writers and successfully completing the command-scoped verification, but before up. The recovery drill uses database-scoped mongodump --archive --gzip --dumpDbUsersAndRoles, restores that archive into an empty target with mongorestore --archive --gzip --drop --restoreDbUsersAndRoles, and restores upload storage from the same named point. The dump/restore must remain scoped to the selected application database; never restore over unrelated databases. Confirm the ledger and status, the restored scoped user, auth and sessions, content and versions, relationships, SDK/admin workflows, rich text, upload objects, tasks, and representative data before directing traffic.

This is a logical dump/restore path, not point-in-time recovery. Define and rehearse your retention, encryption, off-site storage, recovery-point objective, and recovery-time objective.

Test the deployment

Before production, rehearse migrations, startup, readiness, a primary election, graceful shutdown, and database/upload restoration on the topology you will deploy. Exercise authentication, content, versions, relationships, uploads, tasks, the SDK, and the admin after restoration. Use disposable databases for these tests; never point them at production.