Connection & configuration
mongo, err := core.NewMongoDB(env)
if err != nil {
panic(err) // a misconfigured Mongo is a boot failure
}
app, _ := core.NewApp(env, core.WithMongo("default", mongo))
defer app.Shutdown(context.Background())NewMongoDB pings before returning, so a wrong password or an unreachable replica set stops the process at startup rather than producing a 500 on the first request that needs a document.
Configuration
DB_MONGO_NAME=mydb # always required — not read from the URI
DB_MONGO_CONNECTION_STRING=mongodb://user:pass@host:27017
# or
DB_MONGO_HOST=a,b,c # a comma-separated list is a replica set
DB_MONGO_PORT=27017
DB_MONGO_USERNAME=app
DB_MONGO_PASSWORD=secret
DB_MONGO_REPLICA_NAME=rs0
DB_MONGO_TLS=true
DB_MONGO_MAX_POOL_SIZE=100
DB_MONGO_MIN_POOL_SIZE=5
DB_MONGO_TIMEOUT=30 # seconds, per operationDB_MONGO_NAME is separate from the URI on purpose: a connection string points at a server, and which database on it this service uses is a different decision — one that a staging deploy sharing a cluster has to be able to change without rewriting the URI.
Credentials are percent-encoded before the URI is assembled, so a password holding @, / or : connects instead of producing a parse error that names the password in the message.
Every key and its default is in Configuration → MongoDB.
Replica sets
A replica set is not only about availability — it is what makes transactions and change streams possible at all. A standalone server refuses both.
DB_MONGO_HOST=mongo-0,mongo-1,mongo-2
DB_MONGO_REPLICA_NAME=rs0Ports per host are allowed in the list (a:27018,b,c), which is what a local three-node set on one machine needs.
Anything the fields do not cover
Use the connection string. Read preferences in the URI, retryWrites, authSource, directConnection, a tlsCAFile, an SRV record — none of them have a discrete field, and adding one per driver option would be a worse version of the URI:
DB_MONGO_CONNECTION_STRING=mongodb+srv://u:[email protected]/?retryWrites=true&w=majority&authSource=admin
DB_MONGO_NAME=mydbTimeouts
DB_MONGO_TIMEOUT is the driver's deadline for one operation, in seconds. It is what stops a query started from a context with no deadline — a cron run, a queue consumer, a subscriber — from hanging forever:
DB_MONGO_TIMEOUT=30Requests already carry a deadline from the HTTP layer, so this mostly matters for the paths that do not. Set it anyway: the failure mode it prevents is a worker that looks alive and is doing nothing.
For one query that needs a different bound, MaxTime bounds it on the server, so a slow one is killed there rather than merely abandoned here:
err := m.Find(&rows, "events", filter, core.MongoFindOptions{MaxTime: 5 * time.Second})The pool
DB_MONGO_MAX_POOL_SIZE=100 # driver default is 100
DB_MONGO_MIN_POOL_SIZE=5The same arithmetic as SQL: max pool × replicas must stay under what the server will accept, with room left for other services and an operator's shell.
Read preferences
Send heavy, staleness-tolerant reads to a secondary without changing the connection every other query uses:
reports := ctx.DBMongo().WithReadPreference(readpref.SecondaryPreferred())
err := reports.Aggregate(&rows, "events", pipeline)WithReadPreference returns a handle; it does not mutate the one it was called on. A read from a secondary is a read from behind the primary — right for a dashboard, wrong for "insert it, then read it back".
Named connections
app, _ := core.NewApp(env,
core.WithMongo("default", primary),
core.WithMongo("audit", auditMongo),
)ctx.DBMongo() // "default"
ctx.DBSMongo("audit") // a named one
mongorepo.NewIn[Event](ctx.DBSMongo("audit"))ctx.DBSMongo on a name that was never registered returns the disabled handle rather than nil, so the failure is a MONGO_DISABLED error naming the problem instead of a panic in an unrelated place.
Health checks
if err := ctx.DBMongo().Ping(); err != nil {
return err // readiness, not liveness
}Housekeeping commands — ping, hello, endSessions, the auth handshake — are excluded from the query log, so a probe running every few seconds does not bury what the service actually asked for. See Query logging.
Shutdown
app.Shutdown(ctx) closes the client it was given. Handing a handle to core.WithMongo transfers that responsibility — do not also Close() it.
Escape hatch
m := ctx.DBMongo()
m.Collection("users") // *mongo.Collection
m.Database() // *mongo.Database
m.Client() // *mongo.Client
m.Context() // the context every helper is usingUse m.Context() with them. Inside a transaction that is the session's context, and a driver call made on any other context silently lands outside the transaction — which is the one Mongo mistake that produces data no rollback removes.